zoukankan      html  css  js  c++  java
  • 从44.556677想到的

    如下代码:

    float a = 44.556677f;
    printf("%f
    ", a);
    

    得到的输出是什么?并不是44.556677,而是44.556679。浮点数这么坑吗?为什么不一样呢?

    浮点数的二进制表示,一种是手算,另一种是直接格式转换然后输出。

    手算的,我看的这里的 https://blog.csdn.net/youmeichifan/article/details/80775360

    发现44.556677算出来是42323a09,44.556679是42323a0a,两个不一样。计算过程如下:

    强转格式输出的,我看的这里的:http://www.cnblogs.com/tlz888/p/9211600.html

    44.556677和44.556679的输出都是42323a0a

    对应的代码:

    //show_float.c 
    #include <stdio.h>
    
    unsigned int float2hexRepr(float* a){
        unsigned int c;
        c = ((unsigned int*)a)[0];
        return c;
    }
    
    int main(){
        float a = 44.556677f;
        printf("print out 44.556677 but get: %f
    ", a);
    
        printf("44.556677's hex: %x
    ", float2hexRepr(&a));
    
        float b = 44.556679f;
        printf("44.556679's hex: %x
    ", float2hexRepr(&b));
       
        return 0;
    }
    

    编译运行结果:

    print out 44.556677 but get: 44.556679
    44.556677's hex: 42323a0a
    44.556679's hex: 42323a0a
    

    也就是:44.556677f这个float32类型的数据,在内存中的16进制,应该是0x42320a0a而不是0x42320a09。为什么呢?

    因为10进制的0.556677转换为2进制的小数时,多次乘以2仍然得不到1.0,但是IEEE 754标准规定的位数又有限,这就产生了真实值和存储值的误差。为了尽量减小误差就需要考虑舍入(rounding)。

    具体怎么rounding呢?实际上IEEE 754规定了4种rounding方式,但只有一个默认方式:rounding to neareast,也就是舍入到偶数,那么转换得到的尾数的2进制表示的最后4位,也就是1001,它的最后一位是奇数,要变成1010。(但是为什么不是1000呢)


    上图来自《CSAPP》。然而感觉还是没有说清楚。

    又看了wikipedia[https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules],总算明白了。IEEE 754其实规定了5个rounding rule,默认是:Round to nearest, ties to even – rounds to the nearest value; if the number falls midway, it is rounded to the nearest value with an even least significant digit; this is the default for binary floating point and the recommended default for decimal.

    完整计算:

    对于44.556677,整数部分44的2进制表示是101100;小数部分的2进制表示,先假设为F,那么整个44.556677的2进制科学记数法表示为1.01100F * 2^5。

    然后考虑这个F的计算:尾数部分总共23位,其中作为整数部分的44的产生了.01100这5位,作为尾数的前5位;

    剩余18位则由0.556677转换为2进制小数而得到:每次乘以2,然后记录下整数部分,拿小数部分再进入下一次计算,而因为0.556677本身的原因,多次乘以2还是无法得到1.0,也就是“乘2”的次数超过了18次,但是还没有停止的意思。。

    我们来看它产生的19位都是什么:1000111010000010011 (为什么要19位,因为第19位不为0,我们当前需要18位,这就产生了截断:去掉了第19位和更后面的位,影响了精度。为了让精度下降的更少些,就需要舍入,也就是rounding)。

    • 我们看到第19位是1,那么第18位从1变成0同时第17位从0变成1,这样精度的损失最多是第20位级别的;
    • 而如果第18位变成0,则精度损失是第18位级别的;
    • 而如果第18位不变,则精度损失是第19位级别的

    显然,只有第17、18位从01变成10,才能由最小的精度损失。这就是rounding to nearest的第一种意思。(另一种意思是:如果被考虑的数字的二进制表示存在精度损失,并且恰好由两个跟它的值最接近的2进制表示,那么取偶数的那个表示)。

    参考:

    http://www.styb.cn/cms/ieee_754.php

    这个网址计算规则是有问题的,不是遵循IEEE 754标准的。 比如44.556677,IEEE 754标准下应该是0x42323a0a,而不是那个网址算出来的0x42323a09。原因是尾数存在精度损失,需要舍入(rounding),但是这个网址里没做rounding。rounding默认规则是rounding to neareast,也就是尾数需要改为“让精度损失最小的表示”。

    rounding规则在这里:

    https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules

    https://www.h-schmidt.net/FloatConverter/IEEE754.html 这个网址的转换才是靠谱的。

  • 相关阅读:
    11、旋转图像
    10、有效的数独
    9、两数之和
    8、移动零
    6、两个数组的交集 II
    7、加一
    5、只出现一次的数字
    3、旋转数组
    spring快速复习
    mybatis XML SQL基本配置
  • 原文地址:https://www.cnblogs.com/zjutzz/p/10140559.html
Copyright © 2011-2022 走看看