这是double precision 的问题。在文章中,我们看到 double 数据类型以 64 位存储,细分如下:
- 符号位:1 位
- 指数:11 位
- 有效位精度:53 位(52 位显式存储)
将其转换为以 10 为基数,我们可以看到我们可以保证获得至少 15 位小数的精度。
log10(2^53 - 1)
[1] 15.95459
我们可以通过用简单的算术观察奇怪的行为来看到这一点:
options(scipen = 999)
1e16
[1] 10000000000000000
1e16 + 5
[1] 10000000000000004 ## incorrect.. should be 10000000000000005
通过您的r = 9、n = 224 和m = 5 示例以及print 函数中的一些print 语句,我们发现了罪魁祸首:
combination_1_Verbose <- function(n,r,verbose = FALSE){
n_0 <- n
num <- 1
denom <- factorial(r)
for(i in 1:(r)){
num <- num * n_0
n_0 <- n_0-1
}
if (verbose) {
print(num)
print(log10(num))
}
num/denom
}
combination_1_Verbose(n, r - 1, TRUE)
[1] 5585745606995474432
[1] 18.74708
[1] 138535357316356
我们正在对超过 18 位进行算术运算……超出了 double 数据类型提供给我们的精度范围。
同样不明显的是,返回值不完全是138535357316356。使用print 的digits 参数,我们实际上看到返回的值不是整数。
print(combination_1_Verbose(n, r - 1), digits = 22)
[1] 138535357316356.015625
这最终成为您错误的根源。如果我们将.015625 乘以factorial(m) = 120,我们得到:
.015625 * 120
[1] 1.875
四舍五入到2,这是我们检查的差异。
我们可以使用多精度库gmp 纠正这种行为:
library(gmp)
combination_1_GMP <- function(n,r,verbose = FALSE){
n_0 <- as.bigz(n)
num <- as.bigz(1)
denom <- factorialZ(r)
for(i in 1:(r)){
num <- mul.bigz(num, n_0)
n_0 <- sub.bigz(n_0, 1)
}
if (verbose) {
print(num)
print(log10(num))
}
as.bigz(num/denom)
}
combination_1_GMP(n, r-1, TRUE)
Big Integer ('bigz') :
[1] 5585745606995473920
[1] 18.74708
Big Integer ('bigz') :
[1] 138535357316356
在原始函数中,num 是 5585745606995474432,而在我们的 gmp 示例中,我们获得了 5585745606995473920。请注意,差异小于500,这是一个 3 位数字。这是有道理的,因为我们的数字超过 18 位,并且如上所述,我们只能保证 15 位总精度(即18 - 3 = 15)。
或者,我们可以round 最终结果。如果绝对需要精度,我不会推荐这个选项,因为仍然有一些 n、m 和 r 的值仍然受双精度的支配。但它在这个例子中有效:
combination_1_Round <- function(n,r){
n_0 <- n
num <- 1
denom <- factorial(r)
for(i in 1:(r)){
num <- num * n_0
n_0 <- n_0-1
}
round(num/denom)
}
both_large_r <- function(n,m,r){m*combination_1_Round(n,r)}
both_small_r <- function(n,m,r){factorial(m)*combination_1_Round(n,(r-1))}
both_large_r(n,m,r) - both_small_r(n,m,r)
[1] 0
最后,您最好的选择是重新编写算法以将数字保持在双精度范围内。
combination_1_Improved <- function(n,r){
denom <- num <- 1
i <- (n - r + 1)
for (denom in 1:r) {
num <- num * i;
num <- num / denom;
i <- i + 1
}
num
}
print(combination_1_Improved(n,r-1), digits = 22)
[1] 138535357316356