Introducing BigDecimal within WarpScript

Warp 10 3.1.2 comes with BigDecimal support. Learn how to use these new functions!

Introducing BigDecimal within WarpScript

Warp 10 3.1.2 comes with BigDecimal support.

The purpose of this article is to introduce BigDecimal to familiarize yourself with this number representation and then to present its use in Warp 10.

BigDecimal, what is it?

Floating point representation has limits. The default floating point format used everywhere in Warp 10, is called DOUBLE (aka 64bit IEEE754). You can represent around 15 decimal digits exactly, no more, and an exponent. It is sufficient for most usage, but it is still a floating point in base 2. Every number that is not a power of 2 is represented with an error (1/2, 1/4, 1/8 is not a problem).

Here is the binary representation of some fractions:

x binary DOUBLE representation
1/1 0011111111110000000000000000000000000000000000000000000000000000
1/2 0011111111100000000000000000000000000000000000000000000000000000
1/3 0011111111010101010101010101010101010101010101010101010101010101
1/4 0011111111010000000000000000000000000000000000000000000000000000
1/5 0011111111001001100110011001100110011001100110011001100110011010
1/6 0011111111000101010101010101010101010101010101010101010101010101
1/7 0011111111000010010010010010010010010010010010010010010010010010
1/8 0011111111000000000000000000000000000000000000000000000000000000
1/9 0011111110111100011100011100011100011100011100011100011100011100
1/10 0011111110111001100110011001100110011001100110011001100110011010

In base 10, we know we have a problem with some rational numbers too in base 10. 1/3 cannot be exactly expressed in base 10, as you have an infinite number of "3". But if you look at the previous result, you see that 1/10 is a problem for a computer floating points representation. 1/10 is rounded by your CPU and FPU (the FPU is designed to help the CPU process floating points faster).

That is why you can have surprising results from very basic operations:

0.3 0.2 - // 0.1 obviously 0.2 0.1 - // 0.1 obviously ==

Both equals 0.1 for you. Not for a computer. This is why the "epsilon equal" operator exists (In WarpScript, see ~=). One of the most obvious operations in the decimal system is to divide by 10: As 10 is not a 2 power, try 0.2 0.1 + 10.0 /… It does not equal 0.03! And errors can cumulate.

There is a field where everything is expressed in base 10, where you do not tolerate such errors: finance. BigDecimal solves the financial calculation problem. It forces your computer to represent numbers in decimal: 12345.67 will be stored in memory as:

  • An array of digits: [ 1 2 3 4 5 6 7 ]
  • A scale (number of digits in the fraction): 2

BigDecimal comes with a cost: it will always be slower than a floating point operation. Operations between numbers with a different scale are even slower.

For interoperability with other languages, the safest compromise is to use the string representation. The fastest JSON parser does not support BigDecimal.

BigDecimal within Warp 10

Warp 10 3.1.2 introduces a new set of functions for BigDecimal operations:

Basics

First thing first, ->BD convert a STRING, a DOUBLE, or a LONG to BigDecimal. TYPEOF will return "BIGDECIMAL". All the BigDecimal operations do an implicit ->BD call on their parameters. So, the explicit conversion to BigDecimal is not mandatory:

0.3 ->BD 0.2 ->BD BDSUB // 0.1 obviously 0.2 ->BD 0.1 ->BD BDSUB // 0.1 obviously == 0.3 0.2 BDSUB // 0.1 obviously 0.2 0.1 BDSUB // 0.1 obviously == 0.3 0.2 BDSUB TYPEOF

The BD-> forces the conversion to a string. As previously explained, some JSON parsing libs do not support BigDecimal. In the following example, select "raw json" output after execution:

// impossible with double floating point, will return 1e20 1000000000000001.0 3 ** // possible with BigDecimal, but JSON output may not be correctly parsed "1000000000000001" 3 BDPOW // possible with BigDecimal, force the string output "1000000000000001" 3 BDPOW BD->

Arithmetic

Warp 10 arithmetic's operators cannot be used safely on BigDecimal (it will lead to unpredictable results), so you have to use these set of functions:

operator BigDecimal operator Function
+ BDADD
- BDSUB
* BDMUL
/ BDDIV See below
/ BDDIVINT See below
% BDREM
** BDPOW Raise to power
ABS BDABS Absolute value
MIN BDMIN Minimum
MAX BDMAX Maximum
BDNEG fast negation

The division has two functions. Remember that BigDecimal cannot represent fractional numbers with infinite digits, such as 1/3. So, 1 3 BDDIV will throw this error: Non-terminating decimal expansion; no exact representable decimal result. BDDIVINT will return the integer part of the result, rounded down, just like the standard division operator applied on LONG numbers.

Comparisons

Warp 10 comparison operators will work out of the box on BigDecimal. You can safely use >, >=, <, <=, ==, !=. However, the standard operators do not do an implicit ->BD when needed, and are slower than BDCOMP.

// impossible with floating point format "100000000000000000" TODOUBLE "100000000000000001" TODOUBLE < // false!!! // using BigDecimal and standard operator "100000000000000000" ->BD "100000000000000001" ->BD < // true // using BDCOMP "100000000000000000" "100000000000000001" BDCOMP // returns -1

Scale related operations

When casting from a string, scaling is obvious: counting the trailing zeroes defines the scale. But when casting from a DOUBLE, scaling is guessed. Remember that 0.1 is actually equal to 0.1000000000000000055511151231257(…) for your computer. ->BD conversion does its best, but the scale can be surprising.

So if you want to be sure to work with scale 2, as in the example below, you need to change the scale. SCALE function will return or set the scale, depending on its arguments.

"2.00" ->BD BDSCALE // 2 2 ->BD BDSCALE // 0 2.00 ->BD BDSCALE // 1 guessed ! // change scale: 2.00 ->BD 2 BDSCALE // force scale to 2 BDSCALE // returns 2

You may also want to get rid of the trailing zeroes: BDSTRIP will optimize the scale for you. But beware, BDSTRIP will also optimize to a negative scale (for example, 320 could be represented with [ 3 2 ] and -1 scale.

The last function, BDULP, returns the minimum unit that can be added without changing scale. If scale is 2, it will return 0.01. If scale is -1, it will return 10.

Conclusion

So you may never use them for your business, but Warp 10 is designed as a horizontal solution that covers basic monitoring to advanced industrial datasets. Finance is an industry too! WarpScript now provides a way to handle BigDecimal easily!

If you want to go further to understand floating point limits, you can read the dedicated Wikipedia page.

A question about BigDecimal? Feel free to ask us on the Lounge, the Warp 10 community on Slack.