浮点数精度和IEEE754标准

浮点数精度丢失的"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();

        }

    }

}

阅读更多
文章标签: F# Microsoft SQL
上一篇一个验证码识别的代码
下一篇测试sp_executesql和exec的性能差别
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭