题目链接:Dollars UVA - 147
历程
在洛谷上练习DP时,遇到两道题:UVA674 Coin Change和UVA147 Dollars。两道题都是完全背包统计方案数类型,且基本一模一样。双倍经验
其中:第一题Coin Charge
的难度评级为普及/提高-,而Dollars
的难度评级为提高+/省选-。看了一下两道题,写完基本一样的代码后直接提交。Coin Change
轻松AC,但Dollars
一直WA。
检查 半个小时 后,发现问题:
int(x * 100)
表达式,当(x=1.15)时,(x imes100)为(115.0),但转为int
后却为(114)。
于是便引出今天的问题:计算机的精度。
正题
一般计算机可以有十几位甚至几十位(二进制)有效数字,计算精度可由千分之几到百万分之几,是任何计算工具所望尘莫及的。(来自百度百科)
计算机存储小数的流程
- 第一步:转换成二进制
- 第二步:用二进制科学计算法表示
- 第三步:表示成 IEEE 754 形式
在上面的第一步和第三步都有可能 丢失精度。(^{[1]})
无限循环小数的存储
考虑在10进制下将(frac17)写成小数。
用 1 除以 7,得到的商就是小数部分,剩下的余数我们继续除以 7,一直除到什么时候结束呢? 有两种情况:
- 如果余数为 0。结束。
- 如果不为0,继续除下去((3,2,6cdots))
- 当除到某一步时,余数等于 1。此时再继续除下去,就又是 (frac17) 了。绕了几圈又回到(1div7)了。
注意3:我们判断他循环,并不是从直观看感觉它重复了,而是因为 在计算过程中,它又回到了开头。
因此:循环小数不能精确表示,放到计算机中会丢失精度; 那么有限小数可以精确表示吧,比如 0.1。
无限循环小数的存储
例如(piapprox3.14159cdots),(eapprox2.71828cdots),……,这些数显然无法精确存储。
有限小数的存储(^{[2]})
我们按照乘以 2 取整数位的方法,把 0.1 表示为二进制
(1) 0.1 x 2 = 0.2 取整数位 0 得 0.0
(2) 0.2 x 2 = 0.4 取整数位 0 得 0.00
(3) 0.4 x 2 = 0.8 取整数位 0 得 0.000
(4) 0.8 x 2 = 1.6 取整数位 1 得 0.0001
(5) 0.6 x 2 = 0.2 取整数位 1 得 0.00011
(6) 0.2 x 2 = 0.4 取整数位 0 得 0.000110
(7) 0.4 x 2 = 0.8 取整数位 0 得 0.0001100
(8) 0.8 x 2 = 1.6 取整数位 1 得 0.00011001
(9) 0.6 x 2 = 1.2 取整数位 1 得 0.000110011
(n) ...
我们得到一个无限循环的二进制小数 0.000110011…
因此,0.1不能在计算机内精确存储。
0.1 到 0.9 的 9 个小数中,只有 0.5 可以用二进制精确的表示。
如果把 0.0 再算上,那么就有两个数可以精确表示,一个奇数 0.5,一个偶数 0.0。
回到历程中的问题:
double
下的(115.0),可能输出时显示(115.0),但由于无法精确存储,可能存入的数字在十进制下为(114.99cdots9),转为int
时取整导致错误
有什么用?
因为本人目前才疏学浅,不知道了解这个有什么用……但是突然发现了感觉很有趣,于是就写了篇博客记录一下。
在后续关于可能涉及精度的问题,可以使用四舍五入:
int(a)
↓↓↓↓↓↓↓↓↓↓
int(a+0.5)
另外:(^{[3]})
参考: [1][2]代码之谜(五)- 浮点数(谁偷了你的精度?)
一位机房大佬:忘怀星 的个人中心