Проблема, наблюдаемая в этом вопросе, в основном вызвана выбором 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».