Математически найти значение, ближайшее к 0

Есть ли способ математически определить, ближе ли значение к 0, чем другое?

Например, closerToZero(-2, 3) вернет -2.

Я попытался удалить знак, а затем сравнил значения для минимума, но тогда я бы назначал беззнаковую версию исходных чисел.

a и b являются совместимыми с IEEE-754 числами с плавающей запятой (номер js)

(64 бита => 1 бит знака 11 бит экспоненты 52 бита дроби)

min (a,b) => b-((a-b)&((a-b)>>52));
result = min(abs(a), abs(b));
// result has the wrong sign ... 

person Twiggeh    schedule 11.12.2020    source источник
comment
В питоне вы можете сделать min(a, b, key=abs).   -  person Stef    schedule 11.12.2020
comment
В других языках может быть что-то под названием argmin вместо min. И если на самом деле язык не имеет такой функции в своей стандартной библиотеке, ее легко закодировать с помощью простой итерации по списку значений. Однако ваш вопрос не касается конкретного языка. На самом деле, действительно неясно, в чем именно заключается ваш вопрос.   -  person Stef    schedule 11.12.2020
comment
Вам нужно сделать это без филиала? Или вы просто надеетесь сделать его эффективным для непредсказуемых значений? Не ясно из тега branch-prediction, потому что в вашем коде нет ветвей для прогнозирования, и вы ничего не сказали в тексте.   -  person Peter Cordes    schedule 11.12.2020
comment
В математике вы бы написали это так: a, если abs(a)‹=abs(b), b иначе. Это так же легко перевести на язык программирования.   -  person Henry    schedule 11.12.2020
comment
@Henry: В языках с целыми числами с фиксированным дополнением до 2 убедитесь, что вы выполняете беззнаковое сравнение результатов abs, чтобы правильно обработать 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.2020
comment
@Генри Спасибо! Как-то забыл, что переменные можно присваивать условно -.-   -  person Twiggeh    schedule 11.12.2020
comment
@PeterCordes напишет ответ с вашим дополнением к комментарию Генри, если никто из вас не опередит меня.   -  person Twiggeh    schedule 11.12.2020
comment
Я уже превращал свой комментарий в ответ, так как понял, что в основном это уже был один. Я предполагаю, что вы ориентируетесь на язык низкого уровня, такой как C или C++, и заботитесь о том, как они компилируются в asm, на основе тега предсказания ветвления.   -  person Peter Cordes    schedule 11.12.2020
comment
Вы можете избежать проблемы INT_MIN, сопоставив положительные числа с отрицательными, а не наоборот.   -  person kaya3    schedule 11.12.2020
comment
@ kaya3: О, здорово, это интересный трюк для языков, которые не дают легкого доступа к неподписанным. (Я думаю, как в Java?) В C вполне возможно избежать переполнения со знаком в абстрактной машине C, но при этом полностью эффективно компилировать в asm на машине с дополнением до 2 с 0U - x вместо (unsigned)-x, т.е. преобразование x в беззнаковое перед вычитанием. godbolt.org/z/K4abbf   -  person Peter Cordes    schedule 11.12.2020
comment
a и b целые или с плавающей запятой?   -  person John Alexiou    schedule 11.12.2020
comment
Какой язык вы используете, который допускает побитовое двоичное И с & и сдвиги вправо для удвоения?   -  person Peter Cordes    schedule 11.12.2020
comment
@PeterCordes, то есть js: D Я хотел довести систему коллизий сущностей до предела ради развлечения, сделав все возможное для оптимизации кода. (Копирую систему коллизий из Frostbite). Деветвление обычно приводит к увеличению скорости от 20 до 40% в js на V8.   -  person Twiggeh    schedule 11.12.2020
comment
О, так вы запускаете неявное преобразование из FP в целое число с помощью JavaScript &, я думаю, не манипулируя фактическими битовыми шаблонами FP. В идеале вы можете написать что-то, что позволит V8 сохранять значения в 32-битном формате int вместо фактического преобразования обратно в двойное.   -  person Peter Cordes    schedule 11.12.2020


Ответы (1)


Очевидный алгоритм заключается в сравнении абсолютных значений и использовании их для выбора исходных значений.

Если это абсолютно необходимо без веток (например, для криптобезопасности), будьте осторожны с ? : 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