double精度丢失问题

double精度丢失问题

从二进制角度考虑问题就会很简单了:把「0.1」转成二进制然后还原成十进制,就能看出问题。

  • 这是二进制与十进制之间的碰撞
  • 这是机器与人们之间的差异
  • 这是思想与结果的碰撞
  • 理解之后一切都会变得合理……毕竟称之为浮点数

一、把 0.1 转成二进制表示

我们知道 DEC(1) 就是 BIN(1),但是 DEC(0.1) 怎么转换成二进制?

是的!用除法:

0.1 = 1 ÷ 10

很简单,二进制就是要算

1 ÷ 1010

我们回到小学的课堂,来列竖式吧:(算了还是用计算器来的更快些……)

       0.000110011...
      /------------------
1010 / 1 0000
    /    1010
       ------
          1100
          1010
          ----
            10000
             1010
            -----
              1100
              1010
              ----
                10

这时候你一定会发现,除不尽,除出了一个无限循环小数:二进制的 0.0001100110011…

二、机器语言的舍入

我们得把 0.0001100110011… 放进一个 double「双精度浮点数」里面,机器容量有限,不可能存储无限循环小数,只能存储部分

双精度浮点数能表示多少精度呢?查看文档会发现:

  • 半精度(16bit) : 11 位有效数字
  • 单精度(32bit) : 24 位有效数字
  • 双精度(64bit) : 53 位有效数字
  • 四精度(128bit):113 位有效数字

好吧,双精度是 53 位有效数字

0.00011001100110011001100110011001100110011001100110011001  10011...

方便起见,我在第 53 个有效数字后面加了空格。有点难数,使用编程打印的

十进制数我们可以四舍五入,二进制怎么办?

  • 精神是一样的:待处理部分有没有达到前一位的一半,达到就进位,没达到就舍去。(暂且当作 0 舍 1 入。)

那么我们的 0.1 在 double 中就是

0.00011001100110011001100110011001100110011001100110011001 10011...
0.00011001100110011001100110011001100110011001100110011010

而 1.1 就是

1.0001100110011001100110011001100110011001100110011001 10011...
1.0001100110011001100110011001100110011001100110011010

三、机器语言的加法

这个很简单,1.1 + 0.1 就是 1.2, 但是让我们看看机器如何相加↓:

  1.0001100110011001100110011001100110011001100110011010
+ 0.00011001100110011001100110011001100110011001100110011010
------------------------------------------------------------
  1.00110011001100110011001100110011001100110011001100111010

因为结果仍然是 double,需要再做一次保留 53 位有效数字和舍入:

1.0011001100110011001100110011001100110011001100110011 1010
1.0011001100110011001100110011001100110011001100110100

四、二进制转十进制的偏差

机器计算完成,机器需要转十进制给我们看

我们把最终结果转换回来:

1.0011001100110011001100110011001100110011001100110100

得到十进制的:

1.20000000000000018

一般的输出函数,在输出浮点数时,都会限制显示的有效数字,即会再做一次四舍五入。

**PS:**至于各个语言之间的差异,答案是可能会有,比如可能因为选择的舍入规则的不同可能导致的结果的不同;甚至有可能某个语言里的浮点数压根不是 IEEE 754 的浮点数,而是以字符串方式保存的,所以可能没有误差。

Double的输出

//使用BigDecimal
@Test
public void DoubleTest() {
    double testDoubleVal = 1.23456789;
    BigDecimal bigDecimal = new BigDecimal(testDoubleVal);
    System.out.println(bigDecimal.setScale(3, BigDecimal.ROUND_HALF_UP));
    System.out.println(bigDecimal.setScale(4, BigDecimal.ROUND_HALF_UP));//不同的舍入规则
    System.out.println(bigDecimal.setScale(4, BigDecimal.ROUND_DOWN));  //不同的舍入规则
}
/*
运算结果:
	1.235
    1.2346
    1.2345
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值