Как получить одинаковое поведение для операций с двойной точностью в 32-битном и 64-битном режимах? - PullRequest
0 голосов
/ 10 октября 2018

У меня есть библиотека, которую я конвертирую в 64-битную.Однако я не могу получить точные битовые результаты в 64-битном режиме, поэтому мои тесты не пройдены.

Я сократил проблему до простого теста:

#include <stdio.h>

int main(void) {
    printf("%d bits: ", sizeof(void*) * 8);
    volatile double d = 10.870191700000001;
    volatile double x = 0.10090000000000002;
    d += x * 30.07;
    printf("%0.15f\n", d);
}

Чтобы избежатьразличия компилятора, я использую тот же компилятор и кросс-компиляцию.В этом случае я использую 64-разрядную версию 5.1.0 TDM-GCC в Windows 7 в процессоре Core i5.Вот моя командная строка:

gcc double_test.c -o double_test.exe -m32 -O0 && double_test.exe && gcc double_test.c -o double_test.exe -m64 -O0 && double_test.exe

И вывод:

32 bits: 13.904254700000001
64 bits: 13.904254700000003

В этом случае ошибка минимальна, но в моих полных тестовых примерах ошибка может складываться ихватит, чтобы удвоить вывод.

Как получить точные битовые операции для совпадения с 32-разрядным выводом?

Ближайшее, что я получил к чему-то актуальному, былоиспользуйте -ffloat-store, но в этом фрагменте он получил 32-битное выполнение, как 64-битное, а мне нужно прямо противоположное.Однако это не оказало заметного влияния на мою библиотеку.Я также протестировал опции -fexcess-precision=standard и -mfp-math безрезультатно.

Ответы [ 2 ]

0 голосов
/ 10 октября 2018

Я бы не стремился воспроизвести 32-битный вывод, поскольку это является следствием чрезмерной точности в 32-битном x86 (x87) ABI и, возможно, также несоответствия компилятора.Вместо этого попробуйте сопоставить 64-битный вывод, который вы должны ожидать для хороших целей.Если у вас все в порядке с машиной с sse2 +, -mfpmath=sse заставит 32-битный x86 вести себя как 64-битный и другие более разумные цели.

Если вам действительно нужен результат из 32-битногоx86, в идеале вы должны написать это переносимо.Это может включать разбивку на пару double s, но для x86-only вы можете просто использовать long double.В конкретном примере в вашем вопросе, функция fma тоже будет работать.

0 голосов
/ 10 октября 2018

Поскольку вы сказали, что вам нужен более точный результат ...01, а также детерминизм, вы, к сожалению, не можете просто использовать -msse2 -mfpmath=sse в своей 32-битной сборке.Будущие читатели, ищущие детерминизм, должны использовать это.


Вы можете использовать -mfpmath=387, чтобы попросить gcc использовать медленную / устаревшую математику x87 в 64-битном режиме, где это не по умолчанию.Соглашение о вызовах передает / возвращает аргументы FP в регистрах xmm, так что это даже хуже, чем в 32-битном режиме, иногда требуя дополнительного сохранения / перезагрузки.

peter@volta:/tmp$ gcc -m64 -mfpmath=387 -O3 fp-prec.c -o fp-64-387
peter@volta:/tmp$ ./fp-64-387 
64 bits: 13.904254700000001

Я не уверен, строго ли gcc ограничивает себядо x87, когда возможна автовекторизация.Если это так, то вам не хватает производительности.


И кстати, в вашем примере ...01 является результатом сохранения точности extra в 80-битном временном коде дляx*30.07 перед добавлением его к d.(d равно volatile, но d += stuff по-прежнему эквивалентно d = d + stuff, поэтому x*30.07 не округляется до 64-разрядного double первого).

Youможно использовать long double, например, d += x * (long double)30.07 для принудительной установки 80-битного временного значения. long double - это 80 бит в x86-64 System V ABI Linux / OS X / * BSD / etc, но в Windows x64это так же, как 64-битный double.Так что это может быть не вариант для вас.


В этом случае вы можете получить тот же результат с FMA, который сохраняет бесконечную точность для умножения перед выполнением сложения.Это медленно на оборудовании без поддержки FMA, но fma(d, 30.07, x) надежно даст желаемый результат.

Если вам это нужно, используйте его там, где требуется такая точность.

ЕслиВы компилируете с включенным FMA, он может быть встроен в инструкцию FMA.(например, -march=native на моем процессоре Skylake)

Даже без использования функции fma() math.h, gcc будет сокращать выражения mul + и добавлять их в FMA при оптимизации.(В отличие от Clang, который, я думаю, не делает FP_CONTRACT по умолчанию без -ffast-math).Обратите внимание, что я не , используя -march=387

 # your original source code, using an FMA instruction (native=skylake in my case)
peter@volta:/tmp$ gcc -m64 -march=native -O3 fp-prec.c -o fp-64-native
peter@volta:/tmp$ ./fp-64-native 
64 bits: 13.904254700000001

Соответствующая часть main:

 57e:   c5 fb 10 44 24 08       vmovsd xmm0,QWORD PTR [rsp+0x8] # load x
 584:   c5 fb 10 0c 24          vmovsd xmm1,QWORD PTR [rsp]     # load d
 589:   c4 e2 f1 99 05 d6 01 00 00      vfmadd132sd xmm0,xmm1,QWORD PTR [rip+0x1d6]        # the 30.07 constant
 592:   c5 fb 11 04 24          vmovsd QWORD PTR [rsp],xmm0     # store d
 597:   c5 fb 10 04 24          vmovsd xmm0,QWORD PTR [rsp]     # reload d
 59c:   e8 8f ff ff ff          call   530 <printf@plt>

FP детерминизм жесткий в целом.

См. Также https://randomascii.wordpress.com/2013/07/16/floating-point-determinism/ и https://randomascii.wordpress.com/2012/03/21/intermediate-floating-point-precision/

...