0.1+0.2 == 0.3 和!=0.3

820 阅读1分钟

前言

本文主要包含

  • 浮点数存储结构
  • 浮点数运算(对阶,舍入)

一、 从一段golang代码说起

func main() {
    var a float32 = 0.1
    var b float32 = 0.2
    var c float32 = 0.3
    fmt.Println( a + b = c)
    // true
    var aa float64 = 0.1
    var bb float64 = 0.2
    var cc float64 = 0.3
    fmt.Println( aa + bb = cc)
    // false
}

是否会疑问,32位0.1+0.2等于0.3,而64位的不等于

二、浮点数存储理论知识

  1. ^s表示符号位,当s=0,V为正数;当s=1,V为负数。
  2. M表示有效数字,大于等于1,小于2。
  3. 2^E表示指数位

三、验证float32 0.1+0.2 == 0.3

3.1 将0.1 0.2 0.3小数转为二进制

方法为乘2取整,示例:

2 * 0.1 = 0.2  整数位0
2 * 0.2 = 0.4  整数位0
2 * 0.4 = 0.8  整数位0
2 * 0.8 = 1.6  整数位1
2 * 0.6 = 1.2  整数位1
2 * 0.2 = 0.4  整数位0
2 * 0.4 = 0.8  整数位0
2 * 0.8 = 1.6  整数位1
⋯⋯

如上 0.1的二进制为
0.000110011001100110011001101
转为指数形式

  • 0.1
    1.10011001100110011001101 × 2⁻⁴
  • 0.2
    1.10011001100110011001101 × 2⁻³
  • 0.3
    1.00110011001100110011010 × 2⁻²

3.2 计算0.1+0.2

  1. 对阶
    由于指数位数不同,需要先对阶,对阶法则为:
    小阶像大阶看齐,小阶尾数右移
  • 0.1对阶后
    0.110011001100110011001101 × 2⁻³
  • 0.1对阶后,尾数已经超过23位,需要舍入,结果为
    0.11001100110011001100111 × 2⁻³
  1. 加法运算(此处加法运算可以使用Windows自带的计算器)
0.11001100110011001100111 × 2⁻³ +
1.10011001100110011001101 × 2⁻³

结果为
10.01100110011001100110100 × 2⁻³
右移一位
1.001100110011001100110100 × 2⁻²
进行舍入,相加结果为
1.00110011001100110011010 × 2⁻²
此时再和前面0.2对比,是否相等

3.3 结论

32位0.1+0.2结果和0.2存储是一样的,故能相等

四、验证float64 0.1+0.2 == 0.3

留待读者自己做