---
title: "The lazy numbers in R"
author: "Stéphane Laurent"
date: '2022-11-12'
tags: R, C++, Rcpp, maths
rbloggers: yes
output:
  md_document:
    variant: markdown
    preserve_yaml: true
  html_document:
    highlight: kate
    keep_md: no
highlighter: pandoc-solarized
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(collapse = TRUE)
```

My new package [**lazyNumbers**](https://github.com/stla/lazyNumbers) is a port 
of the lazy numbers implemented in the C++ library **CGAL**. The lazy numbers 
represent the real numbers and arithmetic on these numbers is exact.

The ordinary floating-point arithmetic is not exact. Consider for example the 
simple operation $1 - 7 \times 0.1$. In double precision, it is not equal to 
$0.3$:

```{r}
x <- 1 - 7 * 0.1
x == 0.3
```

With the lazy numbers, it is equal to $0.3$:

```{r, message=FALSE}
library(lazyNumbers)
x <- lazynb(1) - lazynb(7) * lazynb(0.1)
as.double(x) == 0.3
```

***Correction:*** *this is* [*wrong*](https://laustep.github.io/stlahblog/posts/lazyNumbers2.html)*!*

In fact, when a binary operation involves a lazy number, the other number is 
automatically converted to a lazy number, so you can shortly enter this 
operation as follows:

```{r}
x <- 1 - lazynb(7) * 0.1
as.double(x) == 0.3
```

Let's see a more dramatic example now. Consider the sequence $(u_n)$ 
recursively defined by $u_1 = 1/7$ and $u_{n+1} = 8 u_n - 1$. You can easily 
check that $u_2 = 1/7$, therefore $u_n = 1/7$ for every $n \geqslant 1$. 
However, when implemented in double precision, this sequence quickly goes 
crazy ($1/7 \approx 0.1428571$):

```{r}
u <- function(n) {
  if(n == 1) {
    return(1 / 7)
  }
  8 * u(n-1) - 1
}
u(15)
u(18)
u(20)
u(30)
```

With the lazy numbers, this sequence never moves from $1/7$:

```{r}
u_lazy <- function(n) {
  if(n == 1) {
    return(1 / lazynb(7))
  }
  8 * u_lazy(n-1) - 1
}
as.double(u_lazy(100))
```

Let's compare with the multiple precision numbers provided by the **Rmpfr** 
package. One has to set the precision of these numbers. Let's try with $256$ 
bits (the double precision corresponds to $53$ bits):

```{r, message=FALSE}
library(Rmpfr)
u_mpfr <- function(n) {
  if(n == 1) {
    return(1 / mpfr(7, prec = 256L))
  }
  8 * u_mpfr(n-1) - 1
}
asNumeric(u_mpfr(30))
asNumeric(u_mpfr(85))
asNumeric(u_mpfr(100))
```

The sequence goes crazy before the $100^{\textrm{th}}$ term. Of course we could 
increase the precision. With the lazy numbers, there's no precision to set. 
Moreover they are faster (at least for this example):

```{r}
library(microbenchmark)
options(digits = 4L)
microbenchmark(
  lazy = u_lazy(200),
  mpfr = u_mpfr(200),
  times = 20L
)
```

Vectors of lazy numbers and matrices of lazy numbers are available in the 
**lazyNumbers** package. One can get the inverse and the determinant of a 
square lazy matrix. 

A thing to note is that the usual mathematical functions such as $\exp$, 
$\cos$ or $\sqrt{}$, are not implemented for lazy numbers. Only the addition, 
the subtraction, the multiplication, the division and the absolute value 
are implemented.
