Search code examples
rrounding

test two numbers equal to the lowest precision


Given two arrays of numbers, I wish to test whether pairs of numbers are equal to the precision of the least precise of each pair of numbers.

This problem originates from validating the reproduction of presented numbers. I have been given a set of (rounded) numbers, an attempt to replicate them has produced more precise numbers. I need to report whether the less precise numbers are rounded versions of the more precise numbers.

For example, the following pair of vectors should all return true

input_a = c(0.01, 2.2, 3.33, 44.4, 560, 700) # less precise values provided
input_b = c(0.011, 2.22, 3.333, 44.4000004, 555, 660) # more precise replication

because when rounded to the lowest pair-wise precision the two vectors are equal:

pair_wise_precision = c(2, 1, 2, 1, -1, -2)

input_a_rounded = rep(NA, 6)
input_b_rounded = rep(NA, 6)

for(ii in 1:6){
  input_a_rounded[ii] = round(input_a[ii], pair_wise_precision[ii])
  input_b_rounded[ii] = round(input_b[ii], pair_wise_precision[ii])
}

all(input_a_rounded == input_b_rounded)
# TRUE
# ignoring machine precision

However, I need to do this without knowing the pair-wise precision.

Two approaches I have identified:

  1. Test a range of rounding and accept the two values are equal if any level of rounding returns a match
  2. Pre-calculate the precision of each input

However, both of these approaches feel cumbersome. I have seen in another language the option to round one number of match the precision of another number (sorry, can't recall which). But I can not find this functionality in R.

(This is not a problem about floating point numbers or inaccuracy due to machine precision. I am comfortable handling these separately.)

Edit in response to comments:

  • We can assume zeros are not significant figures. So, 1200 is considered rounded to the nearest 100, 530 is rounded to the nearest 10, and 0.076 is rounded to the nearest thousandth.
  • We stop at the precision of the least precise value. So, if comparing 12300 and 12340 the least precise value is rounded to the nearest 100, hence we compare round(12300, -2) and round(12340, -2). If comparing 530 and 570, then the least precise value is rounded to the nearest 10, hence we compare round(530, -1) and round(570, -1).

Solution

  • My initial thinking followed @jay.sf's approach to analyse values as numeric. However, considering the values as character provides another way to determine rounding:

    was_rounded_to = function(x){
      x = as.character(x)
      location_of_dot = as.numeric(regexpr("\\.", x))
      ref_point = ifelse(location_of_dot < 0, nchar(x), location_of_dot)
      last_non_zero = sapply(gregexpr("[1-9]", x), max)
      
      return(last_non_zero - ref_point)
    }
    
    # slight expansion in test cases
    a <- c(0.01, 2.2, 3.33, 44.4, 555, 700, 530, 1110, 440, 3330)
    b <- c(0.011, 2.22, 3.333, 44.4000004, 560, 660, 570, 1120, 4400, 3300)
    
    rounding = pmin(was_rounded_to(a), was_rounded_to(b))
    
    mapply(round, a, digits = rounding) == mapply(round, b, digits = rounding)
    

    Special case: If the numbers only differ by rounding, then it is easier to determine the magnitude by examining the difference:

    a <- c(0.01, 2.2, 3.33, 44.4, 555, 700)
    b <- c(0.011, 2.22, 3.333, 44.4000004, 560, 660)
    
    abs_diff = abs(a-b)
    mag = -floor(log10(abs_diff ) + 1e-15)
    mapply(round, a, digits = mag - 1) == mapply(round, b, digits = mag - 1)
    

    However, this fails when the numbers differ by more than rounding. For example: a = 530 and b = 540 will incorrectly round both 530 and 540 to 500.