浮点数精度问题

1,031 阅读2分钟

前两天做题,用了python,但是没考虑精度问题,一直有问题。直到我打印出中间变量,我才傻了眼。

这不是我第一次遇到精度的问题了。上一次蓝桥杯省赛用python,也是被精度问题坑死。 既然再次遇到,那么这次就要好好研究明白。

为什么会有精度损失

我们知道,计算机内十进制数会保存为二进制,小数点前的数保存起来是没问题的,21,222^1, 2^2等用来表示整数。

但是小数点后的数字,就开始出问题了。计算机使用21,222^{-1}, 2^{-2}等来保存小数。 0.5,0.25,0.125,0.0625...0.5, 0.25, 0.125, 0.0625...相加起来,有没有发现不太对:这些有限的数字加起来,很难组成你想要的小数。

比如0.10.1的二进制就是0.0001100110˙0˙1˙1˙0.000110011\dot{0}\dot{0}\dot{1}\dot{1}

要保存一个数,计算机存储的位数是有限的,所以无法存储这个无限循环小数,那计算机在丢弃位数的过程中,就会产生精度损失。

我在Java、Python3和JS里尝试计算0.1+0.20.1+0.2,并打印结果。结果是这样的形式: 0.30000000000000004

python如何解决这个问题

物理层无法解决的问题,我们只能通过软件进行解决。

1、round函数进行四舍五入

我们可以看到,被丢弃的都是位于后面的位数。而越往后表示的数就越小,那我们就可以使用四舍五入解决掉这些似乎无关紧要的数字。

round(1.20003, 2)

输出结果就是1.21.2。保留了2位小数。

2、使用Demical类

这个暂时不做研究

3、使用int()强制转换

强制转换函数可以直接将小数部分去掉。

Java如何解决这个问题

1、Math.round()函数进行四舍五入

和python不同,这个函数只接受一个参数,要么是float要么是double。 也就是说,不保留小数,但是会考虑四舍五入。

如果你想实现小数后保留几位的四舍五入,也是可以的。

double a = 1.234567;
// 要想保留两位小数,那就先乘以100,处理后再强制转换再除以100.
double b = (double)Math.round(a*100) / 100;
System.out.println(b); // 输出 1.23

2、使用BigDecimal类

这个类可以实现高精度计算。建议使用字符串进行初始化。

// 浮点数构造方式有两种
new BigDecimal(12.3); // 不推荐,无法控制初始化的精度!
new BigDecimal("12.3"); // 推荐使用字符串的方式
  • 加法:BigDecimal result1 = num1.add(num2);
  • 减法subtract()函数
  • 乘法multiply()函数
  • 除法divide()函数
  • 绝对值abs()函数

3、(int)强制转换

和python一样,直接砍掉小数部分。