Java中ans *= (N-i+1)/i与ans=ans*(N-i+1)/i结果为何不同?
ans *= (N-i+1)/i和ans = ans * (N-i+1)/i结果不一致? 在解决Unique Paths问题时,当输入m=51、n=9时,两种看似等价的迭代写法得到了完全不同的计算结果,核心原因是整数除法的运算顺序导致精度丢失的时机不同。
问题重现
写法1:使用ans *= (N-i+1)/i
public int uniquePaths(int m, int n) { double ans = 1; int N = m+n-2; int R = Math.min(n, m)-1; for(int i = 1; i <= R; i++) { ans *= (N-i+1)/i; } return (int)ans; }
输出片段:
51/1 ans=58.0 52/2 ans=1624.0 53/3 ans=29232.0 ...
写法2:使用ans = ans * (N-i+1)/i
public int uniquePaths(int m, int n) { double ans = 1; int N = m+n-2; int R = Math.min(n, m)-1; for(int i = 1; i <= R; i++) { System.out.println((N-R+i)+"/"+i); ans = ans * (N-i+1)/i; System.out.println("ans="+ans); } return (int)ans; }
输出片段:
51/1 ans=58.0 52/2 ans=1653.0 53/3 ans=30856.0 ...
核心原因分析
Java中,N、i都是int类型,整数除法会直接舍去小数部分,这是两种写法差异的关键:
写法1的运算顺序:
ans *= (N-i+1)/i等价于ans = ans * ((N-i+1)/i)
先执行(N-i+1)/i的整数除法,得到一个整数结果,再和ans(double类型)相乘。
以i=2为例:(58-2+1)/2 = 57/2 = 28(整数除法舍去0.5),所以ans = 58 * 28 = 1624。写法2的运算顺序:
ans = ans * (N-i+1)/i等价于ans = (ans * (N-i+1)) / i
先执行ans * (N-i+1):ans是double,N-i+1会自动转为double,相乘得到带小数的结果;再除以i(同样转为double),保留了小数部分。
以i=2为例:58 * 57 = 3306(double类型),再除以2得到1653.0,没有丢失精度。
后续迭代中,写法1的整数除法误差会被不断放大,最终导致结果完全偏离正确值;而写法2通过先乘后除的浮点数运算,最大程度保留了精度,结果更接近正确的组合数计算值。
补充说明
Unique Paths的本质是计算组合数C(m+n-2, min(m,n)-1),正确的迭代计算应该优先保证运算过程中不丢失精度,写法2的逻辑更符合组合数的迭代计算规则(每次乘分子、除以分母,用浮点数或大整数类型避免精度损失)。
内容的提问来源于stack exchange,提问作者Shivam Taneja

