Skip to content

128 Bit Decimal Floating-Point Support

Introduction

The original IEE-754 64-bit binary floating point (FP) data type (also known as type number 645), that is used internally by Dyalog APL to represent floating-point values, does not have sufficient precision for certain financial computations – typically involving large currency amounts. The binary representation also causes errors to accumulate even when all values involved in a calculation are "exact" (rounded) decimal numbers, since many decimal numbers cannot be accurately represented regardless of the precision used to hold them. To reduce this problem, Dyalog APL includes support for the 128-bit decimal data type described by IEEE-754-2008 as an alternative representation for floating-point values.

System Variable: Floating-point Representation

Computations using 128-bit decimal numbers require twice as much space for storage, and run more than an order of magnitude more slowly on platforms which do not provide hardware support for the type. At this time, hardware support is only available from IBM (POWER 6 chips onwards, and recent System z mainframes). Even with hardware support, a slowdown of a factor of 4 can be expected. For this reason, Dyalog allows users to decide whether they need the higher-precision decimal representation, or prefer to stay with the faster and smaller binary representation.

The system variable ⎕FR (for Floating-point Representation) can be set to the value 645 (the installed default) to indicate 64-bit binary FP, or 1287 for 128-bit decimal FP. The default value of ⎕FR is configurable.

Simply put, the value of ⎕FR decides the type of the result of any floating-point calculation that APL performs. In other words, when entered into the session:

     ⎕FR = ⎕DR 1.234 ⍝ Type of a floating-point constant
     ⎕FR = ⎕DR 3÷4   ⍝ Type of any floating-point result

⎕FR has workspace scope, and may be localised. If so, like most other system variables, it inherits its initial value from the global environment.

However: Although ⎕FR can vary, the system is not designed to allow "seamless" modification during the running of an application and the dynamic alteration of ⎕FR is not recommended. Strange effects may occur. For example, the type of a constant contained in a line of code (in a function or class), will depend on the value of ⎕FR when the function is fixed. Similarly, a constant typed into a line in the Session is evaluated using the value of ⎕FR that pertained before the line is executed. Thus, it would be possible for the first line of code above to return 0, if it is in the body of a function. If the function was edited and while suspended and execution is resumed, the result would become 1. Also note:

      ⎕FR1287
      x1÷3
 
      ⎕FR645
      x=1÷3
1

The decimal number has 17 more 3s. Using the tolerance which applies to binary floats (type 645), the numbers are equal. However, the "reverse" experiment yields 0, as tolerance is much narrower in the 128-bit universe:

      ⎕FR645
      x1÷3
 
      ⎕FR1287
      x=1÷3
0

Since ⎕FR can vary, it will be possible for a single workspace to contain floating-point values of both types (existing variables are not converted when ⎕FR is changed). For example, an array that has just been brought into the workspace from external storage may have a different type from ⎕FR in the current namespace. Conversion (if necessary) will only take place when a new floating-point array is generated as the result of "a calculation". The result of a computation returning a floating-point result will not depend on the type of the arrays involved in the expression: ⎕FR at the time when a computation is performed decides the result type, alone.

Structural functions generally do NOT change the type, for example:

      ⎕FR1287
      x1.1 2.2 3.3
      
      ⎕FR645
      ⎕dr x
1287
      ⎕dr 2x
1287

128-bit decimal numbers not only have greater precision (roughly 34 decimal digits); they also have significantly larger range- from ¯1E6145 to 1E6145. Loss of precision is accepted on conversion from 645 to 1287, but the magnitude of a number may make the conversion impossible, in which case a DOMAIN ERROR is issued:

      ⎕FR1287
      x1E1000
      
      ⎕FR645
      x+0
DOMAIN ERROR

WARNING: The use of COMPLEX numbers when ⎕FR is 1287 is not recommended, because:

  • any 128-bit decimal array into which a complex number is inserted or appended will be forced in its entirety into complex representation, potentially losing precision
  • all comparisons are done using ⎕DCT when ⎕FR is 1287, and this is equivalent to 0 for complex numbers.

Conversion between Decimal and Binary

Conversion of data from Binary to Decimal is logically equivalent to formatting, and the reverse conversion is equivalent to evaluating input. These operations are performed according to the same rules that are used when formatting (and evaluating) numbers with ⎕PP set to 17 (guaranteeing that the decimal value can be converted back to the same binary bit pattern). Because the precision of decimal floating-point numbers is much higher, there will always be a large number of potential decimal values which map to the same binary number: As with formatting, the rule is that the SHORTEST decimal number which maps to a particular binary value will be used as its decimal representation.

Data in component files will be stored without conversion, and only converted when a computation happens. It should be stored in decimal form if it will repeatedly be used by application code in which ⎕FR has the value 1287. Even in applications which use decimal floating point everywhere, reading old component files containing arrays of type 645, or receiving data via ⎕NA, the .NET interface or other external sources, will allow binary floating-point values to enter the system and require conversion.

Decimal Comparison Tolerance

When ⎕FR has the value 1287, the system variable ⎕DCT will be used to specify comparison tolerance. The default value of ⎕DCT is 1E¯28, and the maximum value is 2.3283064365386962890625E¯10 (the value is chosen to avoid fuzzy comparison of 32-bit integers).

Name Association and Floating-point Values

⎕NA supports the data type "D" to represent the Densely Packed Decimal (DPD) form of 128-bit decimal numbers, as specified by the IEEE-754 2008 standard. Dyalog has decided to use DPD, which is the format used by IBM for hardware support, on ALL platforms, although "Binary Integer Decimal" (BID) is the format that Intel libraries use to implement software libraries to do decimal arithmetic. Experiments have shown that the performance of 128-bit DPD and BID libraries are very similar on Intel platforms. In order to avoid the added complication of having two internal representations, Dyalog has elected to go with the hardware format, which is expected to be adopted by future hardware implementations.

The support libraries for writing APs and DLLs include new functions to extract the contents of a value of type D as a string or double-precision binary "float" – and convert data to D format.

Decimal Floats and Microsoft.NET

The Microsoft.NET framework contains a type named System.Decimal, which implements decimal floating-point numbers. However, it uses a different internal format from that defined by IEEE-754 2008.

Dyalog APL includes a Microsoft.NET class (called Dyalog.Dec128), which will perform arithmetic on data represented using the "Binary Integer Decimal" format. All computations performed by the Dyalog.Dec128 class will produce exactly the same results as if the computation was performed in APL. A "DCT" property allows setting the comparison tolerance to be used in comparisons, Ceiling/Floor, etc.).

The Dyalog class is modelled closely after the existing System.Decimal type, providing the same methods (Add, Ceiling, Compare, CompareTo, Divide, Equals, Finalize, Floor, FromOACurrency, GetBits, GetHashCode, GetType, GetTypeCode, MemberwiseClone, Multiply, Negate, Parse, Remainder, Round, Subtract, To*, Truncate, TryParse) and operators (Addition, Decrement, Division, Equality, Explicit, GreaterThan, GreaterThanOrEqual, Implicit, Increment, Inequality, LessThan, LessThanOrEqual, Modulus, Multiply, Subtraction, UnaryNegation, UnaryPlus).

The "bridge" between Dyalog and .NET is able to cast floating-point numbers to or from System.Double, System.Decimal and Dyalog.Dec128 (and perform all other reasonable casts to integer types etc.). Casting a Dyalog.Dec128 to or from strings will perform a "lossless" conversion.

Incoming .NET data types VT_DECIMAL (96-bit integer) and VT_CY (currency value represented by a 64-bit two's complement integer, scaled by 10,000) are converted to 126-bit decimal numbers (DECFs). This conversion is performed independently of the value of ⎕FR.

If you want to perform arithmetic on values imported in this way, then you should set ⎕FR to 1287, at least for the duration of the calculations.

Note that the .NET interface converts System.Decimal to DECFs but does not convert System.Int64 to DECFs.