首页 > Unity3D频道 > 【Unity3D研究院之游戏开发】 > Unity3D研究院之被坑了的浮点数的精度(一百零三)
2018
12-26

Unity3D研究院之被坑了的浮点数的精度(一百零三)

最近被浮点数坑了,所以一定要写篇文章记录下。先看看如下代码

通过上面代码,我们可以发现float和uint(为了取最大值,这里就用无符号整形)都是4字节,那么为什么float的取值范围要比int大呢?继续再看一段代码

如果有人不知道 取值范围 -3.402823E+38-3.402823E+38 其中的E+38的含义我这里解释一下

-3.402823E+38 等价于 -3.402823*10^38(10的38次方)取值范围-3.402823E+38 – 3.402823E+38 也就等价于
-340282300000000000000000000000000000000 至 340282300000000000000000000000000000000

看到这里大家应该心里有点明白了吧?float的最大值340282300000000000000000000000000000000完全秒杀int的最大值4294967295

在回到文章开头的代码,float占4字节int也占4字节 ,此刻你的内心是否有个疑问?为什么差这么多?

首先咱先看看int 4字节 (1个字节8位,4字节就是32位)也就表示它由32位保存 也就是它的最大值是 2^32(2的32次方)=4294967296(大家可以拿起计算器算一下是不是这个值)

同样的float 4字节 也表示它由32位保存那么凭什么它能存3.402823E+38这么大的数呢?机器不会骗人,原因就是它不是按int这么存数据的。比如一个float 数据

1024.1024 等价于 1.0241024 * 10^3  (3表示指数,1.0241024表示有效数字)float其实只是把符号、指数、有效数字3部分保存,真正在运算的时候是根据指数在移位操作。

float把 32位分成了3部分,1位(符号位)8位(指数位)23位(有效数字)那么 1+ 8 + 23 等于32吧,所以float的32位是这么来的。23位有效数字就表示float真正能存的精度,23位小数部分是反被储存的部分,所以它是有24位存储的,2^24(2的24次方)=16777216 ,请大家记住这个数值,下面我们在做个试验

读完代码,如果你的程序中有一个float的数值运算后的小数部分,如果超过16777216.xxx那么很抱歉它们的结果是一致的。

float我们介绍完了,double的原理和float差不多,double占8字节,1位符号位+11位指数+52位有效数字 = 64位。double的有效2^53(2的53次方)=9007199254740992,超过9007199254740992.xxx那么很抱歉它们的结果是一致的。

而且浮点数计算结果不同的CPU计算出来可能是不一致的,像帧同步那种游戏基本应该告别float和double了。

最后我们在聊一下定点数,应对解决精度问题,c#提供了定点数decimal关键字。可以看到它是由16个字节组成也就是64位,内存大小相当于4个float或者2个double 取值范围也很高。

1-12字节:它先用前12个字节来表示定点数数据,1字节8位,12个字节就是8*12 =96位,那么它的取值范围就是2^96(2的96次方)7.922816251426E+28

13-15字节:在用3个字节来表示指数

16字节:最后一个字节来表示符号

Unity3D研究院之被坑了的浮点数的精度(一百零三) - 雨松MOMO程序研究院 - 1

只要数据在10进制28-29位之间,小数点在中间任意位置均可。数据运算就不会像float和double一样被和谐了。所以说如果需要准确精度的小数计算就一定要使用定点数。

最后欢迎大家提出意见或者建议。

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

Unity3D研究院之被坑了的浮点数的精度(一百零三)》有 12 条评论

  1. 天气不错 说:

    Momo大人,“23位小数部分是反被储存的部分”。这个没看明白是啥意思,咋有24位存储的?

    • 闲的蛋疼 说:

      MOMO跳过了一些内容,详细建议参考IEEE-754。简单来说这23位存储的是规约化之后的二进制小数的小数点右边的23位,不足部分右补零(个人理解‘反’在这里)。而IEEE规定,规约化之后的二进制小数,小数点左边必定是1,因此这23位可以表达的最大数值是1.11111111111111111111111(二进制),一共是24位。

      顺便解释楼下的问题,1.11111111111111111111111(二进制) 的十进制近似值就是 3.402823。16777216 一般作为精度边界被拎出来,因为它按照float标准规范化存储之后是 1.00000000000000000000000(二进制) * 2^24,,而乘2相当于小数点右移,所以很明显,右移24次以后的十进制结果在整数层面都不连续了,自然就没有精度可言了。

  2. hao 说:

    16777216 和 3.402823 啥关系?没看懂

  3. 曹伟 说:

    1字节4位,12个字节就是8*12 =96位.这句话是不是有些问题,应该是4*12=48位

  4. pro1 说:

    so嘎,学习了。

  5. wafcc 说:

    最后我们在聊一下定点数……可以看到它是由16个字节组成也就是128位,
    1-12字节:它先用……1字节8位…
    嘻嘻。。

  6. LonelyWind 说:

    用float计算long类型的时间戳的时候被坑过,总是丢精度float的后几位精度,没找到原因,最后无奈转double,估计就是这个原因

留下一个回复

你的email不会被公开。