С# с двойной точностью до десятичной точности
У меня есть двойной "138630.78380386264"
, и я хочу преобразовать его в десятичную, однако, когда я это делаю, я делаю это либо путем литья, либо с помощью Convert.ToDecimal()
, и я теряю точность.
Что происходит? Оба десятичных и двойных могут содержать это число:
![enter image description here]()
double doub = double.Parse("138630.78380386264");
decimal dec = decimal.Parse("138630.78380386264");
string decs = dec.ToString("F17");
string doubse =DoubleConverter.ToExactString(doub);
string doubs = doub.ToString("F17");
decimal decC = (decimal) doub;
string doudeccs = decC.ToString("F17");
decimal decConv = Convert.ToDecimal(doub);
string doudecs = decConv.ToString("F17");
Также: как я могу заставить ToString()
on double распечатать тот же результат, что и отладчик? например 138630.78380386264
?
Ответы
Ответ 1
138630.78380386264
не является точно представимым для двойной точности. Ближайшее число двойной точности (как найдено здесь) составляет 138630.783803862635977566242218017578125
, что согласуется с вашими выводами.
Вы спрашиваете, почему преобразование в десятичное не содержит больше точности. В документации для Convert.ToDecimal()
есть ответ:
Десятичное значение, возвращаемое этим методом, содержит максимум 15 значащих цифр. Если параметр значения содержит более 15 значащих цифр, он округляется с округлением до ближайшего. В следующем примере показано, как метод Convert.ToDecimal(Double) использует округление до ближайшего, чтобы вернуть десятичное значение с 15 значащими цифрами.
Двойное значение, округленное до ближайшего из 15 значащих цифр, равно 138630.783803863
, как показано выше.
Ответ 2
Это, к сожалению, неудачно. Около 139 000, a Decimal
имеет гораздо лучшую точность, чем Double
. Но все же из-за этой проблемы у нас разные Double
проецируются на тот же Decimal
. Например
double doub1 = 138630.7838038626;
double doub2 = 138630.7838038628;
Console.WriteLine(doub1 < doub2); // true, values differ as doubles
Console.WriteLine((decimal)doub1 < (decimal)doub2); // false, values projected onto same decimal
На самом деле существует шесть различных представляемых Double
значений между doub1
и doub2
выше, поэтому они не совпадают.
Вот несколько глупый work-aronud:
static decimal PreciseConvert(double doub)
{
// Handle infinities and NaN-s first (throw exception)
// Otherwise:
return Decimal.Parse(doub.ToString("R"), NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint);
}
Строка формата "R"
обеспечивает включение достаточного количества лишних фигур, чтобы сделать инъекцию отображения (в области, где Decimal
имеет превосходную точность).
Обратите внимание, что в некотором диапазоне a long
(Int64
) имеет точность, превосходящую точность Double
. Поэтому я проверил, сделаны ли преобразования таким же образом (первое округление до 15 значащих знаков после запятой). Они не! Итак:
double doub3 = 1.386307838038626e18;
double doub4 = 1.386307838038628e18;
Console.WriteLine(doub3 < doub4); // true, values differ as doubles
Console.WriteLine((long)doub3 < (long)doub4); // true, full precision of double used when converting to long
Кажется непоследовательным использовать другое "правило", когда цель Decimal
.
Обратите внимание, что из-за этого (decimal)(long)doub3
дает более точный результат, чем просто (decimal)doub3
.