当给出相同的输入时,R< sum()
函数和RcppArmadillo的accu()
函数的结果存在细微差别。例如,以下代码:
R:
vec <- runif(100, 0, 0.00001)
accu(vec)
sum(vec)
C ++:
// [[Rcpp::depends("RcppArmadillo")]]
// [[Rcpp::export]]
double accu(arma::vec& obj)
{
return arma::accu(obj);
}
给出结果:
0.00047941851844312633(C ++)
0.00047941851844312628(R)
根据http://keisan.casio.com/calculator,真正的答案是:
4.79418518443126270948E-4
这些小差异在我的算法中加起来并且显着影响它的执行方式。有没有办法在C ++中更准确地总结向量?或者至少得到与R相同的结果而不必调用R代码?
答案 0 :(得分:14)
更新:根据其他人在源代码中找到的内容,我错了 - sum()
不排序。我在下面找到的一致性模式源于以下事实:排序(在下面的某些情况下完成)和使用扩展精度中间值(如在sum()
中所做的那样)可以对精度产生类似的影响...
@ user2357112评论如下:
src/main/summary.c ...没有进行任何排序。 (添加到求和操作需要付出很多费用。)它甚至不使用成对或补偿求和;它只是天真地在LDOUBLE中添加从左到右的所有内容(长双或双,取决于HAVE_LONG_DOUBLE)。
我已经厌倦了自己在R源代码中寻找这个(没有成功 - sum
很难搜索),但我可以通过实验证明在执行;以下sum()
时,R将输入向量从最小到最大排序,以便最大化准确度sum()
和Reduce()
结果之间的差异是由于使用了扩展精度。我不知道accu
做了什么......
set.seed(101)
vec <- runif(100, 0, 0.00001)
options(digits=20)
(s1 <- sum(vec))
## [1] 0.00052502325481269514554
使用Reduce("+",...)
只需按顺序添加元素。
(s2 <- Reduce("+",sort(vec)))
## [1] 0.00052502325481269514554
(s3 <- Reduce("+",vec))
## [1] 0.00052502325481269503712
identical(s1,s2) ## TRUE
?sum()
也说
尽可能使用扩展精度累加器,但这取决于平台。
在RcppArmadillo
中对有序向量执行此操作会得到与R中相同的答案;按照原始顺序对矢量进行处理给出了一个不同的答案(我不知道为什么;我的猜测是前面提到的扩展精度累加器,当数据未排序时会更多地影响数值结果)。 / p>
suppressMessages(require(inline))
code <- '
arma::vec ax = Rcpp::as<arma::vec>(x);
return Rcpp::wrap(arma::accu(ax));
'
## create the compiled function
armasum <- cxxfunction(signature(x="numeric"),
code,plugin="RcppArmadillo")
(s4 <- armasum(vec))
## [1] 0.00052502325481269525396
(s5 <- armasum(sort(vec)))
## [1] 0.00052502325481269514554
identical(s1,s5) ## TRUE
但正如评论中指出的那样,这并不适用于所有种子:在这种情况下,Reduce()
结果更接近到sum()
的结果
set.seed(123)
vec2 <- runif(50000,0,0.000001)
s4 <- sum(vec2); s5 <- Reduce("+",sort(vec2))
s6 <- Reduce("+",vec2); s7 <- armasum(sort(vec2))
rbind(s4,s5,s6,s7)
## [,1]
## s4 0.024869900535651481843
## s5 0.024869900535651658785
## s6 0.024869900535651523477
## s7 0.024869900535651343065
我被困在这里。我原本预计至少s6
和s7
是相同的......
我将指出,一般来说,当你的算法依赖于这些微小的数字差异时,你很可能会非常沮丧,因为结果可能会因许多小而且可能不同而有所不同您正在使用的特定操作系统,编译器等控制因素。
答案 1 :(得分:10)
我找到了什么:
我成功地编写了一个能够模仿R&和函数的函数。看起来R使用更高精度的变量来存储每个加法运算的结果。
我写的:
// [[Rcpp::depends("RcppArmadillo")]]
// [[Rcpp::export]]
double accu2(arma::vec& obj)
{
long double result = 0;
for (auto iter = obj.begin(); iter != obj.end(); ++iter)
{
result += *iter;
}
return result;
}
速度如何比较:
set.seed(123)
vec <- runif(50000, 0, 0.000001)
microbenchmark(
sum(vec),
accu(vec),
accu2(vec)
)
expr min lq mean median uq max neval
sum(vec) 72.155 72.351 72.61018 72.6755 72.7485 75.068 100
accu(vec) 48.275 48.545 48.84046 48.7675 48.9975 52.128 100
accu2(vec) 69.087 69.409 70.80095 69.6275 69.8275 182.955 100
所以,我的c ++解决方案仍然比R的总和更快,但是它比犰狳的精确度慢得多()
答案 2 :(得分:4)
您可以使用mpfr
包(多精度浮点可靠)并指定小数点
library("Rmpfr")
set.seed(1)
vec <- runif(100, 0, 0.00001)
# [1] 2.655087e-06 3.721239e-06 5.728534e-06 9.082078e-06 2.016819e-06 8.983897e-06 9.446753e-06 6.607978e-06 6.291140e-06 6.178627e-07 2.059746e-06
# [12] 1.765568e-06 6.870228e-06 3.841037e-06 7.698414e-06 4.976992e-06 7.176185e-06 9.919061e-06 3.800352e-06 7.774452e-06 9.347052e-06 2.121425e-06
# [23] 6.516738e-06 1.255551e-06 2.672207e-06 3.861141e-06 1.339033e-07 3.823880e-06 8.696908e-06 3.403490e-06 4.820801e-06 5.995658e-06 4.935413e-06
# [34] 1.862176e-06 8.273733e-06 6.684667e-06 7.942399e-06 1.079436e-06 7.237109e-06 4.112744e-06 8.209463e-06 6.470602e-06 7.829328e-06 5.530363e-06
# [45] 5.297196e-06 7.893562e-06 2.333120e-07 4.772301e-06 7.323137e-06 6.927316e-06 4.776196e-06 8.612095e-06 4.380971e-06 2.447973e-06 7.067905e-07
# [56] 9.946616e-07 3.162717e-06 5.186343e-06 6.620051e-06 4.068302e-06 9.128759e-06 2.936034e-06 4.590657e-06 3.323947e-06 6.508705e-06 2.580168e-06
# [67] 4.785452e-06 7.663107e-06 8.424691e-07 8.753213e-06 3.390729e-06 8.394404e-06 3.466835e-06 3.337749e-06 4.763512e-06 8.921983e-06 8.643395e-06
# [78] 3.899895e-06 7.773207e-06 9.606180e-06 4.346595e-06 7.125147e-06 3.999944e-06 3.253522e-06 7.570871e-06 2.026923e-06 7.111212e-06 1.216919e-06
# [89] 2.454885e-06 1.433044e-06 2.396294e-06 5.893438e-07 6.422883e-06 8.762692e-06 7.789147e-06 7.973088e-06 4.552745e-06 4.100841e-06 8.108702e-06
# [100] 6.049333e-06
sum(mpfr(vec,10))
# 1 'mpfr' number of precision 53 bits
# [1] 0.00051783234812319279