0

I would like to round a matrix M to arbitrary precision in Rcpp. It is particularly easy to do so in R:

M <- matrix(rnorm(4), 2, 2)
M
           [,1]      [,2]
[1,] 0.04463484 0.1455878
[2,] 1.77416096 1.0787835

round(M,2)
     [,1] [,2]
[1,] 0.04 0.15
[2,] 1.77 1.08

That turns out to be slightly challenging in Rcpp / C++.

There is a round() function, however, it unfortunately only rounds to the nearest integer. For output purposes, e.g. the "%.2f" format can be used to round to two decimals. If the rounded numbers are to be used in further computations, it is possible to round a single element to arbitrary precision by playing around with floorf, roundf and ceilingf functions using manually adjusted, different scaling factors, see the discussion and proposed solutions here.

Hence, a possible way forward would be to apply above-mentioned approach to each element (or more efficiently, to each column) of the matrix M. This seems unnecessarily complicated and I was wondering whether one of you has a more efficient/elegant solution for rounding matrices to arbitrary precision in Rcpp.

yrx1702
  • 1,619
  • 15
  • 27
  • 1
    You write "a possible way forward would be to apply above-mentioned approach to each element" and that is a general truth. There is no other approach. To a first approximation, _each and every_ Rcpp Sugar function eventually goes down to work on each element. How else could it work? Now, rounding is tricky. There was just a discussion on the r-devel mailing list, and R Core member Martin Maechler just split off a test package for different / new approaches. So you too should do what many of us do: If you want/need different behavior, implement it. Rcpp is a toolkit. – Dirk Eddelbuettel Jan 25 '20 at 16:40
  • I know there are reasons why this may be a bad idea with floating point math, but can't you just define `double round_to_n_places(double& d, int n) { return std::round(d * std::pow(10, n))/std::pow(10, n);}` – Allan Cameron Jan 25 '20 at 17:11

2 Answers2

4

F. Privé has a technically correct answer. But it, just like the OP before him, missed that the Rcpp Sugar function already does exactly the same:

R> Rcpp::cppFunction("NumericVector mr(NumericVector x,int d) {return round(x,d);}")
R> set.seed(42)    
R> x <- runif(5)     
R> x    
[1] 0.914806 0.937075 0.286140 0.830448 0.641746     
R> mr(x, 2)    
[1] 0.91 0.94 0.29 0.83 0.64     
R> mr(x, 0)          
[1] 1 1 0 1 1          
R> mr(x, 7)        
[1] 0.914806 0.937075 0.286139 0.830448 0.641745   
R>  

The confusion, if any, was thinking that the default value of the digits argument was the only permissible value for the number of digits. Naturally, it is not.

Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
  • Ahah sorry about that. Because the OP said it was not implemented, I assumed it was not implemented.. – F. Privé Jan 26 '20 at 17:17
  • 1
    I know how it goes. Even grep'ing in the headers can be imperfect (so I often grep in unit tests instead ;-) ). As always, we could do with more/better documentation. No worries, and no harm implied. Was simply worth correcting. – Dirk Eddelbuettel Jan 26 '20 at 17:18
  • 1
    I like this bookdown: https://teuder.github.io/rcpp4everyone_en/210_rcpp_functions.html. The linked section provides the R-like functions provided by Rcpp, and `round()` is one of them indeed. – F. Privé Jan 26 '20 at 17:21
  • Yes that is one of several decent contributed resources. But I can grep my sources or tests on my deployment machine so that is often quicker... – Dirk Eddelbuettel Jan 26 '20 at 17:24
2

You could implement it yourself using e.g.

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
NumericVector myround(const NumericVector& A, int digits = 0) {
  NumericVector B = clone(A);
  std::size_t K = A.size();
  for (std::size_t k = 0; k < K; k++) {
    B[k] = ::Rf_fround(A[k], digits);
  }
  return B;
}

In R:

> (x <- runif(10))
 [1] 0.5050331 0.8921151 0.4566404 0.5828360 0.6931808 0.9296267 0.3091896 0.4077148 0.9563310
[10] 0.6905403
> myround(x)
 [1] 1 1 0 1 1 1 0 0 1 1
> myround(x, 2)
 [1] 0.51 0.89 0.46 0.58 0.69 0.93 0.31 0.41 0.96 0.69
> (M <- matrix(rnorm(4), 2, 2))
           [,1]     [,2]
[1,] -1.0852162 1.793925
[2,] -0.1912413 1.170089
> myround(M, 2)
      [,1] [,2]
[1,] -1.09 1.79
[2,] -0.19 1.17
F. Privé
  • 11,423
  • 2
  • 27
  • 78