故事开始于Unity Doc中关于Half的进度的解释
Medium precision: half
half
Medium precision floating point value; generally 16 bits (range of –60000 to +60000, with about 3 decimal digits of precision).Half precision is useful for short vectors, directions, object space positions, high dynamic range colors.
不知是人入中年,还是隔得太久,早已把一些基础的东西忘却了。看到这里关于精确的描述,感觉有点不可思议,16bit,最多也就2的16次方=65535中可能性,怎么能表示这么大的范围呢?后面经过Google的脑补,才明白过来,因为float的有损,才使得这个成为可能,而float精辟的地方也就在表示的每个数字的时候,误差相对于它的绝对值都是很小的,即大数误差较大,小数误差较小。这里备忘一下。
IEEE 754 标准指定了一个 binary16 要有如下的格式:
- Sign bit(符号位): 1 bit
- Exponent width(指数位宽): 5 bits
- Significand precision(尾数精度): 11 bits (有10位被显式存储)
按如下顺序排列:

除非指数位全是0,否则就会假定隐藏的起始位是1。因此只有10位尾数在内存中被显示出来,而总精度是11位。据IEEE 754的说法,虽然尾数只有11位,但是尾数精度是11位的(log10(211) ≈ 3.311 十进制数).
而对于指数位,25=32,考虑到正负,范围为-15~16,由于浮点型到二进制的转换方法为1.xxx*2n,所以最大值为216=65535,加上符号位,所以才有取值范围为-6000~6000的说法,因为1.xxx*2n,因为小数点前面一直是1,所以小数点后能有3位精度。
附:float的二进制表示
C语言和C#语言中,对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储,float数据占用32bit,double数据占用64bit,我们在声明一个变量float f= 2.25f的时候,是如何分配内存的呢?如果胡乱分配,那世界岂不是乱套了么,其实不论是float还是double在存储方式上都是遵从IEEE的规范的,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。
无论是单精度还是双精度在存储中都分为三个部分:
- 符号位(Sign) : 0代表正,1代表为负
- 指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储
- 尾数部分(Mantissa):尾数部分
其中float的存储方式如下图所示:
而双精度的存储方式为:
R32.24和R64.53的存储方式都是用科学计数法来存储数据的,比如8.25用十进制的科学计数法表示就为:8.25*
,而120.5可以表示为:1.205*
,这些小学的知识就不用多说了吧。而我们傻蛋计算机根本不认识十进制的数据,他只认识0,1,所以在计算机存储中,首先要将上面的数更改为二进制的科学计数法表示,8.25用二进制表示可表示为1000.01,我靠,不会连这都不会转换吧?那我估计要没辙了。120.5用二进制表示为:1110110.1用二进制的科学计数法表示1000.01可以表示为1.0001*
,1110110.1可以表示为1.1101101*
,任何一个数都的科学计数法表示都为1.xxx*
,尾数部分就可以表示为xxxx,第一位都是1嘛,干嘛还要表示呀?可以将小数点前面的1省略,所以23bit的尾数部分,可以表示的精度却变成了24bit,道理就是在这里,那24bit能精确到小数点后几位呢,我们知道9的二进制表示为1001,所以4bit能精确十进制中的1位小数点,24bit就能使float能精确到小数点后6位,而对于指数部分,因为指数可正可负,8位的指数位能表示的指数范围就应该为:-127-128了,所以指数部分的存储采用移位存储,存储的数据为元数据+127,下面就看看8.25和120.5在内存中真正的存储方式。
首先看下8.25,用二进制的科学计数法表示为:1.0001*
按照上面的存储方式,符号位为:0,表示为正,指数位为:3+127=130 ,位数部分为,故8.25的存储方式如下图所示:
而单精度浮点数120.5的存储方式如下图所示:
那么如果给出内存中一段数据,并且告诉你是单精度存储的话,你如何知道该数据的十进制数值呢?其实就是对上面的反推过程,比如给出如下内存数据:0100001011101101000000000000,首先我们现将该数据分段,0 10000 0101 110 1101 0000 0000 0000 0000,在内存中的存储就为下图所示:
根据我们的计算方式,可以计算出,这样一组数据表示为:1.1101101*
=120.5
而双精度浮点数的存储和单精度的存储大同小异,不同的是指数部分和尾数部分的位数。所以这里不再详细的介绍双精度的存储方式了,只将120.5的最后存储方式图给出,大家可以仔细想想为何是这样子的
参考:
https://docs.unity3d.com/Manual/SL-DataTypesAndPrecision.html
http://www.cnblogs.com/jillzhang/archive/2007/06/24/793901.html
https://zh.wikipedia.org/zh-cn/%E5%8D%8A%E7%B2%BE%E5%BA%A6%E6%B5%AE%E7%82%B9%E6%95%B0