Introduction and Motivation
From the LogicBlox 3.x series to 4.0, we’re going to encourage migrating applications from using floating-point binary numbers to fixed-precision decimal numbers. We propose introducing a new unambiguous syntax for decimal and float literals on 3.10 and 4.0; moreover, we will parse literals of the form ‘2.71’ as decimals on 4.0, rather than as floats as on 3.x.
3.10 and 4.0
Semantics and Examples
Currently in 3.10, float literals have the form ‘2.71’, but there is no literal syntax for decimals. Our proposal consists of two parts:
1. Adding unambiguous syntax for floats and decimals literals on 3.10 and 4.0:
We’re going to add two new syntactic forms with a suffix ‘d’ or ‘f’ to distinguish the type: ‘2.71d’ for decimal and ‘2.71f’ for float.
2. Reinterpreting numeric literals without postfix as decimal literals to encourage the use of decimals. For the existing literal syntax:
On 3.10, we’ll continue to parse ‘2.71’ as a float.
On 4.0, we’ll parse ‘2.71’ as a decimal instead of a float. We don’t plan on supporting the exponent notation ‘2.71E8’ for decimals.
Decimal numbers have better behavior which leads to improved accuracy, efficiency, and safety over floats. Specifically,
- Decimal addition is associative and thus enables total aggregations to be done much more efficiently and accurately. For comparison, floating-point addition is not associative, which makes aggregations require expensive data structures and still yields inexact answers.
- Decimal arithmetic checks for overflow and aborts a transaction, as opposed to silent floating-point overflows that can be hard to detect.
More details can be found in Todd Veldhuizen’s proposal for fixed-point decimal types.
This syntax change will break any existing code that uses float literals with predicates that are explicitly typed as floats. For instance, ‘float:add[2.71, 3.14]’ will be flagged as a compile-time type error. Similarly, ‘float_type_predicate(2.71)’ will similarly be a compile-time error, assuming ‘float_type_predicate’ has the type ‘float’.
There are two straightforward paths for migration. Our recommended migration path is to change the *code*: an existing built-in operation such as ‘float:add[2.71, 3.14]’ should be replaced by the polymorphic built-in operation ‘2.71 + 3.14’, or ‘add[2.71, 3.14]’; a user-defined predicate ‘float_type_predicate(x) -> float(x)’ that takes a float should instead take a decimal ‘float_type_predicate(x) -> decimal(x)’. Alternatively, if you prefer to keep on using floats, you can use the new unambiguous syntax ‘2.71f’ for float literals.
Maybe emphasize that decimal is a fixed-point type.
So its characteristics for accuracy are different than for floating-point types.
Each decimal numbers has exactly 5 decimal digits right of the decimal point and 13 digits left of the decimal point. Operations will be rounded if there are more than 5 decimal digits. For example: 0.002 * 0.002 = 0.0
This is very different to floating-point types, in which the position of the decimal point is floating.
My primary concern is likelihood of overflow. For a fun example of a number that this type can’t represent see: http://www.usdebtclock.org. Can we survey our existing apps to understand the importance of representing numbers greater than 9,999,999,999,999.99999 and numbers less than 0.00001?
I assume that if we overflow we report and error and abort a transaction (i.e. we don’t overflow silently). Is that right?
As far as I understand the semantics of decimal numbers, overflow will cause the transaction to be aborted. I’m not sure what kind of error reporting is produced. We wouldn’t want the operation to retry indefinitely, but that would depend on the application driving transactions.
We definitely should check that the current precision of 5 digits to the right and 13 digits to the left is enough for existing applications. I expect that in principle the runtime can support wider precision if that isn’t enough.