问题总结:
对于一些十进制值,当我们的类型转换为十进制翻一番,一小部分被添加到结果。
是什么使情况变得更糟,是有可能转换时产生不同的双值的两个“平等”的十进制值。
代码示例:
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
看看在评论结果。 结果从调试器的手表复制。 产生这种效果的数字有小数位数比数据类型的限制少得多,所以它不可能是一个溢出(我猜的!)。
什么使得它更有趣的是,可以有两个相等的十进制值(上面的代码示例中,请参见“DCM”和“DCM2”,用“deltaDcm”等于零),从而导致不同的双精度值转换时。 (在代码中,“DBL”和“DBL2”,它有一个非零“deltaDbl”)
我想这应该是在这两个数据类型与差异中的数字的按位表示的东西,但无法弄清楚什么! 我需要知道怎样做才能使转换我需要它的方式。 (像DCM2 - > DBL2)
有趣的 - 虽然我一般不信任写出浮点值,当你感兴趣的精确结果的正常方式。
这里有一个稍微简单的演示,使用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));
}
}
结果:
8224055000.00000095367431640625
8224055000
现在的问题是,为什么原来的值(8224055000.0000000000),这是一个整数- ,准确地表示为一个double
-与额外的数据结束了我强烈怀疑这是由于用于从转换算法怪癖, decimal
到double
,但它的不幸的。
它也违反了C#规范的6.2.1:
对于从decimal浮动或双转换,十进制值被舍入到最近的双键或浮点值。 尽管这种转换可能会损失精度,但从来没有导致异常被抛出。
“最近双重价值”显然是刚刚8224055000 ...所以这是一个错误IMO。 这不是一个我期望得到固定的任何时间,虽然很快。 (它给的方式在.NET 4.0b1相同的结果。)
为了避免错误,你可能需要首先规范的十进制值,有效地“清除”了小数点后的额外0。 这是有点棘手,因为它涉及到96位的整数运算-在.NET 4.0 BigInteger
类可能更容易,但可能不适合你的选择。
答案就在于这样一个事实: decimal
企图保存的显著位数。 因此, 8224055000.0000000000m
具有20个显著位数和存储为82240550000000000000E-10
,而8224055000m
仅具有10和被存储为8224055000E+0
。 double
的尾数(逻辑上)53个比特,即至多16个十进制数字。 这正是当你转换成你得到的精度double
,确实杂散1
在你的例子是在第16位小数。 转换不是1对1的,因为double
用途基座2。
这里是你的数字的二进制表示:
dcm:
00000000000010100000000000000000 00000000000000000000000000000100
01110101010100010010000001111110 11110010110000000110000000000000
dbl:
0.10000011111.1110101000110001000111101101100000000000000000000001
dcm2:
00000000000000000000000000000000 00000000000000000000000000000000
00000000000000000000000000000001 11101010001100010001111011011000
dbl2 (8224055000.0):
0.10000011111.1110101000110001000111101101100000000000000000000000
对于双,我用点分隔符号,指数和尾数领域; 对于小数,请参阅MSDN上decimal.GetBits ,但本质上是最后96位的尾数。 注意怎样的尾数位dcm2
和最显著位dbl2
相吻合,准确(不要忘了隐含的1
比特double
的尾数),而事实上这些位表示8224055000.的尾数位dbl
是一样的在dcm2
和dbl2
但讨厌1
在最显著位。 的指数dcm
为10,尾数为82240550000000000000。
更新二:它实际上是很容易砍掉尾随零。
// 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") ;
这篇文章有什么每台计算机科学家应该知道关于浮点运算将是一个很好的开端。
简短的回答是浮点二进制算术必然是一个近似值 ,它并不总是你会猜逼近。 这是因为CPU的做在基座2运算,而人类(通常)在底座10做算术有各种各样的,从这个干意想不到的效果。
要看到这个问题更清楚地说明试试这个在LinqPad(或替换所有使用.dump()'S和变化,以Console.WriteLine()■如果你喜欢)。
这似乎逻辑上不正确,我认为小数的精度可能会导致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
这是一个老问题,一直是StackOverflow上很多类似的问题的主题。
简单化的解释是十进制数字不能精确二进制表示
此链接是这或许可以解释这个问题的文章。