Очевидный алгоритм заключается в сравнении абсолютных значений и использовании их для выбора исходных значений.
Если это абсолютно необходимо без веток (например, для криптобезопасности), будьте осторожны с ? :
ternary. Он часто компилируется в ассемблер без веток, но это не гарантируется. (Я предполагаю, что именно поэтому вы пометили branch-prediction? Если бы это было просто из соображений производительности, компилятор, как правило, принимал бы правильные решения.)
В языках с целыми числами с фиксированным дополнением до 2 помните, что abs(INT_MIN)
переполняет знаковый результат той же ширины, что и ввод. В C и C++ функция abs()
неудобна для возврата int
, и поведение undefined вызывает ее с наиболее отрицательным целым числом в дополнении до 2 в дополнении до 2. системы. В системах с четко определенной математикой для подписанных целых чисел (например, gcc -fwrapv
или, может быть, Java) подписанное abs(INT_MIN)
будет переполняться обратно в INT_MIN, давая неправильные результаты, если вы выполняете сравнение со знаком, потому что INT_MIN максимально далеко от 0.
Убедитесь, что вы выполняете беззнаковое сравнение результатов abs
, чтобы правильно обработать INT_MIN
. (Или, как предлагает @kaya3, преобразуйте положительные целые числа в отрицательные, а не отрицательные в положительные.)
Безопасная реализация C, которая позволяет избежать неопределенного поведения:
unsigned absu(int x) {
return x<0? 0U - x : x;
}
int minabs(int a, int b) {
return absu(a) < absu(b) ? a : b;
}
Обратите внимание, что <
против <=
на самом деле имеет значение в minabs
: это решает, какой из них выбрать, если их величины равны.
0U - x
преобразует x
в unsigned
перед вычитанием из 0, что может привести к переполнению. Преобразование отрицательных целых чисел со знаком в беззнаковые четко определено в C и C++ как сокращение по модулю (в отличие от чисел с плавающей запятой, UB IIRC). На машинах с дополнением до 2 это означает использование одного и того же битового шаблона без изменений.
Это хорошо компилируется для x86-64 (Godbolt), особенно с clang. (GCC избегает cmov
даже с -march=skylake
, что приводит к худшей последовательности. За исключением окончательного выбора после выполнения обеих операций absu, тогда он использует cmovbe
, что составляет 2 мопов вместо 1 для cmovb
на процессорах Intel, потому что ему нужно читать оба ZF и CF. Если бы он уже получил противоположное значение в EAX, он мог бы использовать cmovb
.)
# clang -O3
absu:
mov eax, edi
neg eax # sets flags like sub-from-0
cmovl eax, edi # select on signed less-than condition
ret
minabs:
mov ecx, edi
neg ecx
cmovl ecx, edi # inlined absu(a)
mov eax, esi
mov edx, esi
neg edx
cmovl edx, esi # inlined absu(b)
cmp ecx, edx # compare absu results
cmovb eax, edi # select on unsigned Below condition.
ret
Полностью без веток с GCC и clang, с включенной оптимизацией. Можно с уверенностью сказать, что другие ISA будут такими же.
Он может прилично автоматически векторизовать, но x86 не имеет целочисленного сравнения SIMD без знака до AVX512. (Вы можете эмулировать, перевернув старший бит, чтобы использовать целое число со знаком pcmpgtd
).
Для float / double abs
дешевле и не может переполняться: просто очистите бит знака, а затем используйте его для выбора оригинала.
person
Peter Cordes
schedule
11.12.2020
min(a, b, key=abs)
. - person Stef   schedule 11.12.2020argmin
вместоmin
. И если на самом деле язык не имеет такой функции в своей стандартной библиотеке, ее легко закодировать с помощью простой итерации по списку значений. Однако ваш вопрос не касается конкретного языка. На самом деле, действительно неясно, в чем именно заключается ваш вопрос. - person Stef   schedule 11.12.2020abs
, чтобы правильно обработатьabs(INT_MIN)
, который (если он будет подписан) переполнится обратно в INT_MIN, даже если он максимально далек от 0. Но да ,abs(a) < (unsigned) abs(b) ? a : b
должно работать в языках, где возвращаемое значениеabs
тупо подписано. За исключением того, что это по-прежнему связано с переполнением со знаком в C, что является неопределенным поведением, если abs фактически выполняется со знаком. Итак, вы действительно хотите реализовать свой собственныйabsu(int)
какreturn x<0? 0U - x : x;
- person Peter Cordes   schedule 11.12.20200U - x
вместо(unsigned)-x
, т.е. преобразованиеx
в беззнаковое перед вычитанием. godbolt.org/z/K4abbf - person Peter Cordes   schedule 11.12.2020a
иb
целые или с плавающей запятой? - person John Alexiou   schedule 11.12.2020&
и сдвиги вправо для удвоения? - person Peter Cordes   schedule 11.12.2020&
, я думаю, не манипулируя фактическими битовыми шаблонами FP. В идеале вы можете написать что-то, что позволит V8 сохранять значения в 32-битном форматеint
вместо фактического преобразования обратно в двойное. - person Peter Cordes   schedule 11.12.2020