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

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

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

r = double.MaxValue на 32-битной машине r = Бесконечность на 64-битной машине

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

Ответ 1

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

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

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

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

Исправление Q & D - это Project + Properties, вкладка Compile, Platform Target = x86.


Fwiw, плохой результат на 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. Понятно, что они не собираются исправлять это, хотя.

Ответ 2

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

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

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

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

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

Ответ 3

Я пробовал это в 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]

Ответ 4

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

Ответ 5

double.MaxValue * double.MaxValue является переполнением.

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

[Являются ли 32-битные и 64-битные сборки одинаковыми настройками и настройками?]