Python的round函数和JS中的Math.round的不同之处
文章目录
如无特殊说明,本文中的
Python
均指代Python 3
。Citation:
现在(2019年)最火的语言是 Python,发展最快、潜力最大的语言是 JavaScript。这俩语言有很多类似的地方,市面上也有一些类似于 gh: PythonJS/PythonJS
和 gh: PiotrDabkowski/Js2Py
等的 JS 代码和 Python 代码互转的库。
但是呢,仔细揪一揪这两个语言的不同之处,其实还是大有文章可书的:比如今天要说的 round()
和 Math.round()
。
Test Result
1 | round(1.5) # 2 |
1 | Math.round(1.5) // 2 |
具体分析
观察一下大概可以发现似乎两个语言的 round
函数的单参数用法都是四舍五入到整数上。
坑 0:Python 2 和 Python 3 中的 round
行为不一致
参见 Citation[2] 和 https://docs.python.org/2.7/library/functions.html#round
Python 2 中的 round
采用四舍五入,而 Pyhton 3 中的 round
采用的是大学物理使用的四舍六入五成双(后文会详细解释)。
坑 1:round
“五入”朝向 0 还是朝向更大的方向
参见 Test Case 4 & 5
Python 把 -1.6
、-1.4
round 到 -2
、-1
,而 JS 则将二者 round 到 -1
、-2
。这是因为 Python 会往绝对值更大的方向“五入”、绝对值更小的方向“四舍”,而 JS 则是往值更大的方向“五入”、值更小的方向“四舍”。
坑 2:round
的参数个数
参见 Test Case 6-9
Python 的 round
接受 1 至 2 个参数,而 JavaScript 的 Math.round
只接受一个参数。所以 Python 可以指定舍入精度,而 JS 则只能舍入到整数。
坑 3:浮点数精度
参见 Test Case 7
在 Python 中,1.15
被舍入为 1.1
,这是因为 1.15
实际被存储为 1.149999999999999911...
,该数舍入到小数点后 2 位时符合“四舍”规则。同样,0.645
被存储为 0.645000000000000017...
,舍入到小数点后 2 位时符合五入规则。
使用 JS 的
Number.prototype.toFixed
函数可以看到更多位:
1.15.toFixed(100)
得到了
1.14999999999999991
1182158029987476766 1094665527343750000 0000000000000000000 0000000000000000000 0000000
坑 4:类型转换
参见 Test Case 10-14
JS 和 Python 的 round
都可以正确处理布尔值。然而其内在实现机理是不同的:
- JS 尝试使用
Number
函数将输入转为数字,再对其进行常规的 round 操作。 - Python 尝试调用输入的值的
__round__
方法,并返回其结果。由于字符串(str
类)没有定义__round__
方法,所以无法被round
函数处理。
坑 5:“四舍六入五成双”
参见 Test Case 9
补充阅读:使用O(1)时间复杂度计算比x大的最小的2的整数次幂 中的浮点数相关内容
“坑 3” 提到的浮点数精度丢失并不能解释 Test Case 9 的结果,因为 Python 和 JS 在底层都使用 IEEE 754 双精度浮点数来表示小数,二者在小数表示上精度相同,但是 Test Case 9 的 Python 和 JavaScript 结果不同。
查阅资料,发现 Python 的 round 遵循的是“四舍六入五成双”的舍入规则,即:
*当要保留__小数点后 \(n\) 位__时,*
- 如果第 \(n+1\) 位不为 5,按照四舍五入规则舍入。
- 如果第 \(n+1\) 位为 5,但是其后仍有有效数字,则进位。
- 如果第 \(n+1\) 位为 5,且其后没有任何有效数字:
- 第 \(n\) 位为奇数时,进位;
- 第 \(n\) 位为偶数时,舍去。
该舍入规则使科学计算中舍入时累积误差更小。
1.25
保留小数点后一位时,小数点后第三位为 0,第二位为 5,第一位为偶数,所以舍去第二位的 5,第一位保持不变。
这也意味着 Python 中
round(2.5)
为 2,想必困扰了无数初学者 233333
按照这个规则,round(2.675, 2)
应该得到 2.68
,但是实际得到了 2.67
。这是因为 2.675
踩到了坑 3,其底层表示为 2.6749999999...
。
JavaScript 的 round 则是简单的四舍五入,没有这么多门道。
备注:如果想在 Python 里实现四舍五入,可以使用 decimal
库:
1 | import decimal |