问题摘要:
对于某些小数值,当我们将类型从十进制转换为double时,会在结果中添加一小部分。
更糟糕的是,可能存在两个“相等”的十进制值,这些值在转换时会产生不同的双精度值。
代码示例:
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)
答案 0 :(得分:49)
有趣 - 虽然当您对确切结果感兴趣时,我通常不相信写出浮点值的正常方法。
这是一个稍微简单的演示,使用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节:
对于从十进制到float或double的转换,十进制值四舍五入为 最近的double或float值。虽然这种转换可能会失去精确度,但它绝不会导致 抛出的例外。
“最接近的双倍值”显然只是8224055000 ...所以这是一个错误的IMO。虽然这不是我希望很快得到修复的。 (顺便说一下,它在.NET 4.0b1中给出了相同的结果。)
为了避免这个错误,你可能想先将十进制值标准化,有效地“删除”小数点后的额外0。这有点棘手,因为它涉及96位整数运算 - .NET 4.0 BigInteger
类可能会使它更容易,但这可能不适合你。
答案 1 :(得分:25)
答案在于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
对于double,我使用点来分隔符号,指数和尾数字段;对于十进制,请参阅MSDN on decimal.GetBits,但基本上最后的96位是尾数。注意dcm2
的尾数位和dbl2
的最高有效位是如何完全一致的(不要忘记1
尾数中隐含的double
位,并且实际上这些位代表8224055000. dbl
的尾数位与dcm2
和dbl2
中的尾数位相同,但对于最低有效位中的讨厌1
。 dcm
的指数为10,尾数为82240550000000000000。
更新II:实际上很容易丢掉尾随的零。
// 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") ;
答案 2 :(得分:5)
文章What Every Computer Scientist Should Know About Floating-Point Arithmetic将是一个很好的起点。
简短的回答是浮点二进制算术必然是近似,并且它并不总是您猜测的近似值。这是因为CPU在基数2中进行算术,而人类(通常)在基数10中进行算术运算。由此产生了各种各样的意外效果。
答案 3 :(得分:2)
要更清楚地看到这个问题,可以在LinqPad中尝试这个(或者替换所有的.Dump()&如果您想要更改为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
答案 4 :(得分:1)