C удваиваются, чем удваивается .NET?
Сравнивая некоторый C-код и F #, я пытаюсь его заменить, я заметил, что в конечном результате были некоторые отличия.
Работая с резервным копированием кода, я обнаружил, что даже при наличии различий - хотя и крошечных.
Код начинается с чтения данных из файла. и самый первый номер выходит по-другому. Например, в F # (проще script):
let a = 71.9497985840
printfn "%.20f" a
Я получаю ожидаемый (для меня) вывод 71.94979858400000000000
.
Но в C:
a = 71.9497985840;
fprintf (stderr, "%.20f\n", a);
выводит 71.94979858400000700000
.
Откуда это 7?
Разница лишь крошечная, но меня это беспокоит, потому что я не знаю почему. (Это также беспокоит меня, потому что это затрудняет отслеживание, когда мои две версии кода расходятся)
Ответы
Ответ 1
Это различие в печати. Преобразование этого значения в IEEE754 double
дает
Prelude Text.FShow.RealFloat> FD 71.9497985840
71.94979858400000694018672220408916473388671875
но представления 71.949798584
достаточно, чтобы отличить число от его соседей. C, когда его просят распечатать с точностью до 20 цифр после того, как десятичная точка преобразует значение, правильно округленное до нужного количества цифр, очевидно, что F # использует кратчайшее однозначно определяющее представление и накладывает его на нужное число 0, как это делает Haskell.
Ответ 2
Это просто другое округление. Номера одинаковы (по крайней мере, по CPython):
>>> '%.44f' % 71.94979858400000000000
'71.94979858400000694018672220408916473388671875'
>>> '%.44f' % 71.94979858400000700000
'71.94979858400000694018672220408916473388671875'
Ответ 3
Это метод .NET System.Double.ToString() - это разница, метод, который преобразует double в строку. Вы можете посмотреть соответствующий код, загрузив источник CLR, как указано в SSCLI20. Преобразование выполняется с помощью функции clr/src/vm/comnumber.cpp, COMNumber:: FormatDouble(). Что выглядит как это, комментарий в коде является наиболее описательным из того, что происходит:
//In order to give numbers that are both friendly to display and round-trippable,
//we parse the number using 15 digits and then determine if it round trips to the same
//value. If it does, we convert that NUMBER to a string, otherwise we reparse using 17 digits
//and display that.
Библиотека времени C не имеет этой функции.
Ответ 4
Другие ответы адекватно объясняют источник проблемы (двойная точность и округление).
Если ваши номера, как правило, имеют умеренную величину, а десятичная точность очень важна (более высокая, чем скорость вычисления), то, возможно, подумайте об использовании формата .NET decimal
. Это дает вам 28-29 точных десятичных знаков точности без дробных двоичных ошибок округления, таких как double. Ограничение в том, что диапазон меньше (без больших экспонентов!).
http://msdn.microsoft.com/en-us/library/364x0z75%28v=vs.100%29.aspx
Ответ 5
Дополнительная информация для тех, кто наткнулся на это.
Используя биты найденного кода здесь, я подтвердил (я считаю) утверждение о том, что базовое двоичное представление (по крайней мере для этого конкретного номера) одно и то же.
Вот примеры кода - обратите внимание на "умножение нуля на ноль", чтобы устранить отрицательный ноль, который является уродливым при преобразовании в длинный.
//(C# this time)
var d = 71.9497985840; //or other incoming double value
if(d == 0) d = d * d; //for negative zero
var longval = System.BitConverter.DoubleToInt64Bits(d); // = 4634763433907061836
В C:
double d;
long long a;
d = 71.9497985840; //or other incoming double value
if(d == 0) d = d * d; //for negative zero
a = *(long long*)&d; //= 4634763433907061836
update. Я прошел через и обнаружил, что несоответствие вводилось во время инверсии матрицы, потому что каждая система вызывала другую библиотеку, реализуя инверсию по-другому...