-
Notifications
You must be signed in to change notification settings - Fork 111
TM017 Numbers
This is a starting sketch of a proposal for two distinct number types in Pyret. It needs fleshing out, suggestions for names, and implementation planning. It can change a lot from this starting point.
Pyret should support two types of numbers, exact numbers (abbreviated to "numbers") and approximations. Exact numbers are positive and negative rational numbers of arbitrary precision. Approximations are inexact rounded-off representations of a number within some error (e.g. perhaps with the same rules as doubles).
These should be two distinct datatypes, with two distinct, non-overlapping annotations, predicates, and concrete syntax.
Number
is for exact numbers
Approx
is for approximations
is-number
and is-approx
are the two predicates.
The concrete syntax for an approximation is
~[-]?[0-9]+[[\.][0-9]+]
For example:
~1.1
~1
~1.0
~-1.2
There are two functions that convert between the two types:
number-to-approx
, which takes an exact number and returns the
closest approximation
to it, with some rule for ties (e.g. favoring the number further from 0)
approx-to-number
, which takes an approximation and returns the closest exact
number.
No built-in function should automatically cast between the two representations; the user should make an explicit choice to switch between them in order to fit a signature that requires one or the other.
This proposal uses "approximation" to avoid using "float" or "double" or other term with specific meaning, and because "inexact number" is a mouthful and hard to parse. There may be a better word, but "approximation" captures a lot of the essence of what a floating-point number is, and motivates why e.g. they can only be trusted to a certain point. Something shorter would be nice, suggestions welcome.
Numbers are exact representations of positive and negative rational numbers of
any size. They are the arguments to things like string-substring
,
string-repeat
, map-n
, build-array
, etc.
They are the best choice for things that are discrete -- grade calculations, animation that doesn't involve trigonometric functions, computing offsets and sums, etc.
Their representation is probably some kind of pair of bignums. JS doubles can represent integers from -9007199254740991 to 9007199254740991 exactly, which is an optimization option for a number of common cases.
It might be useful to have two additional values that are of type Number
-- a
Infinity
and a MinusInfinity
that are larger and smaller than all exact
numbers.
Approximations are probably best represented as doubles, though other representations are possible and worth considering. A few design decisions:
- Are there maximum and minimum approximation values (e.g. double has a finite range), or is it arbitrary-size with some design decisions about precision for numbers close to zero and above MAX_DOUBLE?
- If we want to use the exact JS integers as Numbers, how do we tell those
doubles apart from approximations that happen to land on the same value?
e.g. with simple representations,
number-to-approx(5)
might well return the same underlying representation, making dynamic signature checking hard without wrapping things.
JavaScript and DOM libraries take JavaScript numbers. If we continue to use e.g. Canvas for drawing images and animating, we will always need to confront conversions to JavaScript numbers. This probably means:
- Accepting inexactness when converting some exact rationals/integers into floating point approximations to pass them to JS APIs
- Putting reasonable bounds on the arguments we pass to built-in APIs. Maybe we have to decide that it doesn't make much sense to try and draw a circles larger than MAX_DOUBLE, for instance.
Both kinds of numbers, internally, should have a toJSNum operation that returns a JS double. Numbers that are in the range of a double get approximated if they are exact, and numbers that are beyond Number.MAX_VALUE may need to return +Infinity or -Infinity, with appropriate handling for those cases in the underlying library.
Dart has bignums in its server VM, but simply treats all numbers as doubles in Dart2JS (so the server and client have different semantics, e.g. http://stackoverflow.com/questions/17427976/bitwise-operations-wrong-result-in-dart2js/17434110#17434110). This is for obvious performance reasons that Dart chose to compromise on semantics for.
GopherJS has lots of conversions for different numeric types, but no bignum (https://github.com/gopherjs/gopherjs/blob/master/js/js.go#L5).
ClojureScript also kicks out to JS doubles, changing its representation from server to client.