What is the correct/standard way to check if difference is smaller than machine precision?

前端 未结 2 1058
野趣味
野趣味 2021-02-01 14:48

I often end up in situations where it is necessary to check if the obtained difference is above machine precision. Seems like for this purpose R has a handy variable: .Mac

2条回答
  •  青春惊慌失措
    2021-02-01 15:04

    The machine precision for double depends on its current value. .Machine$double.eps gives the precision when the values is 1. You can use the C function nextAfter to get the machine precision for other values.

    library(Rcpp)
    cppFunction("double getPrec(double x) {
      return nextafter(x, std::numeric_limits::infinity()) - x;}")
    
    (pr <- getPrec(1))
    #[1] 2.220446e-16
    1 + pr == 1
    #[1] FALSE
    1 + pr/2 == 1
    #[1] TRUE
    1 + (pr/2 + getPrec(pr/2)) == 1
    #[1] FALSE
    1 + pr/2 + pr/2 == 1
    #[1] TRUE
    pr/2 + pr/2 + 1 == 1
    #[1] FALSE
    

    Adding value a to value b will not change b when a is <= half of it's machine precision. Checking if the difference is smaler than machine precision is done with <. The modifiers might consider typical cases how often an addition did not show a change.

    In R the machine precision can be estimated with:

    getPrecR <- function(x) {
      y <- log2(pmax(.Machine$double.xmin, abs(x)))
      ifelse(x < 0 & floor(y) == y, 2^(y-1), 2^floor(y)) * .Machine$double.eps
    }
    getPrecR(1)
    #[1] 2.220446e-16
    

    Each double value is representing a range. For a simple addition, the range of the result depends on the reange of each summand and also the range of their sum.

    library(Rcpp)
    cppFunction("std::vector getRange(double x) {return std::vector{
       (nextafter(x, -std::numeric_limits::infinity()) - x)/2.
     , (nextafter(x, std::numeric_limits::infinity()) - x)/2.};}")
    
    x <- 2^54 - 2
    getRange(x)
    #[1] -1  1
    y <- 4.1
    getRange(y)
    #[1] -4.440892e-16  4.440892e-16
    z <- x + y
    getRange(z)
    #[1] -2  2
    z - x - y #Should be 0
    #[1] 1.9
    
    2^54 - 2.9 + 4.1 - (2^54 + 5.9) #Should be -4.7
    #[1] 0
    2^54 - 2.9 == 2^54 - 2      #Gain 0.9
    2^54 - 2 + 4.1 == 2^54 + 4  #Gain 1.9
    2^54 + 5.9 == 2^54 + 4      #Gain 1.9
    

    For higher precission Rmpfr could be used.

    library(Rmpfr)
    mpfr("2", 1024L)^54 - 2.9 + 4.1 - (mpfr("2", 1024L)^54 + 5.9)
    #[1] -4.700000000000000621724893790087662637233734130859375
    

    In case it could be converted to integer gmp could be used (what is in Rmpfr).

    library(gmp)
    as.bigz("2")^54 * 10 - 29 + 41 - (as.bigz("2")^54 * 10 + 59)
    #[1] -47
    

提交回复
热议问题