go语言基础之浮点数
这里写目录标题
小数的浮点表示法
±x.yz*e±pq
IEEE754标准
32位单精度浮点数在内存中的存储方式
存储一个32位浮点数, 比如20.5, 在内存或硬盘中要占用32个二进制位,这32个二进制位被划分为3部分
这32个二进制位的内存编号从高到低 (从31到0), 共包含如下几个部分:
符号位: sign ,即图中蓝色的方块
符号位: 占据最高位(第31位)这一位, 用于表示这个浮点数是正数还是负数, 为0表示正数, 为1表示负数
偏移后的指数位: biased exponent, 即图中绿色的方块
这部分用于表示以2位底的指数, IEEE754规定, 指数位用于表示[-127, 128]范围内的指数。不过为了表示起来更方便, 浮点型的指数位都有一个固定的偏移量(bias), 用于使 指数 + 这个偏移量 = 一个非负整数.。这样指数位部分就不用为如何表示负数而担心了。 在32位单精度类型中, 这个偏移量是127. 在64位双精度类型中, 偏移量是1023。所以, 这里的偏移量是127。即, 如果你运算后得到的指数是 -127, 那么偏移后, 在指数位中就需要表示为: -127 + 127(偏移量) = 0
如果你运算后得到的指数是 -10, 那么偏移后, 在指数位中需要表示为: -10 + 127(偏移量) = 117,有了偏移量, 指数位中始终都是一个非负整数.
尾数位:fraction , 即图中红色的方块
尾数位: 占据剩余的22位到0位这23位. 用于存储尾数.
在以二进制格式存储十进制浮点数时, 首先需要把十进制浮点数表示为二进制格式, 还拿十进制数20.5举例:
十进制浮点数20.5 = 二进制10100.1
然后, 需要把这个二进制数转换为以2为底的指数形式:
二进制10100.1 = 1.01001 * 2^4
注意转换时, 对于乘号左边, 加粗的那个二进制数1.01001, 需要把小数点放在左起第一位和第二位之间. 且第一位需要是个非0数. 这样表示好之后, 其中的1.01001就是尾数.
我们再来看看规范化之后的这个数: 1.01001 * 2^4
其中1.01001是尾数, 而4就是偏移前的指数(unbiased exponent), 上文讲过, 32位单精度浮点数的偏移量(bias)为127, 所以这里加上偏移量之后, 得到的偏移后指数(biased exponent)就是 4 + 127 = 131, 131转换为二进制就是1000 0011
现在还需要对尾数做一些特殊处理
隐藏高位1.
尾数部分的最高位始终为1. 比如这里的 **1.**01001, 这是因为前面说过, 规范化之后, 尾数中的小数点会位于左起第一位和第二位之间. 且第一位是个非0数. 而二进制中, 每一位可取值只有0或1, 如果第一位非0, 则第一位只能为1. 所以在存储尾数时, 可以省略前面的 1和小数点. 只记录尾数中小数点之后的部分, 这样就节约了一位内存. 所以这里只需记录剩余的尾数部分: 01001
所以, 以后再提到尾数, 如无特殊说明, 指的其实是隐藏了整数部分1. 之后, 剩下的小数部分
低位补0
有时候尾数会不够填满尾数位(即图中的红色格子). 比如这里的, 尾数01001不够23位。此时, 需要在低位补零, 补齐23位.之所以在低位补0, 是因为尾数中存储的本质上是二进制的小数部分, 所以如果想要在不影响原数值的情况下, 填满23位, 就需要在低位补零。比如, 要把二进制数1.01在不改变原值的情况下填满八位内存, 写出来就应该是: 1.010 0000, 即需要在低位补0。同理, 本例中因为尾数部分存储的实际上是省略了整数部分 1. 之后, 剩余的小数部分, 所以这里补0时也需要在低位补0:
原尾数是: 01001(不到23位)
补零之后是: 0100 1000 0000 0000 000 (补至23位)
实验
所以根据上述浮点数存储原理,我们可以自己实现浮点数取整的方法。
我们以float32型的数据10.5
为例:
首先用math.Float32bits()
方法获取10.5的二进制表示为:01000001001010000000000000000000
其中符号位为0
;偏移后的指数位为10000010
;尾数位为01010000000000000000000
想要获取浮点数的整数部分,首先要获取指数位,我们以0到31来表示二进制数下标,那么指数位为第23到30位,可以采用先左移1位,在右移24位的方式获取n := uint(b << 1 >> 24)
如果n<127
,说明指数位为负数,整数部分为0。
然后再看尾数位,根据上述的描述,尾数位隐藏了高位的1,所以在获取完前n-127
位尾数位后,在补充上高位的1
,最后不要忘记符号位。
func ParseInt(f float32) int {
b := math.Float32bits(f)
n := uint(b << 1 >> 24)
if n < 127 {
fmt.Println("整数部分为0")
return 0
} else {
x := b<<9>>(32+127-n) | (1 << (n - 127))
if b&(1<<31) != 0 {
return int(x) * -1
}
return int(x)
}
}
同理,我们也可以自己判断浮点数是否为整数。
需要注意的是,go语言中float32的最小可以表示的浮点数数为2^-23≈0.0000001192,小于这个值就无法表示了,所以当输入为1.00000001
时,返回值为true
;所以当输入为1.0000001
时,返回值为false
。
func IsInt(f float32) bool {
b := math.Float32bits(f)
n := uint(b << 1 >> 24)
if n == 0 {
return true
}
if n < 127 {
return false
} else {
if uint(b<<(9+n-127)) > 0 {
return false
} else {
return true
}
}
}