#define USES_ARITHMETIC_SHR(TYPE) ((TYPE)(-1) >> 1 == (TYPE)(-1))
int asr(int value, int amount) /* Better codegen on some older compilers */
{
return !USES_ARITHMETIC_SHR(int) && value < 0 ? ~(~value >> amount) : value >> amount ;
}
int asr2(int value, int amount) /* Completely portable */
{
return value < 0 ? ~(~value >> amount) : value >> amount ;
}
Этот код решает, использовать ли сначала встроенный оператор >>
или нет.Возможно, вы захотите либо доверять, либо не доверять препроцессору, дающему тот же результат, что и целевая архитектура, но безопасным резервом является не доверять ему.
Давайте объясним часть value < 0 ? ~(~value >> amount) : value >> amount
.
- Если
value >= 0
, то не имеет значения, является ли >>
логическим или арифметическим, мы можем его использовать. - Если
value < 0
, тогда ~value
- это побитовое дополнение, которое будетположительное число и (~value >> amount)
будет переносимым (верхнее amount
количество бит будет очищено, остальные сдвинуты вправо, как и ожидалось).
~(~value >> amount)
перевернет все биты назад, включая переворачивание верхней amount
количество нулей в единицах, которое является именно тем, что вы хотите с арифметическим смещением вправо.
Код, предполагающий USES_ARITHMETIC_SHR(int) == true
, компилируется с -O2
в:
asr(int, int): // x86-64 GCC 4.4.7
mov eax, edi
mov ecx, esi
sar eax, cl
ret
asr(int, int): // x86-64 Clang 3.4.1
mov cl, sil
sar edi, cl
mov eax, edi
ret
asr(int, int): // ARM GCC 4.5.4
mov r0, r0, asr r1
bx lr
This должен быть портативным, но я также не уверен, действительно ли он педантичен.Если это не так, вы можете #define USES_ARITHMETIC_SHR(TYPE) false
или просто отказаться от проверки и только проверить value < 0
.Но это приводит к менее оптимальному коду на некоторых старых компиляторах.
Новейшая версия компиляторов (GCC 8+, Clang 7+) компилирует обе версии, asr
и asr2
, в одну и ту же эффективнуюсборка, как указано выше, так что вы можете использовать любую версию кода.Ниже показано, как старые компиляторы работают с asr2
, очень переносимым решением.
asr2(int, int): // x86-64 GCC 4.4.7
test edi, edi
js .L8
mov eax, edi
mov ecx, esi
sar eax, cl
ret
.L8:
mov eax, edi
mov ecx, esi
not eax
sar eax, cl
not eax
ret
asr2(int, int): // x86-64 Clang 3.4.1
mov cl, sil
sar edi, cl
mov eax, edi
ret
asr2(int, int): // ARM GCC 4.5.4
cmp r0, #0
mvnlt r0, r0
mvnlt r0, r0, asr r1
movge r0, r0, asr r1
bx lr