Как обеспечить одинаковые числа с плавающей запятой в разных системах?

Если я скомпилирую следующие строки c в Windows и Linux (ubuntu), я получу разные результаты. Я хотел бы избежать. Как мне это сделать?

 double a = DBL_EPSILON;
 double b = sqrt(a);
 printf("eps = %.20e\tsqrt(eps) = %.20e\n", a, b);

линукс вывод:

eps = 2.22044604925031308085e-16        sqrt(eps) = 1.49011611938476562500e-08

вывод окна:

eps = 2.22044604925031310000e-016       sqrt(eps) = 1.49011611938476560000e-008

В Linux протестировано с помощью gcc и clang на 32-битной и 64-битной системе результат одинаков. В окнах, протестированных с помощью gcc-mingw на 32-разрядной версии и Visual-Studio с 32-разрядной и 64-разрядной версиями, также получены одинаковые результаты.


person Will    schedule 26.11.2012    source источник
comment
Какова базовая архитектура каждой системы?   -  person Mike    schedule 26.11.2012
comment
Реализации printf используемых библиотек c различаются. Windows печатает только 17 значащих цифр или около того и заполняет нулями, если требуется больше. glibc печатает правильно округленное значение.   -  person Daniel Fischer    schedule 26.11.2012
comment
но мне кажется, что это не просто проблема печати, мой численный алгоритм также возвращает немного другие результаты. И я указал на эти строки, так как предположил, что они являются источником различий.#   -  person Will    schedule 26.11.2012
comment
Переменная double обычно имеет точность около 16 значащих десятичных цифр. Дальше печатать или сравнивать цифры бессмысленно.   -  person Blastfurnace    schedule 26.11.2012
comment
Конечно, но в любом случае они влияют на результат числовой операции.   -  person Will    schedule 26.11.2012
comment
Он не зависит от платформы, так как Ubuntu Linux может работать на нескольких разных платформах, включая Система IBM Z.   -  person Bo Persson    schedule 27.11.2012
comment
@Blastfurnace: нет смысла печатать цифры, превышающие 16. Для представления точного значения 64-битного двоичного числа с плавающей запятой IEEE 754 может потребоваться 54 десятичных цифры, и может потребоваться увидеть точное значение для различных причинам, таким как передача значений в математическое программное обеспечение, способное работать с большей точностью, или для отладки.   -  person Eric Postpischil    schedule 27.11.2012
comment
Если вы не привержены C, у Java есть режим strictfp, в котором округление фиксируется для разных платформ и реализаций, за исключением некоторых функций, таких как Math.sin.   -  person Patricia Shanahan    schedule 27.11.2012
comment
... Не обращайте внимания на тот факт, что 17 десятичных цифр требуются для дифференциации всех значений с двойной точностью, а не 16.   -  person Stephen Canon    schedule 27.11.2012


Ответы (1)


В приведенном вами примере кажется, что обе программы имеют одинаковые числа с плавающей запятой. Просто печатают по другому. Самое простое решение этой конкретной проблемы — написать собственную функцию печати с плавающей запятой. Если вы не ожидаете слишком хорошего результата, вы можете использовать функцию здесь как псевдокод для написания своего на C. Он не правильно округлен, но работает для того, для чего предназначен (то есть воспроизводим и читабелен выходы).


Более глубокая проблема, с которой вы столкнулись, — это вычисления с плавающей запятой, дающие разные результаты на разных платформах. Это результат того, что стандарт (стандарты) C не вынуждает компиляторы точно реализовывать стандарт IEEE 754 с плавающей запятой, в частности, обеспечивая более высокую точность для промежуточных результатов. И эта относительная снисходительность стандарта (стандартов) C вызвана, по крайней мере, частично историческими инструкциями x86 с плавающей запятой, что делает дорогостоящей реализацию точной семантики IEEE 754.

В Linux, если вы используете GCC, попробуйте вариант компиляции -msse2. РЕДАКТИРОВАТЬ: ОП прокомментировал, что -msse2 -mfpmath=sse работал на него. Это заставляет GCC генерировать современные инструкции SSE2, которые дают точную семантику IEEE 754 с плавающей запятой. Если в Windows вы также используете GCC, используйте ту же опцию.

Если вы используете Visual C: Visual C использует еще один трюк, чтобы заставить исторические инструкции с плавающей запятой соответствовать семантике IEEE 754: он сообщает старому 80-битному оборудованию с плавающей запятой использовать только столько значащих битов, сколько IEEE 754 двойной точности. имеет. Это дает точную симуляцию чисел с двойной точностью, за исключением нескольких крайних случаев, с которыми вы не столкнетесь. В этом случае было бы полезно (*), если бы ваша программа использовала только числа с двойной точностью (тип C double).

(*) Компилятор Visual C теоретически мог бы генерировать код, который вычисляет точную арифметику с одинарной точностью, округляя каждый промежуточный результат от двойной до одинарной точности, но это было бы дорого, и я сомневаюсь, что он это делает.

person Pascal Cuoq    schedule 26.11.2012
comment
Я не знаю, назвал бы я переполнение и неполное переполнение угловыми случаями, с которыми вы не столкнетесь; они удивительно часто встречаются в реальном коде. - person Stephen Canon; 27.11.2012
comment
@StephenCanon Вы меня поймали, возможно, я был не совсем честен: может случиться недополнение и переполнение. Моя проблема в том, что я не могу предложить никакого решения, если это произойдет, за исключением ручной вставки в программу везде, где это может произойти, intermediate_result = intermediate_result >= 0x1.0p+1024 ? inf : intermediate_result;. И еще хуже обстоят дела с недостаточным потоком, для которого я не вижу никакого переносимого кода для усечения мантиссы. Если программист знает, что он использует x87, то два вызова ldexp() должны сделать это, но это сбросит денормали в ноль на оборудовании IEEE 754. - person Pascal Cuoq; 27.11.2012
comment
@StephenCanon Кроме того, решение «два вызова ldexp ()» вводит двойное округление. - person Pascal Cuoq; 27.11.2012
comment
Да, это невероятно сложный вопрос; будем надеяться, что вопрошающему посчастливится избежать этого. - person Stephen Canon; 27.11.2012
comment
Кажется, что gcc -msse2 -mfpmath=sse работает для моей проблемы. Спасибо! - person Will; 28.11.2012