Ira*_*chi 43 c# double decimal
问题摘要:
对于某些十进制值,当我们将类型从十进制转换为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
Run Code Online (Sandbox Code Playgroud)
查看评论中的结果.结果从调试器的手表中复制.产生这种效果的数字的十进制数字远远少于数据类型的限制,所以它不能是溢出(我猜!).
更有趣的是,可以有两个相等的十进制值(在上面的代码示例中,参见"dcm"和"dcm2","deltaDcm"等于零),在转换时产生不同的双精度值.(在代码中,"dbl"和"dbl2",它们具有非零"deltaDbl")
我想它应该是与两种数据类型中数字的按位表示的差异相关的东西,但是无法弄清楚是什么!而且我需要知道如何按照我需要的方式进行转换.(比如dcm2 - > dbl2)
Jon*_*eet 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));
}
}
Run Code Online (Sandbox Code Playgroud)
结果:
8224055000.00000095367431640625
8224055000
Run Code Online (Sandbox Code Playgroud)
现在的问题是,为什么原来的值(8224055000.0000000000),这是一个整数- ,准确地表示为一个double-与额外的数据结束了我强烈怀疑这是由于用于从转换算法怪癖.decimal到double,但它的不幸.
它还违反了C#规范的第6.2.1节:
对于从十进制到float或double的转换,十进制值四舍五入为最接近的double或float值.虽然这种转换可能会失去精度,但它永远不会导致异常抛出.
"最近的双重值"显然只是8224055000 ......所以这是一个错误的IMO.虽然这不是我希望很快得到修复的.(顺便说一下,它在.NET 4.0b1中给出了相同的结果.)
为避免该错误,您可能希望首先对十进制值进行标准化,从而有效地"删除"小数点后的额外0.这有点棘手,因为它涉及96位整数运算 - .NET 4.0 BigInteger类可能会使它更容易,但这可能不适合你.
Ant*_*hyy 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
Run Code Online (Sandbox Code Playgroud)
对于double,我使用点来分隔符号,指数和尾数字段; 对于十进制,请参见decimal.GetBits上的MSDN,但基本上最后的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") ;
Run Code Online (Sandbox Code Playgroud)
这篇文章有什么每台计算机科学家应该知道关于浮点运算将是一个很好的开端.
简短的回答是浮点二进制算术必然是一个近似值,并不总是你猜的近似值.这是因为CPU在基数2中进行算术,而人类(通常)在基数10中进行算术运算.由此产生了各种各样的意外效果.