printed value for high numbers is wronger than it should?

Viewed 213

When printing a high number in R I expect to see a rounded value due to floating point magic. Indeed :

options(scipen = 999)
x <- 10000000000000000000000000
x
#> [1]  9999999999999998758486016

However I expected that this rounded number would be rounded to itself, and it appears it isn't

x ==  9999999999999998758486016
#> [1] FALSE

9999999999999998758486016
#> [1]  9999999999999996611002368

I found manually the min number that rounds to the original rounded value

x ==  9999999999999998799999999
#> [1] FALSE

9999999999999998799999999
#> [1]  9999999999999996611002368

x ==  9999999999999998800000000
#> [1] TRUE

9999999999999998800000000
#> [1]  9999999999999998758486016

While an explanation would be appreciated, I have a practical issue. I'd like to design a faithful dput() equivalent that would work with any number.

The constraint to satisfy is : x == as.numeric(my_deparser(x))

If mydeparser() could return "9999999999999998800000000" for the above for instance I'd be happy, because

10000000000000000000000000 == as.numeric("9999999999999998800000000")
#> [1] TRUE

I've tried format(), dput(), deparse() with no luck.

How can I achieve this ?

My session info :

R version 4.1.3 (2022-03-10) Platform: aarch64-apple-darwin20 (64-bit) Running under: macOS Monterey 12.0.1

3 Answers

An option is to use a package like gmp or Rmpfr. Below a few examples with package gmp.

library(gmp)
#> 
#> Attaching package: 'gmp'
#> The following objects are masked from 'package:base':
#> 
#>     %*%, apply, crossprod, matrix, tcrossprod

x <- as.bigz("10000000000000000000000000")
x
#> Big Integer ('bigz') :
#> [1] 10000000000000000000000000

x + 1
#> Big Integer ('bigz') :
#> [1] 10000000000000000000000001
x * x
#> Big Integer ('bigz') :
#> [1] 100000000000000000000000000000000000000000000000000

Created on 2022-07-07 by the reprex package (v2.0.1)

Thanks to @MrFlick's comment here's a workaround :

my_deparser <- function(x) {
  x_chr <- format(x, digits = 22)
  if (as.numeric(x_chr) == x) x_chr else sprintf("%a", x)
}

# printed fine already, se we can deparse pretty
x <- 20000000000000000000000000
my_deparser(x)
#> [1] "19999999999999997516972032"
as.numeric(my_deparser(x)) == x
#> [1] TRUE

# need workaround
x <- 10000000000000000000000000
my_deparser(x)
#> [1] "0x1.08b2a2c28029p+83"
as.numeric(my_deparser(x)) == x
#> [1] TRUE

That's not ideal since I'd rather print a readable number. I have explored some convoluted solutions computing the log10(abs(x - x_printed)) and changing digits of x_printed ("9999999999999998758486016") to check if the equality is verified, for instance changing the "7" to an "8" below :

9999999999999998858486016 == 10000000000000000000000000
#> [1] TRUE

This is silly but this can probably give a robust solution with some iterations, very inefficient however.

I believe that the scipen option has to do with how values are printed, not how they are stored, so when you do:

x <- 10000000000000000000000000

The value is actually stored as 10000000000000000000000000, even if it prints as something else. So

x == 10000000000000000000000000

should return TRUE.

Related