提问人:Max577 提问时间:11/10/2022 最后编辑:r2evansMax577 更新时间:11/11/2022 访问量:81
Double 的 R 精度 - 为什么代码返回负值,为什么预期正结果?
R Precision for Double - Why code returns negative why positive outcome expected?
问:
我正在测试计算 Prod(b-a) 的 2 种方法,其中 a 和 b 是长度为 n 的向量。 Prod(b-a)=(b1-a1)(b2-a2)(b3-a3)*...(bn-an),其中 b_i>a_i>0 表示所有 i=1,2,3,n。对于某些特殊情况,另一种计算方法(方法2)此prod(b-a)更有效。它使用以下公式,即展开项并求和:
我的问题是:当a_i非常接近 b_i 时,真正的结果可能是非常非常接近 0,比如 10^(-16)。方法 1(减法和乘法)始终返回正输出。使用公式的方法 2 有时会返回负输出(我的实验大约有 7~8% 的时间返回负值)。从数学上讲,这两个方法应该返回完全相同的输出。但在计算机语言中,它显然会产生不同的输出。
以下是我运行测试的代码。当我运行测试代码 10000 次时,大约 7~8% 的方法 2 运行返回负输出。根据官方文档,R double 的精度为 “2.225074e-308”,如 R 参数所示:“.机器$double.xmin”。为什么当差异在 10^(-16) ~ 10^(-18) 之间时它会变成负值?任何能说明这一点的帮助都将被逮捕。我还希望得到一些关于如何实际将精度提高到更高级别的建议,如R文档所示。
########## Testing code 1.
ftest1case<-function(a,b) {
n<-length(a)
if (length(b)!=n) stop("--------- length a and b are not right.")
if ( any(b<a) ) stop("---------- b has to be greater than a all the time.")
out1<-prod(b-a)
out2<-0
N<-2^n
for ( i in 1:N ) {
tidx<-rev(as.integer(intToBits(x=i-1))[1:n])
tsign<-ifelse( (sum(tidx)%%2)==0,1.0,-1.0)
out2<-out2+tsign*prod(b[tidx==0])*prod(a[tidx==1])
}
c(out1,out2)
}
########## Testing code 2.
ftestManyCases<-function(N,printFreq=1000,smallNum=10^(-20))
{
tt<-matrix(0,nrow=N,ncol=2)
n<-12
for ( i in 1:N) {
a<-runif(n,0,1)
b<-a+runif(n,0,1)*0.1
tt[i,]<-ftest1case(a=a,b=b)
if ( (i%%printFreq)==0 ) cat("----- i = ",i,"\n")
if ( tt[i,2]< smallNum ) cat("------ i = ",i, " ---- Negative summation found.\n")
}
tout<-apply(tt,2,FUN=function(x) { round(sum(x<smallNum)/N,6) } )
names(tout)<-c("PerLess0_Method1","PerLee0_Method2")
list(summary=tout, data=tt)
}
######## Step 1. Test for 1 case.
n<-12
a<-runif(n,0,1)
b<-a+runif(n,0,1)*0.1
ftest1case(a=a,b=b)
######## Step 2 Test Code 2 for multiple cases.
N<-300
tt<-ftestManyCases(N=N,printFreq = 100)
tt[[1]]
答:
我很难想象一个由生成 2^n 排列并将它们相加的算法何时会比直接的差异乘积更有效,但我会相信你的话,它有一些特殊情况。
正如评论中所建议的,问题的根源是添加不同量级的值时浮点误差的累积;有关浮点的 R 特定问题,请参阅此处,有关一般解释,请参阅此处。
首先,举个简单的例子:
n <- 12
set.seed(1001)
a <- runif(a,0,1)
b <- a + 0.01
prod(a-b) ## 1e-24
out2 <- 0
N <- 2^n
out2v <- numeric(N)
for ( i in 1:N ) {
tidx <- rev(as.integer(intToBits(x=i-1))[1:n])
tsign <- ifelse( (sum(tidx)%%2)==0,1.0,-1.0)
j <- as.logical(tidx)
out2v[i] <- tsign*prod(b[!j])*prod(a[j])
}
sum(out2v) ## -2.011703e-21
使用扩展精度(精度为 1000 位)来检查简单/蛮力计算是否更可靠:
library(Rmpfr)
a_m <- mpfr(a, 1000)
b_m <- mpfr(b, 1000)
prod(a_m-b_m)
## 1.00000000000000857647286522936696473705868726043995807429578968484409120647055193862325070279593735821154440625984047036486664599510856317884962563644275433171621778761377125514191564456600405460403870124263023336542598111475858881830547350667868450934867675523340703947491662460873009229537576817962228e-24
这证明了这种情况的重点,但一般来说,执行扩展精度算术可能会扼杀您获得的任何性能提升。
用值重做基于排列的计算(使用 ,并返回运行求和,而不是在向量中累积值并调用)给出准确的答案(至少到前 20 位左右,我没有进一步检查),但在我的机器上需要 6.5 秒(而不是使用常规浮点时的 0.03 秒)。mpfr
out2 <- mpfr(0, 1000)
out2 <- out2 + ...
sum()
为什么这个计算有问题?首先,请注意 (approx 2e-308) 和 (approx 2e-16) 之间的差异,前者是系统可以存储的最小浮点值,后者是 1+x > x 的最小值,即可以在不发生灾难性抵消的情况下添加的最小相对值(比此量级大一点的值将经历严重,但不是灾难性的, 取消)。.Machine$double.xmin
.Machine$double.eps
现在看一下 值的分布 ,值的序列 :out2v
out2v
hist(out2v)
存在类似大小的负数和正数聚类。如果我们的求和碰巧加上了一堆几乎取消的值(因此结果非常接近 0),然后将其添加到另一个不接近零的值,我们将得到错误的取消。
完全有可能有一种方法可以重新排列此计算,以免发生错误的取消,但我想不出一个容易的办法。
评论
上一个:R 中处理无限分数时的数学不精确
下一个:在 R 中处理非常精确的数字
评论