TM016 Testing Inexact Numbers - brownplt/pyret-lang GitHub Wiki
Testing Inexact Numbers
Problem
Comparing inexact numbers is a source of bugs and is difficult to test.
Pyret's is%(pred)
syntax provides a good option for specifying ranges of
comparison when numbers are being compared directly; for example:
check:
within = lam(delta): lam(n1, n2): num-abs(n1 - n2) < delta end end
num-sin(1) is%(within(0.01)) 0.84
end
However, when scaling up to lists and structures that contain inexact numbers, this quickly becomes difficult:
check:
within = lam(delta): lam(n1, n2): num-abs(n1 - n2) < delta end end
fun within-list(delta):
lam(l1, l2): lists.all2(within(delta), l1, l2) end
end
sins = map(num-sin, [list: 1, 2, 3, 4])
sins is%(within-list(0.01)) [list: 0.84, 0.90, 0.14, -0.75]
end
For more and more complicated structures, the tester has to essentially
reproduce the equality traversal for the structure in question, but replace all
inexact comparisons with within
rather than equality checks.
Solution
The proposed solution is twofold (and either part may be valuable on its own).
Make Inexact Numbers Incomparable
Pyret already has a notion of incomparable values, for which it refuses to
report true
or false
in equality: functions and methods. If we add inexact
numbers to the set of incomparable values, we could make comparisons on
inexacts return Unknown
.
We could even extend the notion of error in this case, and have any comparison
of inexact numbers to exact numbers return Unknown
as well (and raise the
corresponding exception).
within
Function
Add a Built-in Further, Pyret could get several new functions:
within-always :: Number -> (Any, Any -> Boolean)
within-always3 :: Number -> (Any, Any -> EqualityResult)
within-now :: Number -> (Any, Any -> Boolean)
within-now3 :: Number -> (Any, Any -> EqualityResult)
These two functions would take numbers (either inexact or exact), and would
produce functions with the same behavior as equal-always
and equal-now
,
with the exception that instead of raising an error/returning Unknown
on
inexact numbers, the two numbers would be compared for equality within the
tolerance of the first Number
argument.
This would re-use existing datatypes' _equals
methods to do the traversal of
members for e.g. user-defined collections, but parameterize the entire equality
process over the user's choice of tolerance.
Datatypes that manage their own internal inexact values can always compare them
with a more rigid or lenient tolerance than the one specified by the user of
within-*
, if that tolerance is part of the datatype's specification.
However, many datatypes (especially collections) would benefit from being
agnostic to this behavior and supporting its parameterization.