juliasignificant-digitsapproximate

How to test approximate equality for a specific number significant digits in Julia


The isapprox() function in Julia is used to test if two numbers or arrays are approximately equal. I want to be able to test for approximate equality for any desired number of significant digits. As the code sample below demonstrates, the tolerance for approximation is either given in an absolute value, or a relative (percentage) deviation.

# Syntax
isapprox(a, b; atol = <absolute tolerance>, rtol = <relative tolerance>)

# Examples
# Absolute tolerance
julia> isapprox(10.0,9.9; atol = 0.1) # Tolerance of 0.1
true

# Relative tolerance
julia> isapprox(11.5,10.5; rtol = 0.1) # Rel. tolerance of 10%
true

julia> isapprox(11.4,10.5; rtol = 0.01) # Rel. tolerance of 1%
false

julia> isapprox(98.5, 99.4; rtol = 0.01) # Rel. tolerance of 1%
true

I read in a forum somewhere that setting rtol = 1e-n, where n is the number of significant digits will compare the significant digits. (Unfortunately, I am unable to find it again.) No matter, as the example demonstrates, this is clearly not true.

Given that we in this case want to approximate equality with two significant digits, both 11.4 and 10.5 is approximately equal to 11. However, the relative difference between the two are greater than 1%, returning the approximation false. However, for any number greater than 90, the approximation will be true. Increasing the relative tolerance tenfold to 10% will result in too low sensitivity, as the code demonstrates.

Is there a parameter/value/formula I can set rtol for isapprox() to return true correctly for any desired number of significant digits?


Solution

  • The quick answer is no, there is no fixed value of rtol you can choose to guarantee that isapprox(x, y; rtol=rtol) compares values x and y to a certain number of significant figures. This is because of how isapprox is implemented: rtol is calculated relative to the maximum of norm(x) and norm(y). You would have to calculate a different rtol for each pair of x and y you are comparing.

    What it looks like you are asking for is a way to compare the values of x and y rounded to a certain number of significant digits (in base 10). The round method has a keyword sigdigits that might be of use:

    isapproxsigfigs(a, b, precision) = round(a, sigdigits=precision) == round(b, sigdigits=precision)
    
    isapproxsigfigs(10, 9.9, 1)  # true,  10 == 10
    isapproxsigfigs(10, 9.9, 2)  # false, 10 != 9.9
    
    isapproxsigfigs(11.4, 10.5, 1)  # true,  10 == 10
    isapproxsigfigs(11.4, 10.5, 2)  # false, 11 != 10 (remember RoundingMode!)
    
    isapproxsigfigs(11.4, 10.51, 1)  # true,  10 == 10
    isapproxsigfigs(11.4, 10.51, 2)  # true,  11 == 11
    isapproxsigfigs(11.4, 10.51, 3)  # false, 11.4 != 10.5
    

    For the second example, remember that 10.5 is only "almost 11" if you round ties up. The default RoundingMode used by Julia is RoundNearest, which rounds ties to even. If you want ties to round up, use RoundNearestTiesUp:

    isapproxsigfigs2(a, b, precision) = 
        round(a, RoundNearestTiesUp, sigdigits=precision) == 
        round(b, RoundNearestTiesUp, sigdigits=precision)
    
    isapproxsigfigs2(11.4, 10.5, 2)  # true, 11 == 11