在 C# 中将小数转换为双数会导致差异
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1584314/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Conversion of a decimal to double number in C# results in a difference
提问by Iravanchi
Summary of the problem:
问题总结:
For some decimal values, when we convert the type from decimal to double, a small fraction is added to the result.
对于一些十进制值,当我们将类型从十进制转换为双精度时,结果中会添加一小部分。
What makes it worse, is that there can be two "equal" decimal values that result in different double values when converted.
更糟糕的是,可能有两个“相等”的十进制值在转换时会导致不同的双精度值。
Code sample:
代码示例:
decimal dcm = 8224055000.0000000000m; // dcm = 8224055000
double dbl = Convert.ToDouble(dcm); // dbl = 8224055000.000001
decimal dcm2 = Convert.ToDecimal(dbl); // dcm2 = 8224055000
double dbl2 = Convert.ToDouble(dcm2); // dbl2 = 8224055000.0
decimal deltaDcm = dcm2 - dcm; // deltaDcm = 0
double deltaDbl = dbl2 - dbl; // deltaDbl = -0.00000095367431640625
Look at the results in the comments. Results are copied from debugger's watch. The numbers that produce this effect have far less decimal digits than the limit of the data types, so it can't be an overflow (I guess!).
看看评论中的结果。结果从调试器的监视中复制。产生这种效果的数字比数据类型的限制少得多,所以它不可能是溢出(我猜!)。
What makes it much more interesting is that there can be two equaldecimal values (in the code sample above, see "dcm" and "dcm2", with "deltaDcm" equal to zero) resulting in differentdouble values when converted. (In the code, "dbl" and "dbl2", which have a non-zero "deltaDbl")
更有趣的是,可以有两个相等的十进制值(在上面的代码示例中,请参阅“dcm”和“dcm2”,“deltaDcm”等于零)在转换时导致不同的双精度值。(在代码中,“dbl”和“dbl2”具有非零的“deltaDbl”)
I guess it should be something related to difference in the bitwise representation of the numbers in the two data types, but can't figure out what! And I need to know what to do to make the conversion the way I need it to be. (like dcm2 -> dbl2)
我想这应该与两种数据类型中数字的按位表示的差异有关,但无法弄清楚是什么!而且我需要知道如何按照我需要的方式进行转换。(如 dcm2 -> dbl2)
采纳答案by Jon Skeet
Interesting - although I generally don't trust normal ways of writing out floating point values when you're interested in the exact results.
有趣 - 尽管当您对确切结果感兴趣时,我通常不相信写出浮点值的正常方法。
Here's a slightly simpler demonstration, using DoubleConverter.cs
which I've used a few times before.
这是一个稍微简单的演示,DoubleConverter.cs
我以前使用过几次。
using System;
class Test
{
static void Main()
{
decimal dcm1 = 8224055000.0000000000m;
decimal dcm2 = 8224055000m;
double dbl1 = (double) dcm1;
double dbl2 = (double) dcm2;
Console.WriteLine(DoubleConverter.ToExactString(dbl1));
Console.WriteLine(DoubleConverter.ToExactString(dbl2));
}
}
Results:
结果:
8224055000.00000095367431640625
8224055000
Now the question is why the original value (8224055000.0000000000) which is an integer - and exactly representable as a double
- ends up with extra data in. I strongly suspect it's due to quirks in the algorithm used to convert from decimal
to double
, but it's unfortunate.
现在的问题是,为什么作为整数的原始值 (8224055000.0000000000)double
最终会包含额外的数据。我强烈怀疑这是由于用于从decimal
to转换的算法中的怪癖double
,但这很不幸。
It also violates section 6.2.1 of the C# spec:
它还违反了 C# 规范的第 6.2.1 节:
For a conversion from decimal to float or double, the decimal value is rounded to the nearest double or float value. While this conversion may lose precision, it never causes an exception to be thrown.
对于从十进制到 float 或 double 的转换,十进制值将四舍五入到最接近的 double 或 float 值。虽然这种转换可能会失去精度,但它永远不会导致抛出异常。
The "nearest double value" is clearly just 8224055000... so this is a bug IMO. It's not one I'd expect to get fixed any time soon though. (It gives the same results in .NET 4.0b1 by the way.)
“最近的双倍值”显然只是 8224055000 ......所以这是一个错误 IMO。不过,这不是我希望很快得到修复的问题。(顺便说一下,它在 .NET 4.0b1 中给出了相同的结果。)
To avoid the bug, you probably want to normalize the decimal value first, effectively "removing" the extra 0s after the decimal point. This is somewhat tricky as it involves 96-bit integer arithmetic - the .NET 4.0 BigInteger
class may well make it easier, but that may not be an option for you.
为避免该错误,您可能希望首先对十进制值进行标准化,从而有效地“删除”小数点后多余的 0。这有点棘手,因为它涉及 96 位整数算术 - .NET 4.0BigInteger
类很可能使它更容易,但这可能不是您的选择。
回答by Greg Hewgill
The article What Every Computer Scientist Should Know About Floating-Point Arithmeticwould be an excellent place to start.
文章What Every Computer Scientist should Know About Floating-Point Arithmetic将是一个很好的起点。
The short answer is that floating-point binary arithmetic is necessarily an approximation, and it's not always the approximation you would guess. This is because CPUs do arithmetic in base 2, while humans (usually) do arithmetic in base 10. There are a wide variety of unexpected effects that stem from this.
简短的回答是浮点二进制算术必然是一个近似值,它并不总是你会猜到的近似值。这是因为 CPU 以 2 为基数进行算术运算,而人类(通常)以 10 为基数进行算术运算。由此产生了多种意想不到的效果。
回答by pavium
This is an old problem, and has been the subject of many similar questions on StackOverflow.
这是一个老问题,并且一直是 StackOverflow 上许多类似问题的主题。
The simplisticexplanation is that decimal numbers can't be exactly represented in binary
在简单的解释是,十进制数字不能精确二进制表示
This linkis an article which might explain the problem.
这个链接是一篇可以解释这个问题的文章。
回答by Anton Tykhyy
The answer lies in the fact that decimal
attempts to preserve the number of significant digits. Thus, 8224055000.0000000000m
has 20 significant digits and is stored as 82240550000000000000E-10
, while 8224055000m
has only 10 and is stored as 8224055000E+0
. double
's mantissa is (logically) 53 bits, i.e. at most 16 decimal digits. This is exactly the precision you get when you convert to double
, and indeed the stray 1
in your example is in the 16th decimal place. The conversion isn't 1-to-1 because double
uses base 2.
答案在于decimal
试图保留有效数字的数量这一事实。因此,8224055000.0000000000m
有 20 个有效数字并存储为82240550000000000000E-10
,而8224055000m
只有 10个有效数字并存储为8224055000E+0
。double
的尾数是(逻辑上)53 位,即最多16 位十进制数字。这正是您转换为 时获得的精度double
,并且1
您示例中的偏差确实位于小数点后 16 位。转换不是 1 对 1,因为double
使用基数 2。
Here are the binary representations of your numbers:
以下是您的数字的二进制表示:
dcm:
00000000000010100000000000000000 00000000000000000000000000000100
01110101010100010010000001111110 11110010110000000110000000000000
dbl:
0.10000011111.1110101000110001000111101101100000000000000000000001
dcm2:
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000001 11101010001100010001111011011000
dbl2 (8224055000.0):
0.10000011111.1110101000110001000111101101100000000000000000000000
For double, I used dots to delimit sign, exponent and mantissa fields; for decimal, see MSDN on decimal.GetBits, but essentially the last 96 bits are the mantissa. Note how the mantissa bits of dcm2
and the most significant bits of dbl2
coincide exactly (don't forget about the implicit 1
bit in double
's mantissa), and in fact these bits represent 8224055000. The mantissa bits of dbl
are the same as in dcm2
and dbl2
but for the nasty 1
in the least significant bit. The exponent of dcm
is 10, and the mantissa is 82240550000000000000.
对于 double,我使用点来分隔符号、指数和尾数字段;对于十进制,请参阅MSDN on decimal.GetBits,但基本上最后 96 位是尾数。注怎样的尾数位dcm2
和最显著位dbl2
重合准确(不要忘了隐含1
在位double
的尾数),而事实上这些位表示8224055000.的尾数位dbl
是相同dcm2
和dbl2
但对于1
最不重要的位令人讨厌。的指数dcm
为 10,尾数为 82240550000000000000。
Update II:It is actually very easy to lop off trailing zeros.
更新二:实际上很容易删除尾随零。
// There are 28 trailing zeros in this constant —
// no decimal can have more than 28 trailing zeros
const decimal PreciseOne = 1.000000000000000000000000000000000000000000000000m ;
// decimal.ToString() faithfully prints trailing zeroes
Assert ((8224055000.000000000m).ToString () == "8224055000.000000000") ;
// Let System.Decimal.Divide() do all the work
Assert ((8224055000.000000000m / PreciseOne).ToString () == "8224055000") ;
Assert ((8224055000.000010000m / PreciseOne).ToString () == "8224055000.00001") ;
回答by Ilan
To see this problem more plainly illustrated try this in LinqPad (or replace all the .Dump()'s and change to Console.WriteLine()s if you fancy).
要更清楚地看到这个问题,请在 LinqPad 中尝试这个(或者替换所有 .Dump() 并更改为 Console.WriteLine()s 如果你喜欢)。
It seems logically incorrect to me that the precision of the decimal could result in 3 different doubles. Kudos to @AntonTykhyy for the /PreciseOne idea:
对我来说,小数的精度可能导致 3 个不同的双精度值在逻辑上是不正确的。感谢@AntonTykhyy 提出的 /PreciseOne 想法:
((double)200M).ToString("R").Dump(); // 200
((double)200.0M).ToString("R").Dump(); // 200
((double)200.00M).ToString("R").Dump(); // 200
((double)200.000M).ToString("R").Dump(); // 200
((double)200.0000M).ToString("R").Dump(); // 200
((double)200.00000M).ToString("R").Dump(); // 200
((double)200.000000M).ToString("R").Dump(); // 200
((double)200.0000000M).ToString("R").Dump(); // 200
((double)200.00000000M).ToString("R").Dump(); // 200
((double)200.000000000M).ToString("R").Dump(); // 200
((double)200.0000000000M).ToString("R").Dump(); // 200
((double)200.00000000000M).ToString("R").Dump(); // 200
((double)200.000000000000M).ToString("R").Dump(); // 200
((double)200.0000000000000M).ToString("R").Dump(); // 200
((double)200.00000000000000M).ToString("R").Dump(); // 200
((double)200.000000000000000M).ToString("R").Dump(); // 200
((double)200.0000000000000000M).ToString("R").Dump(); // 200
((double)200.00000000000000000M).ToString("R").Dump(); // 200
((double)200.000000000000000000M).ToString("R").Dump(); // 200
((double)200.0000000000000000000M).ToString("R").Dump(); // 200
((double)200.00000000000000000000M).ToString("R").Dump(); // 200
((double)200.000000000000000000000M).ToString("R").Dump(); // 199.99999999999997
((double)200.0000000000000000000000M).ToString("R").Dump(); // 200
((double)200.00000000000000000000000M).ToString("R").Dump(); // 200.00000000000003
((double)200.000000000000000000000000M).ToString("R").Dump(); // 200
((double)200.0000000000000000000000000M).ToString("R").Dump(); // 199.99999999999997
((double)200.00000000000000000000000000M).ToString("R").Dump(); // 199.99999999999997
"\nFixed\n".Dump();
const decimal PreciseOne = 1.000000000000000000000000000000000000000000000000M;
((double)(200M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.0M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.00M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.0000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.00000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.0000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.00000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.0000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.00000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.0000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.00000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.000000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.0000000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.00000000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.000000000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.0000000000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.00000000000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.000000000000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.0000000000000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.00000000000000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.000000000000000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.0000000000000000000000000M/PreciseOne)).ToString("R").Dump(); // 200
((double)(200.00000000000000000000000000M/PreciseOne)).ToString("R").Dump(); // 200