С# - результат несогласованной математической операции по 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-битные сборки одинаковыми настройками и настройками?]