The lazy numbers in R
My new package 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\):
1 - 7 * 0.1
x <-== 0.3
x ## [1] FALSE
With the lazy numbers, it is equal to \(0.3\):
library(lazyNumbers)
lazynb(1) - lazynb(7) * lazynb(0.1)
x <-as.double(x) == 0.3
## [1] TRUE
Correction: this is wrong!
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:
1 - lazynb(7) * 0.1
x <-as.double(x) == 0.3
## [1] TRUE
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\)):
function(n) {
u <-if(n == 1) {
return(1 / 7)
}8 * u(n-1) - 1
}u(15)
## [1] 0.1428223
u(18)
## [1] 0.125
u(20)
## [1] -1
u(30)
## [1] -1227133513
With the lazy numbers, this sequence never moves from \(1/7\):
function(n) {
u_lazy <-if(n == 1) {
return(1 / lazynb(7))
}8 * u_lazy(n-1) - 1
}as.double(u_lazy(100))
## [1] 0.1428571
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):
library(Rmpfr)
function(n) {
u_mpfr <-if(n == 1) {
return(1 / mpfr(7, prec = 256L))
}8 * u_mpfr(n-1) - 1
}asNumeric(u_mpfr(30))
## [1] 0.1428571
asNumeric(u_mpfr(85))
## [1] 0.140625
asNumeric(u_mpfr(100))
## [1] -78536544841
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):
library(microbenchmark)
options(digits = 4L)
microbenchmark(
lazy = u_lazy(200),
mpfr = u_mpfr(200),
times = 20L
)## Unit: milliseconds
## expr min lq mean median uq max neval cld
## lazy 38.42 39.57 40.30 40.04 40.68 43.47 20 a
## mpfr 59.22 60.09 61.01 60.60 61.94 64.89 20 b
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.