C# - Непоследовательный результат математической операции на 32-разрядном и 64-разрядном

Рассмотрите следующий код:

double v1 = double.MaxValue;
double r = Math.Sqrt(v1 * v1);

r = дважды. MaxValue на 32-разрядной машине r = Бесконечность на 64-разрядной машине

Мы разрабатываем на 32-разрядной машине и таким образом не знающий о проблеме, пока не уведомлено клиентом. Почему такое несоответствие происходит? Как предотвратить это?

11
задан david.healed 17 March 2010 в 10:09
поделиться

5 ответов

Набор инструкций x86 имеет сложные проблемы с согласованностью операций с плавающей запятой из-за того, как работает FPU. Внутренние вычисления выполняются с более значимыми битами, чем может быть сохранено в double, вызывая усечение, когда число сбрасывается из стека FPU в память.

Это было исправлено в компиляторе x64 JIT, он использует инструкции SSE, регистры SSE имеют такой же размер, как двойной.

Это будет байт, когда ваши вычисления проверят границы точности и диапазона с плавающей запятой. Вы никогда не захотите приблизиться к тому, чтобы вам нужно было больше 15 значащих цифр, вы никогда не захотите приближаться к 10E308 или 10E-308. Вы, конечно, никогда не захотите возводить в квадрат наибольшее представимое значение. Это никогда не проблема, числа, которые представляют физические величины, не могут быть близки.

Воспользуйтесь этой возможностью, чтобы выяснить, что не так в ваших расчетах. Очень важно, чтобы вы использовали ту же операционную систему и оборудование, которые использует ваш клиент, и пора получить машины, необходимые для этого. Код доставки, который тестируется только на машине x86, не тестируется.

Исправление в вопросах и ответах: Project + Properties, вкладка Compile, Platform Target = x86.


Фу, плохой результат на x86 вызван ошибкой в ​​JIT-компиляторе. Он генерирует следующий код:

      double r = Math.Sqrt(v1 * v1);
00000006  fld         dword ptr ds:[009D1578h] 
0000000c  fsqrt            
0000000e  fstp        qword ptr [ebp-8] 

Инструкция fmul отсутствует, удалена оптимизатором кода в режиме выпуска. Без сомнения, это вызвано тем, что он видит значение double.MaxValue. Это ошибка, вы можете сообщить о ней на connect.microsoft.com. Хотя почти уверен, что они не собираются это исправлять.

21
ответ дан 3 December 2019 в 03:18
поделиться

Это почти дубликат

Почему это вычисление с плавающей запятой дает разные результаты на разных машинах?

Мой ответ на этот вопрос также отвечает на этот. Вкратце: различное оборудование может давать более или менее точные результаты в зависимости от деталей оборудования.

Как этого не допустить? Поскольку проблема заключается в микросхеме, у вас есть два варианта. (1) Не выполняйте математические вычисления с числами с плавающей запятой. Делайте все свои математические вычисления в целых числах. Целочисленная математика на 100% согласована от микросхемы к микросхеме. Или (2) потребовать, чтобы все ваши клиенты использовали то же оборудование, на котором вы разрабатываете.

Обратите внимание: если вы выберете (2), у вас все еще могут быть проблемы; мелкие детали, например, была ли программа скомпилирована для отладки или для розничной продажи, могут повлиять на то, выполняются ли вычисления с плавающей запятой с дополнительной точностью или нет. Это может привести к противоречивым результатам между отладочной и розничной сборками, что также является неожиданным и запутанным. Если ваше требование согласованности более важно, чем ваше требование скорости, вам придется реализовать свою собственную библиотеку с плавающей запятой, которая выполняет все свои вычисления в целых числах.

3
ответ дан 3 December 2019 в 03:18
поделиться

Я пробовал это в x86 и x64 в режиме отладки и выпуска:

x86 debug:   Double.MaxValue
x64 debug:   Infinity
x86 release: Infinity
x64 release: Infinity

Похоже, что такой результат можно получить только в режиме отладки.

Не знаю, почему существует разница, код x86 в режиме отладки:

            double r = Math.Sqrt(v1 * v1);
00025bda  fld         qword ptr [ebp-44h] 
00025bdd  fmul        st,st(0) 
00025bdf  fsqrt            
00025be1  fstp        qword ptr [ebp-5Ch] 
00025be4  fld         qword ptr [ebp-5Ch] 
00025be7  fstp        qword ptr [ebp-4Ch] 

такой же, как код в режиме выпуска:

            double r = Math.Sqrt(v1 * v1);
00000027  fld         qword ptr [ebp-8] 
0000002a  fmul        st,st(0) 
0000002c  fsqrt            
0000002e  fstp        qword ptr [ebp-18h] 
00000031  fld         qword ptr [ebp-18h] 
00000034  fstp        qword ptr [ebp-10h]
2
ответ дан 3 December 2019 в 03:18
поделиться

Проблема в том, что Math.Sqrt ожидает двойное значение в качестве аргумента. v1 * v1 не может быть сохранен как double и переполняется , что приводит к неопределенному поведению .

1
ответ дан 3 December 2019 в 03:18
поделиться

double.MaxValue * double.MaxValue - это переполнение.

Вам следует избегать переполнения вычислений, а не полагаться на 32-битное поведение, о котором вы сообщили (что, как прокомментировано, кажется маловероятным).

[У 32-битной и 64-битной версий одинаковая конфигурация и настройки?]

1
ответ дан 3 December 2019 в 03:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: