Преобразование float в double теряет точность C # - PullRequest
0 голосов
/ 07 декабря 2018

У меня есть следующий код

    double temp3 = 61.01;

    //This can actually be various other types, but float is the one that causes problems
    dynamic temp = 61.01f;
    double temp2 = (double)Convert.ChangeType(temp, typeof(double));

    double newValue = temp2 - temp3;

    //newValue should == 0 but it does not

    Console.WriteLine(String.Format("  {0:F20}", temp));
    Console.WriteLine(String.Format("  {0:F20}", temp2));
    Console.WriteLine(String.Format("  {0:F20}", temp3));
    Console.WriteLine(String.Format("  {0:F20}", newValue));

, который производит

61.01000000000000000000

61.00999832153320000000

61.01000000000000000000

-0.000085488479*

Почему Convert.ChangeType теряет точность?

Мы используем Convert.ChangeType из-за использования динамической переменной, которая может быть byte / uint / float / double и т. Д.

Ответы [ 2 ]

0 голосов
/ 07 декабря 2018

Проблема, наблюдаемая в этом вопросе, в основном вызвана выбором Microsoft форматирования, в частности, то, что программное обеспечение Microsoft не отображает точные значения, поскольку ограничивает количество цифр, используемых для преобразования в десятичную форму, даже когда строка формата запрашивает больше цифр.Кроме того, он использует меньше цифр при преобразовании float, чем при преобразовании double.Таким образом, если отформатированы float и double с одним и тем же значением, результаты могут отличаться, поскольку при форматировании float будет использоваться меньше значащих цифр.

Ниже я рассмотрю операторы кода ввопрос один за другим.Таким образом, суть дела в том, что значение 61.0099983215332 отформатировано как «61.0100000000000», когда оно равно float, и «61.0099983215332», когда оно равно double.Это чисто форматирование, выбранное Microsoft, и оно не вызвано природой арифметики с плавающей запятой.

Оператор double temp3 = 61.01 инициализирует temp3 ровно 61,00999999999999801048033987171947956085205078125.Это изменение с 61.01 необходимо из-за характера двоичного формата с плавающей точкой - он не может точно представлять 61.01, поэтому используется ближайшее значение, представляемое в double.

Оператор dynamic temp = 61.01f инициализирует temp точно 61.009998321533203125.Как и в случае double, было использовано ближайшее представимое значение, но, поскольку float имеет меньшую точность, ближайшее значение не так близко, как в случае double.

Оператор double temp2 = (double)Convert.ChangeType(temp, typeof(double));преобразует temp в double, имеющее то же значение, что и temp, поэтому оно имеет значение 61.009998321533203125.

Оператор double newValue = temp2 - temp3; правильно вычитает два значения, обеспечивая точный результат 0,00000167846679488548033987171947956085205078125, сбез ошибок.

Оператор Console.WriteLine(String.Format(" {0:F20}", temp)); форматирует float с именем temp.Форматирование float включает вызов Single.ToString.Документация Microsoft немного расплывчата.Он говорит, что по умолчанию возвращаются только семь (десятичных) цифр точности.В нем говорится, что для получения до девяти нужно использовать форматы G или R, а F20 не использует ни G, ни R.Поэтому я считаю, что используются только семь цифр.Когда 61.009998321533203125 округляется до семи значащих десятичных цифр, результатом является «61.01000».Затем метод ToString дополняет его до двадцати цифр после десятичной точки, производя «61.01000000000000000000».

Далее я рассмотрю ваш третий оператор WriteLine и позже вернусь ко второму.

Оператор Console.WriteLine(String.Format(" {0:F20}", temp3)); форматирует double с именем temp3.Поскольку temp3 является double, вызывается Double.ToString.Этот метод использует 15 цифр точности (если не используются G или R).Когда 61.00999999999999801048033987171947956085205078125 округляется до 15 значащих десятичных цифр, получается «61.0100000000000».Затем метод ToString дополняет его до двадцати цифр после десятичной точки, создавая «61.01000000000000000000».

Оператор Console.WriteLine(String.Format(" {0:F20}", temp2)); форматирует double с именем temp2.temp2 - это double, который содержит значение из float temp, поэтому он содержит 61.009998321533203125.Когда это значение преобразуется в 15 значащих десятичных цифр, получается результат «61.0099983215332».Затем метод ToString дополняет его до двадцати цифр после десятичной точки, создавая «61.00999832153320000000».

Наконец, оператор Console.WriteLine(String.Format(" {0:F20}", newValue)); форматирует newValue.Форматирование .00000167846679488548033987171947956085205078125 до 15 значащих цифр дает «0,00000167846679488548».

0 голосов
/ 07 декабря 2018

это потому, что числа с плавающей запятой имеют меньшее количество битов, чем удваивается.Число с плавающей точкой обычно имеет 23 бита, а число с двойным числом - 52. Поэтому, преобразовывая, вы в основном отбираете биты до тех пор, пока не сможете поместить их в число с плавающей точкой.Вот почему вы теряете точность.

...