1.浮点数陷阱
- 在Go语言中,整数数据类型无法用int、int32、int64 这些类型来表示小数,浮点数类型可以在程序中高效表示小数,但在计算的过程中可能丢失精度。
- 浮点数计算丢失经度举例:
package main import "fmt" func main() { var a float64 = 0.6 var b float64 = 0.3 fmt.Println(a + b) }
运行结果如下图:
2.定点数与浮点数
- 计算机底层是通过二进制的形式存储数据,很多小数表示成二进制后是近似且无限的,例如十进制的 0.1,如果转化为二进制结果是
0.0001100110011001100…
,它是一个无限循环的数字,而计算机底层是有限的地址空间,无法存储无限的数字。 - 最简单表示小数的办法就是使用
定点表示法
,可以用固定的大小来表示整数,剩余的部分表示小数。 - 例如用一个 16 位的无符号整数(uint16),可以用前面的 8 位存来储整数部分,后面的 8 位来存储小数部分,这样的话整数的部分最大表示的数字是 2 的 8 次方,即 256,最小表示的数字是 0,8 位表示的数字范围是 0~256,小数部分可以表示的范围是 1/256~1。
- 在有些情况下有可能整数的部分占位很长,小数的部分占位却很短,为了解决这中问题,可以使用浮点来存储数据。
- 用科学计数法来表示小数,例如使用 5 位的数字就可以表示的范围是 0.00~9.99×10^99 的数字,通过调整科学计数法后边 10 的指数可表示不同大小十进制的数,计算机浮点数设计的灵感和这个类似。
3.IEEE-754浮点数标准
- IEEE-754 规范是以 2 为底数的指数表示小数,和十进制树的科学计数法很类似。
- 例如 0.025 可以使用 1.6✖️2^-6 表示,其中 1.6 为系数,2 为底数,-6 为指数。
- 十进制数表示法举例:
十进制数字 | 科学计数法 | 指数表示法 | 系数 | 底数 | 指数 |
350000 | 3.5e+5 | 3.5✖️10^5 | 3.5 | 10 | 5 |
313.232 | 3.13232e+2 | 3.13232✖️10^2 | 3.1323210 | 10 | 2 |
-0.02424 | 2.424e-2 | 2.424✖️10^-2 | 2.424 | 10 | -2 |
- 位数越多,可以存储数字范围也越大,大部分硬件浮点数单元支持的32位的单精度浮点数与64位的双精度浮点数。
- 单精度与双精度浮点数格式:
精度 | 符号位 | 指数位 | 小数位 | 偏移量 |
单精度(32 Bits) | 1 Bit (31) | 8 Bits (高位30~低位23) | 23 Bits (高位22~低位00) | 127 |
双精度(64 Bits) | 1 Bit (63) | 11 Bits (高位62~低位52) | 52 Bits (高位51~低位00) | 1023 |
Tips:最开头的 1 位是符号位, 1 表示负数,0 表示正数,符号位后边的紧接着是指数位,单精度的指数位长度是 8,双精度指数位长度是 11,指数位存储了指数加上偏移量的值,例如当指数为-8 时,实际存储的值为 -8+127=119,指数位后边紧接着是小数位,单精度的小数位长度是 23,双精度的小数位长度是 52,小数位存储系数中小数位的准确值或最接近的值,是 0 到 1 之间的数。
- 0.085单精度浮点数表示
符号 | 指数部分(123) | 小数部分(.36) |
0 | 0111 1011 | 010 1110 0001 0100 0111 1011 |
Tips:指数部分 -4 + 127,即 123。
4.小数部分计算:
- 小数部分存储的可能是系数的近似值而不是准确值。
- 小数位的每一位代表的都是2的幂,并且指数依次减少1。
- 以 0.085 的浮点表示法中系数的小数部分(0.36) 为例,对应的二进制数为 010 1110 0001 0100 0111 1011:
位 | 对应的整数 | 转化为分数 | 转化为十进制小数 | 各位的总和 |
2 | 4 | 1/4 | 0.25 | 0.25 |
4 | 16 | 1/16 | 0.0625 | 0.3125 |
5 | 32 | 1/32 | 0.03125 | 0.34375 |
6 | 64 | 1/64 | 0.015625 | 0.359375 |
11 | 2048 | 1/2048 | 0.00048828125 | 0.35986328125 |
13 | 8192 | 1/8192 | 0.0001220703125 | 0.3599853515625 |
17 | 131072 | 1/131072 | 0.00000762939453 | 0.35999298095703 |
18 | 262144 | 1/262144 | 0.00000381469727 | 0.3599967956543 |
19 | 524288 | 1/524288 | 0.00000190734863 | 0.35999870300293 |
20 | 1048576 | 1/1048576 | 0.00000095367432 | 0.35999965667725 |
位 | 对应的整数 | 转化为分数 | 转化为十进制小数 | 各位的总和 |
22 | 4194304 | 1/4194304 | 0.00000023841858 | 0.35999989509583 |
23 | 8388608 | 1/8388608 | 0.00000011920929 | 0.36000001430512 |
- Go语言标准库的 math 包提供了许多有用的计算函数,float32 类型可以以字符串的形式打印出单精度浮点数的二进制值。
- 判断浮点数为整数的重要思路是指数能够弥补小数部分(即指数的值大于或等于小数的位数)。
- 在十进制数中,1.23×10^2 是整数,而 1.234×10^2 不是整数,因为指数 2 不能弥补 3 个小数位,以 2 为底数的浮点数的判断思路类似:
func IsInt(bits uint32, bias int) { exponent := int(bits >> 23) - bias - 23 coefficient := (bits & ((1 << 23) - 1)) | (1 << 23) intTest := (coefficient & (1 << uint32(-exponent) - 1)) fmt.Printf("\nExponent: %d Coefficient: %d IntTest: %d\n", exponent, coefficient, intTest) if exponent < -23 { fmt.Printf("NOT INTEGER\n") return } if exponent < 0 && intTest != 0 { fmt.Printf("NOT INTEGER\n") return } fmt.Printf("INTEGER\n") } //(1)第一步,计算指数。这里多减去了数字 23,后面会看到其用途,所以在第一个判断中判断条件为 exponent<-23,即比较指数位的值与 0 的大小。 //(2)第二步,通过位运算的方式计算出小数部分的值。
Tips: 要保证浮点数格式中实际存储的数为整数,一个必要条件就是浮点数格式中指数位的值大于127。指数位的值为127代表指数为0,如果指数位的值大于127,则代表指数大于0,反之则代表指数小于0。