Подтвердить что ты не робот

Изменения в Math.Exp или двойная реализация в .net 4.5.2

Если я запустил оператор

Math.Exp(113.62826122038274).ToString("R")

на машине с установленной .net 4.5.1, затем я получаю ответ

2.2290860617259248E+49

Однако, если я запустил ту же команду на компьютере с установленной установкой .net framework 4.5.2, тогда я получаю ответ

2.2290860617259246E+49

(т.е. окончательная цифра изменяется)

Я понимаю, что это очень незначительно в чистых числовых выражениях, но кто-нибудь знает о каких-либо изменениях, внесенных в .net 4.5.2, которые объяснят изменение?

(Я не предпочитаю один результат другому, мне просто интересно понять, почему он изменился)

Если я вывешу

The input in roundtrip format
The input converted to a long via BitConverter.DoubleToInt64Bits
Math.Exp in roundtrip format
Math.Exp converted to a long via BitConverter.DoubleToInt64Bits

тогда на 4.5.1 я получаю

113.62826122038274
4637696294982039780
2.2290860617259248E+49
5345351685623826106

а на 4.5.2 я получаю:

113.62826122038274
4637696294982039780
2.2290860617259246E+49
5345351685623826105

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

Подробнее:

Скомпилирован один раз с использованием VS2015

Обе машины, на которых я запускаю двоичные файлы, являются 64-битными

У вас установлен .net 4.5.1, другой 4.5.2

Просто для ясности: преобразование строк не имеет значения... Я получаю изменение результатов независимо от того, связано ли преобразование строк. Я упомянул об этом исключительно для демонстрации изменений.

4b9b3361

Ответ 1

Вздох, тайны математики с плавающей запятой продолжают пнуть программистов навсегда. Это не имеет ничего общего с версией фреймворка. Соответствующим параметром является Project > Properties > Build tab.

Платформа target = x86: 2.2290860617259248E+49
Платформа target = AnyCPU или x64: 2.2290860617259246E+49

Если вы запускаете программу в 32-разрядной операционной системе, вы всегда получаете первый результат. Обратите внимание, что формат roundtrip недооценен, он содержит больше цифр, чем может хранить двойной файл. Который 15. Считайте их, вы получите 16. Это гарантирует, что двоичное представление double, 1s и 0s одинаково. Разница между этими двумя значениями является наименее значимым битом в мантиссе.

Причина, по которой LSB не то же самое, заключается в том, что джиттер x86 обременен созданием кода для FPU. Который имеет очень нежелательное свойство использовать больше бит точности, чем двойной может хранить. 80 бит вместо 64. Теоретически для получения более точных результатов расчета. Что он делает, но редко воспроизводимым образом. Небольшие изменения кода могут привести к большим изменениям в результате вычисления. Просто запуск кода с прикрепленным отладчиком может изменить результат, поскольку это отключает оптимизатор.

Intel исправила эту ошибку с помощью набора инструкций SSE2, полностью заменив математические инструкции с плавающей запятой FPU. Он не использует дополнительную точность, у double всегда есть 64 бита. С очень желательным свойством, что результат вычисления теперь больше не зависит от промежуточного хранилища, теперь он намного более согласован. Но менее точный.

Что джиттер x86 использует инструкции FPU - это исторический случай. Выпущенный в 2002 году процессоров вокруг поддерживаемого SSE2 было недостаточно. Эта авария больше не может быть исправлена, поскольку она изменяет наблюдаемое поведение программы. Это не было проблемой для джиттера x64, и 64-битный процессор также будет поддерживать SSE2.

32-разрядный процесс использует функцию exp(), которая использует код FPU. 64-битный процесс использует функцию exp(), которая использует SSE-код. Результат может отличаться одним LSB. Но все же точность до 15 значащих цифр, это 2.229086061725925E + 49. Все, что вы можете ожидать от математики с двойным.

Ответ 2

.NET использует математические функции lib из CRT для выполнения этих вычислений. CRT, используемый .NET, часто обновляется с каждой версией, поэтому вы можете ожидать, что результаты будут изменены между версиями .NET, однако они всегда будут в пределах обещанного +/1ulp.

Ответ 3

Я справился с одной и той же проблемой, но я получаю ее только после установки .Net 4.6.

Обновление .Net 4.6 обновлено c:\windows\system32\msvcr120_clr0400.dll и c:\windows\syswow64\msvc120_clr0400.dll.

Перед установкой эти библиотеки DLL имели свойства файла → подробности: "Версия файла 12.0.51689.34249" и "Название продукта Microsoft Visual Studio 12 CTP"

После установки .net 4.6 у них были "File version 12.0.52512.0" и "Product name Microsoft Visual Studio 2013"

Я изменил свой тест, чтобы включить ваш пример, и я вижу то же самое до/после чисел, перебрасывая между версиями этой DLL.

(Наш тестовый пакет не показывал каких-либо измененных результатов при работе на .net версиях 4, 4.5, 4.5.1 или 4.5.2 до тех пор, пока эти библиотеки не были обновлены.)

Ответ 4

Чтобы показать, как проекты, ориентированные на разные версии .NET, влияют на двойное преобразование из строки, я построил 4 проекта, предназначенных для разных версий, которые все работают на одном компьютере с .NET 4.6.

Здесь код

double foo = Convert.ToDouble("33.94140881672595");

И здесь вывод

33.941408816725954 (.NET 4)

33.941408816725946 (.NET 4.5)

33.941408816725946 (.NET 4.5.2)

33.941408816725946 (.NET 4.6)

Таким образом, после .NET 4 было показано изменение метода преобразования