Closed dmcclean closed 8 years ago
I changed the definition of log
from log (I a b) = (if a > 0 then log a else negInfinity) ... log b
to log (I a b) = (if a > 0 then log a else negInfinity) ... (if b > 0 then log b else negInfinity)
. This is more consistent because it treats intervals with two negative endpoints more like intervals with one negative endpoint.
Then I created a variant of conservative
that works when the scalar type is in RealFloat
, like this:
let conservativeExceptNaN sf f xs = forAll (choose (inf xs, sup xs)) $ \x -> isNaN (sf x) || (sf x) `elem` (f xs)
I switched the relevant tests from conservative
to conservativeExceptNaN
.
I feel like this is a decent solution from a test perspective, so the only remaining question is whether we are happy with the design that the interval version of a function f
with a limited domain, applied to an interval x
that includes values outside that domain, is the hull
of f
applied to the part of x
that is in the domain of f
with the nearest value to x
that is in the domain of f
.
Under this rule, acos (-3 ... -2)
is pi ... pi
, slightly different from the NaN ... pi
that we give now, but more consistent with the pi ... pi
that we now give for acos (-3 ... -1)
.
Our current definitions of some functions fail this test for values that are outside the domain. log (-2) is NaN, and log (-3 ... -1) is NaN ... -Infinity (which seems like a strange beast in its own right...), but NaN is not an element of NaN ... -Infinity because of the way the Ord Double instance works.
You make a pretty compelling argument there.
I think if we call a function that returns NaN, our current system has no real way it can provide an interval that would contain the result, so conservativeExceptNaN
is as close as we're going to get.
I feel like this is a decent solution from a test perspective, so the only remaining question is whether we are happy with the design that the interval version of a function f with a limited domain, applied to an interval x that includes values outside that domain, is the hull of f applied to the part of x that is in the domain of f with the nearest value to x that is in the domain of f.
How far reaching are the implications of this rule on the API?
The existing implementations of log
, acos
, asin
, acosh
, and atanh
all sometimes return intervals that have NaN
as an endpoint. Generally this happens when both endpoints of the argument are outside of the domain, they deal well with just one endpoint outside.
Applying the same
conservative :: (a -> a) -> (Interval a -> Interval a) -> Property
property that I wrote to test the trig functions, I found some other interesting counterexamples that raise a design question.I expected that the essence of the interval version of, say,
sin
, is that it's a conservative approximation of the scalar version. So if you pick a value in the intervalx
and evaluatesin
there, the result will be in the intervalsin x
. (This isn't the entirety of what we want, since we want the "best" such approximation, but it's a start.)Our current definitions of some functions fail this test for values that are outside the domain.
log (-2)
isNaN
, andlog (-3 ... -1)
isNaN ... -Infinity
(which seems like a strange beast in its own right...), butNaN
is not an element ofNaN ... -Infinity
because of the way theOrd Double
instance works.For the interval type where
Empty
is an option, it seems like a good one and the current definitions take it. IfNaN
could somehow be an element ofEmpty
that would be even better, but I'm not sure if there is a parametric way to do that. So all we would do would be to relax the definition of conservativeness that we have in our heads.For the non empty interval type it is a bit thornier. Returning
whole
instead ofEmpty
seems like an option. It's conservative, just not very. ;) Might be more user friendly than an exception, but then again maybe an exception is the right thing to do. Perhaps makingNaN
an element of the strange interval we return now would be OK, but with the same parametricity thorn. I'm not sure what otherFloating
instances might be lurking out there that don't haveNaN
ish things or where those definitions would leave them. Rounded? Fixed?