2021年4月

有趣的浮点数

  • 浮点数编码

    从高到低分别是:符号位、指数位、有效数位
    float:4字节,符号1位、指数8位、有效数23位
    double:8字节,符号1位、指数11位、有效数52位
  • 二进制表示

    以4字节的float为例,整数部分采用除2取余法,小数部分采用乘2取整法,然后严格按照以下步骤编码:

    1. 正数或0符号位0,负数符号位1
    2. 移动小数点到第一个有效数字的右边,假定移动N位,左移N为正,右移N为负
    3. 若N为正,则指数位的最高位为1,剩余7位则用N-1的二进制来填充
    4. 若N为负或0,则指数位的最高位为0,剩余7位则用N的二进制取反来填充
    5. N的取之范围[-127, 127],当N为128时,则为无穷数
    6. 小数点右边的其余数字,依次填充有效数位即可,多余数字截断
    7. 为了表示更小的小数,当右移位数大于等于127位时,只移127位,此时需要借用有效数位的最高位来区分小数点前是0还是1,因此此时的有效数位少一位
  • 取值范围

    从以上分析可得出float的取值范围:最大数为1.999...pow(2, 127),最小数为pow(2, -127-22)
    double的取值范围:最大数为1.999...
    pow(2, 1023),最小数为pow(2, -1023-51)

  • 精度

    以整数为例,对于float来说,能精确表示的最大整数为pow(2, 24)
    对于不在精度范围内的数,编译器和cpu采用“五舍六入”法

  • 游戏采用浮点数的误区

    游戏内经常会判断两个float是否相等,采用差值的绝对值小于一个很小的数delta,这种是不严谨的,由于float的最小粒度为pow(2, -23),精确到小数点后7位,是在整数部分只占最多1bit的情况下,假设整数部分为10000,此时若delta为0.0001,会超出精度,应改成delta为0.001,可采用以下函数来判断:

    bool is_float_eq(float x, float y) {
        return x == y || abs(*(unsigned int*)&x-*(unsigned int*)&y) == 1;
    }