符号位不用说,0 表示正数,1 表示负数。着重看指数部分和尾数部分。(基数前文说了,固定是 2,因此不存)
尾数部分前面提到过,浮点数名称的由来在于小数点是浮动的。但具体存储时,需要固定一种形式,这叫做尾数的标准化。IEEE754 规定,在二进制数中,通过移位,将小数点前面的值固定为 1。IEEE754 称这种形式的浮点数为规范化浮点数(normal number)。
比如十进制数 0.15625,转为二进制是 0.00101。为了让第 1 位为 1,执行逻辑右移 3 位,尾数部分成为 1.01,因为右移了 3 位,所以指数部分是 -3。因为规定第 1 位永远为 1,因此可以省略不存,这样尾数部分多了 1 位,只需存 0100(要记住,这是的数字是小数点后的数字,因此实际是 0.01,转为十进制是 0.25 — 没算未存的小数点前面的 1)。
因此对于规范化浮点数,尾数其实比实际的多 1 位,也就是说单精度的是 24 位,双精度是 53 位。为了作区分,IEEE754 称这种尾数为 significand。
有规范化浮点数,自然会有非规范化浮点数(denormal number),这会在后文讲解。
请牢记,尾数决定了精度,对于单精度浮点数,因为只有 23 位,而1<<23 对应十进制是 8388608,因此不能完整表示全部的 7 个十进制位,所以说,单精度浮点数有效小数位最多 7 位;双精度的有效小数位是 15 位;切记切记,有精度问题!!
指数部分因为指数有正、有负,为了避免使用符号位,同时方便比较、排序,指数部分采用了 The Biased exponent(有偏指数)。IEEE754 规定,2ᵉ⁻¹-1 的值是 0,其中 e 表示指数部分的位数,小于这个值表示负数,大于这个值表示正数。因此,对于单精度浮点数而言, 2⁸⁻¹-1 = 127 是 0;双精度浮点数,2¹¹⁻¹-1 = 1023 是 0。
没看懂?举个栗子。
还是用十进制 0.15625 举例。上文知道,因为右移了 3 位,所以指数是 -3。根据 IEEE754 的定义,单精度浮点数情况下,-3 的实际值是 127 - 3 = 124。明白了吗?127 表示 0,124 就表示 -3 了。而十进制的 124 转为二进制就是 1111100。
如果你还不理解,想想这个问题。
小结如果让你用扑克牌(A ~ K,也就是 1 ~ 13)来表示支持负数的。怎么办?我们会选择一个中间的数,比如 7 当做 0,因此 10 就是 3,4 就是 -3。现在理解了吧!
结合尾数和指数的规定,IEEE754 单精度浮点数,十进制 0.15625 对应的二进制内存表示是:0 01111100 01000000000000000000000。
6、程序确认下 IEEE754 的如上规定读到这里,希望你能坚持下去。为了进一步加深理解,我画一张图和一个确认程序。
一张图这张图是单精度浮点数 0.15625 的内存存储表示。根据三部分的二进制表示,可以反推出计算该数的十进制表示。作为练习,十进制的 2.75,用上图表示的话,各个位置分别都是什么值呢?
程序确认单精度浮点数的内存表示使用 Go 语言编写一个程序,能够得到一个单精度浮点数的二进制内存表示。比如提供单精度浮点数 0.15625,该程序能够输出:0-01111100-01000000000000000000000。
packagemain
import(
"fmt"
"math"
)
funcmain(){
varffloat32=0.15625
outputFEEE754(f)
}
funcoutputFEEE754(ffloat32){
//将该浮点数内存布局当做uint32看待(因为都占用4字节)
//这里实际上是做强制转换,内部实现是:return *(*uint32)(unsafe.Pointer(&f))
buf:=math.Float32bits(f)
//加上两处-,结果一共34byte
varresult[34]byte
//从低字节开始
fori:=33;i>=0;i--{
ifi==1||i==10{
result[i]='-'
}else{
ifbuf%2==1{
result[i]='1'
}else{
result[i]='0'
}
buf/=2
}
}
fmt.Printf("%s\n",result)
}
//output:0-01111100-01000000000000000000000
你可以使用上述程序,验证下 2.75,看看你做对没有!提供了一个在线可运行版本:https://play.studygolang.com/p/pg0QNQtBHYx。
其实上面推荐的那个工具就能够得到十进制浮点数的二进制内存表示,地址:https://baseconvert.com/ieee-754-floating-point。
另外,在 Java 语言中也有类似的方法:Float.floatToIntBits(),你可以使用 Java 实现上面类似的功能。
6、再看 0.1 0.2 = 0.30000000000000004有了上面的知识,我们回过头看看这个经典的问题。(讨论单精度的情况,因此实际是 0.1 0.2 = 0.300000004)
出错的原因出现这种情况的根本原因是,有些十进制小数无法转换为二进制数。如下图:
在小数点后 4 位时,连续的二进制数,对应的十进制数却是不连续的,因此只能增加位数来尽可能近似的表示。
0.1 和 0.2 是如何表示的?根据前面的讲解,十进制 0.1 转为二进制小数,得到的是 0.0001100… (重复1100)这样一个循环二进制小数,使用 IEEE754 表示如下图: