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. Я прошел через и обнаружил, что несоответствие вводилось во время инверсии матрицы, потому что каждая система вызывала другую библиотеку, реализуя инверсию по-другому...