R&(s)和(犰狳)之间的差异()

时间:2016-07-26 12:17:19

标签: c++ r precision rcpp armadillo

当给出相同的输入时,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代码?

3 个答案:

答案 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

我被困在这里。我原本预计至少s6s7是相同的......

我将指出,一般来说,当你的算法依赖于这些微小的数字差异时,你很可能会非常沮丧,因为结果可能会因许多小而且可能不同而有所不同您正在使用的特定操作系统,编译器等控制因素。

答案 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