浮点数精度丢失的"bug"不停的被种语言的开发人员或者数据库开发人员发现,或许有些人已经听说了其中的原因,但是仍然无法把握其出现的规律。解决问题的最好办法是让错误原因重现,而能让错误随时重现,说明该错误已经在你掌控之中,从而可以在真正的开发中去避免。 我们看两个场景: 一个是c#代码,他没有按照预期的输出0.1,而是0.10000000149011612
Console.WriteLine(Convert.ToDouble( 0.1f ));
一个是SQL代码,他和c#代码出现了同样的错误 declare @f float ( 23 ) declare @s float ( 53 ) set @f = 0.1 set @s = @f select @s
这个错误出于什么原因,最权威的解释就是ieee754浮点数标准,可以参考msdn: http://msdn.microsoft.com/zh-cn/library/0b34tf65(VS.80).aspx 如果你觉得他的描述过于枯燥,或者还无法与上面的错误示例结合起来的话,可以参考我的一些理解
我们用的很多十进制数是无法转为精确的二进制数的,包括0.1,0.2,0.11这样简单的小数对于IEEE浮点数格式来说是个无限循环小数,0.25,0.5,0.375这样的小数才能转为精确的二进制小数。 举个例子,比如十进制0.1和十进制0.375 这两个数,我们来手工转一下,思路很简单,同小学生学科学记数法一样。 0.375=0.375 * 2^2 *2^(-2)=1.5*2^(-2) //整数位在1-2之间,我们以前十进制也是这样做的!! 0.5=0.5*2*2^(-1) 0.375=2^-(2)+2^(-1 + -2)=0.011 //他是个有限小数 如果用分数表达可能更明了点 0.375=1/4+1/8 = 0.01+0.001=0.011 --------- 0.1=1.6*(1/16) =1/16+0.6/16 =1/16+1.2/32 =1/16+1/32+0.2/32 =1/16+1/32+1.6/2^8 =1/2^4+1/2^5+1/2^8+0.6/2^8 ...显然和第二步的步骤一样了回到了0.6,他是个无限循环小数 0.1=0.00011001 00011001 00011001 00011001 ...无论机器尾数多长都不会精确的何况只有23或52位 然后我们再看以下一段代码测试的例子:
using System; using System.Collections.Generic; using System.Text; namespace ConsoleApplication19 { class Program { static void Main(string[] args) { //无限循环小数的0.1,在数据类型转换时出现精度丢失。 Console.WriteLine(Convert.ToDouble(0.1f)); //有限小数的0.375不会出现。 Console.WriteLine(Convert.ToDouble(0.375f)); //实际情况当中的两个精度不同的数据在做运算时就会出现问题。 //那为什么相同精度的情况下不会出现精度丢失问题,这个是优化过的。 //还是0.1为例,他的内存数据 byte[] b = System.BitConverter.GetBytes(0.1f); foreach (byte bb in b) { Console.Write("{0} ",bb); } Console.WriteLine("/r/n"); //可以看到第一个205是进位过的,其他的都是204,这个就像是3/2转为0.66667的意思一样。 b[0] -= 1; float f = BitConverter.ToSingle(b, 0); //f的实际值为0.099999994f,但是默认输出的时候把最后一个4给舍掉了 Console.WriteLine(f); //因此系统是无法识别0.099999994f到0.1之间的精度的。 //设断点一次察看下面的浮点数,就知道了,到了0.099999998f它就自动进位为0.1了。 float f1 = 0.099999994f; float f2 = 0.099999995f; float f3 = 0.099999996f; float f4 = 0.099999997f; float f5 = 0.099999998f; float f6 = 0.099999999f; Console.WriteLine("f1:{0}/r/nf2:{1}/r/nf3:{2}/r/nf4:{3}/r/nf5:{4}/r/nf6:{5}",f1,f2,f3,f4,f5,f6); //用BitConverter.GetBytes检查上面的6个浮点数,你会发现f1到f4是一样的,而f5、f6和0.1是一样的。 Console.Read(); } } }