float 浮点型底层存储原理
“0.1 + 0.2 到底等于几?”
>>> 0.1 + 0.2
0.30000000000000004
>>> 39.29 + 0.3
39.589999999999996
>>> 0.2 + 0.3
0.5
常用的编程语言,其中的小数都是默认的 float 浮点型,而 float 浮点型的内部存储机制会导致其有时候不精准。
十进制转二进制
39.29 转换为二进制过程:
- 整数部分直接转成二进制。39 直接转换为 100111
- 小数部分转换:让小数一直乘以 2,小于 1 则用结果继续乘,大于 1 则结果减 1 继续乘,等于1 则结束。
0.29 * 2 = 0.58 # 小于1,则继续乘
0.58 * 2 = 1.16 # 大于1,则减1继续乘
0.16 * 2 = 0.32 # 小于1,则继续乘
0.32 * 2 = 0.64 # 小于1,则继续乘
0.64 * 2 = 1.28 # 大于1,则减1继续乘
0.28 * 2 = 0.56 # 小于1,则继续乘
0.56 * 2 = 1.12 # 大于1,则减1继续乘
0.12 * 2 = 0.24 # 小于1,则继续乘
0.24 * 2 = 0.48 # 小于1,则继续乘
0.48 * 2 = 0.96 # 小于1,则继续乘
0.96 * 2 = 1.92 # 大于1,则减1继续乘
0.92 * 2 = 1.84 # 大于1,则减1继续乘
0.84 * 2 = 1.68 # 大于1,则减1继续乘
0.68 * 2 = 1.36 # 大于1,则减1继续乘
0.36 * 2 = 0.72 # 小于1,则继续乘
0.72 * 2 = 1.44 # 大于1,则减1继续乘
0.44 * 2 = 0.88 # 小于1,则继续乘
0.88 * 2 = 1.76 # 大于1,则减1继续乘
0.76 * 2 = 1.52 # 大于1,则减1继续乘
0.52 * 2 = 1.04 # 大于1,则减1继续乘
0.04 * 2 = 0.08 # 小于1,则继续乘
0.08 * 2 = 0.16 # 小于1,则继续乘
0.16 * 2 = 0.32 # 小于1,则继续乘(与第三行相同,这样会一直循环执行下去)
。。。
将相乘之后的结果的整数部分拼接起来,所以0.29 的二进制表示:01001010001111010111000...
科学计数法
39.29科学计数法表示:
- 10111.01001010001111010111000…
- 1.011101001010001111010111000…* 2^5
1.011101001010001111010111000…* 2^5
- sign,用1位来表示浮点数正负,0表示正数;1表示负数。
- exponent,用8位表示二进制的科学计数法中的指数部分的值,8位表示的数据范围可以是0~255,但是由于指数部分可能为负数,所以这个指数的范围为
-128~127
。再计算时让【指数+127】得到值转换为二进制存储在此即可。注:0.5375的二进制小数位0.10001
,科学计数法为 1.0001 * 2^-1。 - fraction,用32为来表示二进制小数的科学计数法中的小数部分(不管整数部分,因为科学计数法中的整数总是为 1)。
- sign = 0,浮点数为正数。
- exponent = 01000010,指数 5 + 127 = 132,将132转换为二进制。
- fraction = 00111010010100011110101,最多保留32为二进制小数位,其他省略(不精确)。
- 所以,最终39.25在存储时的二进制为:
0 01000010 00111010010100011110101
当我们开发的程序里用到的小数需要是精准的,一定不能是 float,需要用 Decimal 类库,各个语言都有相应的库。