1. 問題
今天在做leetcode 不同路徑 的時候發現了個問題
對于m=53 n=4class Solution:def uniquePaths(self, m: int, n: int) -> int:rlt = 1for i in range(0, m-1):rlt *= (m + n - 2 - i)for i in range(0, m-1):rlt /= (i + 1)return int(rlt)為什么這個結果是 26234class Solution:def uniquePaths(self, m: int, n: int) -> int:up = 1down = 1for i in range(0, m-1):up *= (m + n - 2 - i)for i in range(0, m-1):down *= (i + 1)return int(up/down)而這個結果是 26235
據GPT描述,兩個代碼邏輯上是相同的,計算的是組合數 C m + n ? 2 m ? 1 C_{m+n-2}^{m-1} Cm+n?2m?1?:
C m + n ? 2 m ? 1 = ( m + n ? 2 ) ! ( m ? 1 ) ! ( n ? 1 ) ! C_{m+n-2}^{m-1} = \frac{(m+n-2)!}{(m-1)!(n-1)!} Cm+n?2m?1?=(m?1)!(n?1)!(m+n?2)!?
然而,兩個代碼的計算方式有所不同:
-
第一個代碼 直接使用浮點除法
rlt /= (i + 1)
,在 Python 中,浮點數的計算可能會引入精度誤差,最終int(rlt)
向下取整,導致結果偏小。 -
第二個代碼 分別計算
up
和down
,然后用int(up/down)
,由于 Python 的整數運算是精確的,這種方式避免了浮點誤差,得到了正確的組合數 26235。
第一個代碼因為浮點誤差,導致 int(rlt)
取整后比正確值少 1,所以結果是 26234,而第二個代碼由于整數計算的精確性,得到了正確答案 26235。
2. 思考
即使除法的數學結果是整數,如果中間計算涉及浮點數,可能會因為浮點精度誤差導致最終取整時丟失 1。我們來深入分析原因:
① Python 的 /
生成浮點數
Python 里的 /
即使除得的是整數,結果仍然是 float
。例如:
print(10 / 2) # 5.0
print(type(10 / 2)) # <class 'float'>
在代碼中,這意味著:
rlt = int(rlt / (i + 1))
即使 rlt / (i + 1)
的數學結果是整數,Python 仍然會先計算 浮點數,然后 int()
強制取整,可能引入誤差。
② 浮點數存儲精度問題
Python 的浮點數是基于 IEEE 754 標準的 雙精度浮點數(double precision)。但 某些整數在浮點數表示中無法精確存儲,例如:
x = 10000000000000003
y = 10000000000000002print(float(x)) # 10000000000000004.0 (存儲時發生變化)
print(float(y)) # 10000000000000002.0 (存儲正確)
當 rlt
變大時,比如 C_{m+n-2}^{m-1}
計算過程中,rlt
可能會進入 浮點存儲的誤差范圍,導致浮點數計算不精確。
③ int()
取整丟失 1
浮點數不能精確表示某些大整數時,取 int()
可能會導致向下取整。假設 rlt / (i + 1)
的數學結果是 26235
,但由于浮點精度問題,Python 存儲的可能是:
26234.999999999998
此時 int(26234.999999999998)
向下取整,結果變成 26234,比正確答案少了 1。
④ 解決方案
避免浮點數參與計算,直接使用 整數除法 //
,確保所有計算都在 整數空間 進行:
rlt //= (i + 1) # 這樣不會有浮點精度問題
這樣,每次除法都保證是 整數運算,不會因浮點精度誤差導致 int()
取整出錯。
3. 進一步理解
為什么x = 10000000000000003
y = 10000000000000002print(float(x)) # 10000000000000004.0 (存儲時發生變化)
print(float(y)) # 10000000000000002.0 (存儲正確)
這個現象是由于 IEEE 754 雙精度浮點數(double precision floating point) 的 有限精度 導致的。讓我們來深入分析為什么 10000000000000003
在轉換成 float
時變成了 10000000000000004.0
,而 10000000000000002
仍然能正確存儲。
① 浮點數的存儲方式
Python 的 float
類型遵循 IEEE 754 雙精度浮點數標準,使用 64 位 進行存儲:
- 1 位 表示符號位(正/負)。
- 11 位 表示指數(決定浮點數的范圍)。
- 52 位 表示 尾數(精確存儲的部分)。
由于 52 位尾數最多只能精確存儲 52 位二進制數,所以如果一個整數的二進制表示超過 52 位,就必須 進行四舍五入,這可能導致數值發生變化。
② 10000000000000003
和 10000000000000002
的二進制表示
我們先看看這兩個數的 二進制表示(十進制 → 二進制):
10000000000000002
10000000000000002 = 10001110000110111100100110101100111011001000000000000000000010 (二進制)
- 這個數的尾數(低 52 位)可以被精確存儲,所以轉換成
float
時不會損失精度。
10000000000000003
10000000000000003 = 10001110000110111100100110101100111011001000000000000000000011 (二進制)
- 這個數的尾數超過了 52 位的存儲限制,所以 IEEE 754 必須進行四舍五入。
- 由于 IEEE 754 采用 “最接近偶數舍入”(round to nearest, ties to even),它會 向上舍入 到
10000000000000004
,因此float(10000000000000003)
變成了10000000000000004.0
。
③ 為什么 10000000000000003
變成 10000000000000004.0
?
當一個整數大到超出 52 位精度范圍 時:
- 無法精確存儲,需要進行近似表示。
- IEEE 754 采用“四舍五入到最近的偶數”規則:
10000000000000003
比10000000000000004
更接近10000000000000004
,所以它會被 向上舍入。- 如果它的二進制表示是
...00011
(奇數結尾),IEEE 754 會向上舍入到...00100
(偶數)。
所以:
print(float(10000000000000003)) # 10000000000000004.0
print(float(10000000000000002)) # 10000000000000002.0
這個現象就是 浮點數的精度限制 造成的,它在轉換時 自動進行了四舍五入,使得 10000000000000003
變成了 10000000000000004.0
。
總結
- Python
float
遵循 IEEE 754 雙精度浮點數(64 位),其中只有 52 位 用于存儲尾數,超過部分會 四舍五入。 10000000000000002
可以精確存儲,因為它的二進制表示沒有超出 52 位精度范圍。10000000000000003
由于超出 52 位精度范圍,被四舍五入成10000000000000004.0
。
寫在最后
本文采用了 ChatGPT 輔助進行內容的書寫和完善