Open claudeha opened 11 months ago
Problem is in (*)
or one of its dependencies
let a = compensated (compensated (-9.231236890307623e12::Double) 6.897617722007686e-4) (compensated (-2.6232210439034232e-20) (-9.294634128446337e-37)) ; b = compensated (compensated (-9.060527246314555e-31) 4.540998182136166e-47) (compensated (-3.1624962852246887e-63) (3.469844214603107e-80 :: Double)) ; rnd = toRational (a * b) ; ext = toRational a* toRational b in logBase 2 (fromRational $ abs (rnd - ext) / abs ext)
Prints -108.26948516958842
instead of about -215
. Test case found by QuickCheck. (+)
passed tests.
Problem is in times
or one of its dependencies (but split
passes rudimentary tests).
main = do
let a,b :: Compensated Double
a = compensated (-211.11871471905192) 8.926709967512482e-15
b = compensated 3.9220612083458963e-19 (-1.80497973320049e-35)
rounded = toRational (times a b compensated)
exact = toRational a * toRational b
err = logBase 2 . fromRational $ abs (rounded - exact) / abs exact
print err
Prints -109 instead of ~-215
Making split
be based on with
fixes the last failure case (those numbers now print -Infinity), but there are still failures in times
:
=== prop_times from tests/quickcheck.hs:80 ===
*** Failed! Falsified (after 158 tests):
compensated 0.13127052166409675 (-5.807852802487686e-20)
compensated (-2.730255721631754e-2) (-9.105471357507769e-19)
I think stacking compensation is ill-founded because add
can give results that differ greatly in magnitude, and then times
of such compensateds gives results with too many significant bits to fit into just two parts: truncating the least significant bits of the most significant half matches the behavior I saw initially..
Roughly speaking: do we need to have a sortah vector rep then? I actually started some experiments Towards that for some of my own stuff?
I don't know. The result of times
needs to be exact afaict, otherwise stacked (*)
breaks...
In the worst case I think that means times (compensated a b) (compensated c d)
needs 8 limbs (instead of just four with compensated (compensated ...)
) to store the results (if a,b,c,d
are all widely spaced in magnitude, then a*c,a*d,b*c,b*d
might not overlap, and they need to be two limbs each to be exactly representable...).
An alternative idea I haven't tried is to truncate or round the least significant half, so that the total precision is fixed - the cost may be prohibitive though. Something like this perhaps?
normalizeAfterAdd hi lo f = f hi lo'
where
p = copysign ((nextafter hi (2*hi) - hi) * 0.5) lo
lo' = (lo + p) - p
This would bring it in line with RealFloat
, too.
I'm not sure where the problem lies, but I initially puzzled over incorrect rendering in a port to another language, before testing the upstream (this package).
Tested with a small Mandelbrot set renderer. Until zoom depth 1e30 or so images look fine, but then they drift to feasible-looking images centred at the wrong point (eventually becoming completely wrong):
Incorrect image using
Compensated (Compensated Double)
:Correct image rendered using
Numeric.QD.QuadDouble
:source code for test program: