---
title: Exact Decimal Rounding via Rationals
author: Martin Mächler
address: ETH Zurich and R Core Team
date: 'July 23, 2020; rendered on `r Sys.Date()`'
output:
  html_vignette:
    css: style.css
vignette: >
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteIndexEntry{Exact Decimal Rounding via Rationals}
  %\VignetteEncoding{UTF-8}
  %\VignetteDepends{round}
  %\VignetteDepends{gmp}
---
```{r build-info, eval=FALSE, echo=FALSE}
  setwd("~/R/Pkgs/round/vignettes")
  rmarkdown::render("rationalRound.Rmd", clean=FALSE)
```
## Intro

As shown in the "basic" vignette [Rounding][] of package `round`,
rounding to decimal digits of double precision numbers is not trivial,
mostly because most rational numbers and even most rational numbers with a finite (small) number of decimal digits are *not* exactly representable as regular (double precision) numbers in R:

In communication with Steven Dirkse (@ GAMS), started on the R-devel mailing lists, then continued in private, we came to concluding his own proposal
for _correct rounding_  as the following, using the definitions (as in [Rounding][]),

Rounding to the nearest integer,
$$
\mathrm{round}(x) := r_0(x) := \mathrm{nearbyint}(x)
$$

and its generalization, rounding to $d$ digits,
$$
 \mathrm{round}(x, d) := r_0(x \cdot 10^d) / 10^d
$$
for all integer $d \in \mathbb{Z}$ (i.e., including negative `digits` $d$).

1. all double precision numbers are rational mathematically, i.e., \(\in \mathbb{Q}\).
2. `round(x, n)` as a mathematical function is well defined unambigously on the rationals,
   i.e., for all \(x \in \mathbb{Q}\) with the definitions above.

These two define _exact rounding_ and he would want R to use that.

In the following, I will use that, via exact arithmetic with rational numbers via CRAN pkg [gmp][] (which is based on the free C library `GNU MP` aka [`GMP`][].

```{r simple}
d1 <- c(.1, .2, .3, .4, .5, .6, .7, .8, .9)
fivers <- list(
    d1 =    .5
  , d2 = c( .05, .15,  .25,  .35,  .45,  .55,  .65,  .75,  .85,  .95)
  , d3 = c(.005, .015, .025, .035, .045, .055, .065, .075, .085, .095,
           .105, .115, .125, .135, .145, .155, .165, .175, .185, .195,
           .205, .215, .225, .235, .245, .255, .265, .275, .285, .295,
           .305, .315, .325, .335, .345, .355, .365, .375, .385, .395,
           .405, .415, .425, .435, .445, .455, .465, .475, .485, .495,
           .505, .515, .525, .535, .545, .555, .565, .575, .585, .595,
           .605, .615, .625, .635, .645, .655, .665, .675, .685, .695,
           .705, .715, .725, .735, .745, .755, .765, .775, .785, .795,
           .805, .815, .825, .835, .845, .855, .865, .875, .885, .895,
           .905, .915, .925, .935, .945, .955, .965, .975, .985, .995)
    )
```
Now, "obviously" (from what I wrote before), most of the decimal fractions above are *not* exactly representable as double precision numbers, but of course *are* representable as fractions, concretely as ratios of (arbitrarily long aka _"big num"_)  integers:

```{r q-fivers}
require(gmp)
q5er <- lapply(fivers, as.bigq)
q5er
as.bigz(2)^60 == max(denominator(q5er$d3))
```

Now the "exact rounding" has been available in CRAN package `gmp` since Feb.2020, but in its first versions, we usee "round up", instead of  `nearbyint()` which would _"round to even"_ as desired, as it is the IEEE default also adhered to by \R itself.
Only from `gmp` version 0.6-1 on (
```{r round0-roundQ}
if(print(packageVersion("gmp")) < "0.6-1") {
    ## From 'gmp's namespace, usually "hidden", needed here :
    is.whole.bigq  <- gmp:::is.whole.bigq
    biginteger_mod <- gmp:::biginteger_mod
    .mod.bigz <- function(e1, e2) .Call(biginteger_mod, e1, e2)

  ##' rounding to integer a la "nearbyint()" -- i.e. "round to even"
  round0 <- function(x) {
      nU <- as.bigz.bigq(xU <- x + as.bigq(1, 2)) # traditional round: .5 rounded up
      if(any(I <- is.whole.bigq(xU))) { # I <==>  x == <n>.5 : "hard case"
          I[I] <- .mod.bigz(nU[I], 2L) == 1L # rounded up is odd  ==> round *down*
          nU[I] <- nU[I] - 1L
      }
      nU
  }

  roundQ <- function(x, digits = 0, r0 = round0) {
      ## round(x * 10^d) / 10^d
      p10 <- as.bigz(10) ^ digits
      r0(x * p10) / p10
  }

  ##' round() method ==>  arguments = (x, digits)
  round.bigq <- function(x, digits = 0) roundQ(x, digits)
  .S3method("round","bigq", round.bigq)

} else ## all the above is part of gmp >= 0.6-1 :
  withAutoprint({
    round0
    roundQ
    gmp:::round.bigq
  })

## "simple round" was used in round.bigq() for several months in 2020:
round0s <- function(x) as.bigz.bigq(x + as.bigq(1, 2))
```

Gives here
```{r round-x-2}
round(q5er$d2, 1)
round(q5er$d3, 2)
```

Now comparing these with all the `round()`ing methods from our package `round`:

```{r roundAll-x-1}
require(round)

roundAllX <- function(x, digits, versions = roundVersions) {
    xQ <- as.bigq(x)
    M <- cbind(roundAll(x, digits, versions=versions)
             , SxctQ = asNumeric(roundQ(xQ, digits, r0=round0s))
             ,  xctQ = asNumeric(roundQ(xQ, digits))
               )
    rownames(M) <- format(x)
    M
}

(rA1 <- roundAllX(fivers$d2, 1))
```

with some differences already, and more with `digits=2` rounding:

```{r roundAll-x-2}
(rA2 <- roundAllX(fivers$d3, 2))
```

Not sure, if exact equality is the correct test here.
We should really check only if rounding _up_ vs rounding _down_ happens in each case,
but the rounded numbers may actually differ at by a few bits
(invisibly when printed by R!) :


```{r eqAll-1}
symnum(rA1 == rA1[,"xctQ"])
```

```{r eqAll-2}
symnum(rA2 == rA2[,"xctQ"])
```


[Wikipedia, Rounding]: https://en.wikipedia.org/wiki/Rounding

[Wikipedia, Round half to even]: https://en.wikipedia.org/wiki/Rounding#Round_half_to_even


[`GMP`]: https://gmplib.org/

[gmp]: https://CRAN.R-project.org/package=gmp
[gmp-dev]: https://forgemia.inra.fr/sylvain.jasson/gmp

[round]: https://CRAN.R-project.org/package=round

[Rounding]: https://CRAN.R-project.org/package=round/vignettes/Rounding.html


### Session information
(Note half a dozen non-standard packages present only as dependences of `rmarkdown` we use for rendering this vignette)
```{r, echo=FALSE}
print(sessionInfo(), locale=FALSE)
```
