zoukankan      html  css  js  c++  java
  • 浮点数精度和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();
    
            }
    
        }
    
    }
    
    
  • 相关阅读:
    Java基础(十四)——API(Calendar类、System类、StringBuilder类、包装类)
    异常
    Java基础(十三)——权限修饰符和内部类
    知识点总结
    Java基础(十二)— —多态
    Java基础(十一)— —继承、抽象类和接口
    java基础(十)——继承
    小程序外部向组件内部传递externalClasses -- 传入样式wxss
    小程序组件交互 -- 传入js
    promise封装小程序的请求类(request,清爽易懂)
  • 原文地址:https://www.cnblogs.com/cl1024cl/p/6204925.html
Copyright © 2011-2022 走看看