A previous fix (issue #6 in the scales package, commit e130daa), changed
the algorithm from:
if (length(x) == 1) return(TRUE)
x <- x / mean(x)
isTRUE(all.equal(x[1], x[2]))
to:
length(x) == 1 || isTRUE(all.equal(x[1] - x[2], 0))
Presumably to deal with "large numbers with small differences" (based on
the added tests).
The use of all.equal is to say "equal to within what is probably just
differences due to floating point representation" (my paraphrase). From
the actual documentation of all.equal:
Numerical comparisons for scale = NULL (the default) are done by first
computing the mean absolute difference of the two numerical vectors. If
this is smaller than tolerance or not finite, absolute differences are
used, otherwise relative differences scaled by the mean absolute
difference.
By default, tolerance is .Machine$double.eps ^ 0.5 (which is
1.490116e-08 on my machine).
The old way only looked a relative difference, the new way looks at
absolute or relative difference (though reading the documentation of
all.equal, I'm still not sure I understand when it does which).
Ultimately, we need more tests cases. Pulling from the existing tests,
the two StackOverflow questions, and some others, I think these are right:
expect_false(zero_range(c(5.63e-147, 5.93e-123)))
expect_false(zero_range(c(-7.254574e-11, 6.035387e-11)))
expect_false(zero_range(c(1330020857.8787, 1330020866.8787)))
expect_true(zero_range(c(1330020857.8787, 1330020857.8787)))
expect_true(zero_range(c(1330020857.8787, 1330020857.8787*(1+1e-20))))
expect_false(zero_range(c(0,10)))
expect_true(zero_range(c(0,0)))
expect_false(zero_range(c(-1,1)))
expect_false(zero_range(c(-1,1*(1+1e-20))))
expect_false(zero_range(c(-1,1)*1e-100))
expect_false(zero_range(c(0,1)*1e-100))
expect_true(zero_range(c(1)))
expect_true(zero_range(c(0)))
expect_true(zero_range(c(1e100)))
expect_true(zero_range(c(1e-100)))
If so, here is a function which meets that criteria:
zero_range <- function(x) {
m <- mean(x)
if(m == 0L) {
if (x[[1]] == 0L) {
m <- 1
} else {
m <- x[[1]]
}
}
length(x) == 1 || isTRUE(all.equal(diff(x/m), 0,
tolerance=.Machine$double.eps))
}
First, I change the tolerance to .Machine$double.eps, not the square
root of it. Second, I rescale the vector based on the mean value, after
checking that it is not exactly 0L to take care of absolute size
differences. If the mean is exactly 0L, then rescale by the first value
(if it is not 0L); if both the mean and first value are exactly 0L,
don't rescale (since all are zero).
Any other tests to throw at this? Can anyone break it? (Other than with
length(x) > 2, but that is part of the documentation).
--
Brian S. Diggs, PhD
Senior Research Associate, Department of Surgery
Oregon Health & Science University