Increase precision of math operations by using BigDecimal
Description
This ticket introduces BigDecimal usage all over BoxLang for two main use cases:
Being able to store and process very large numbers that wouldn't fit inside a Long or a Double like 111111111111111111111111111 + 222222222222222222222222222
Being able to retain precision for decimals-- even small ones. For ex: (0.1 + 0.2).toString() outputs 0.30000000000000004 in Lucee 5 and ACF 2023. But in Lucee 6 and BoxLang, it correctly returns .3 without any special work
So here is what has changed. Not ALL numbers are a BigDecimal-- just the ones that need to be.
integer literals in your source code less than 11 chars will be a java Integer
integer literals in your source code less than 20 chars will be a java Long
All other integer literals in your source will be a java BigDecimal
All decimal literals in your source will be a java BigDecimal
When casting object to numbers internally, we will employ the same basic logic:
any recognizable classes like int, long, double, big decimal, etc are just returned directly
Any strings which contain integers (no decimal or sci notation) follow the same rules above (integer for small ones, long for bigger ones, big decimal for really big ones)
Any strings with a decimal or sci notation will be made a BigDecimal
Basically, we return the "Smallest" data type we can without losing any precision. (An Integer is about 24 Bytes and a BigDecimal is about 64 Bytes so it seemed worth optimizing a bit)
Any BIF/UDF arg or component attribute using numeric or number as its type.
This means all BIFs using numeric can seamlessly handle huge numbers and high precision out of the box!
The last thing I did was modify ALL language numeric operators and numeric BIFs to add special handling for BigDecimals, if they are passed in, but still reserving a simpler, faster code path for smaller numbers so we can keep stuff like 1 + 1 nice and fast with little overhead. Operations like Plus are also smart enough to detect that 1+1 can store its result in an integer but larger numbers need to "upgrade" their type so if you cross the threshold you seamlessly get the next "biggest" data type back. BoxLang code doesn’t not need to do ANYTHING special-- all math operations should “just work”. Only the internals of BoxLang needs to care about handing BigDecimals.
And on a more technical note, we're using the DECIMAL128 math context by default which follows the precision of the IEEE 754-2019 decimal128 format, 34 digits, and a rounding mode of HALF_EVEN. People can modify the default BigDecimal handling in BoxLang by importing the MathUtil class and calling its methods. The change will apply to math operations right away.
Activity
Show:
John Whish August 9, 2024 at 6:52 AM
Great stuff! Thanks :)
Brad Wood August 8, 2024 at 5:47 PM
I’ve updated this to detail the changes I made that fix your other ticket and allow large numbers and high precision in general.
Fixed
Pinned fields
Click on the next to a field label to start pinning.
This ticket introduces
BigDecimal
usage all over BoxLang for two main use cases:Being able to store and process very large numbers that wouldn't fit inside a
Long
or aDouble
like111111111111111111111111111 + 222222222222222222222222222
Being able to retain precision for decimals-- even small ones. For ex:
(0.1 + 0.2).toString()
outputs0.30000000000000004
in Lucee 5 and ACF 2023. But in Lucee 6 and BoxLang, it correctly returns.3
without any special workSo here is what has changed. Not ALL numbers are a
BigDecimal
-- just the ones that need to be.integer literals in your source code less than 11 chars will be a java
Integer
integer literals in your source code less than 20 chars will be a java
Long
All other integer literals in your source will be a java
BigDecimal
All decimal literals in your source will be a java
BigDecimal
When casting object to numbers internally, we will employ the same basic logic:
any recognizable classes like int, long, double, big decimal, etc are just returned directly
Any strings which contain integers (no decimal or sci notation) follow the same rules above (integer for small ones, long for bigger ones, big decimal for really big ones)
Any strings with a decimal or sci notation will be made a BigDecimal
Basically, we return the "Smallest" data type we can without losing any precision. (An Integer is about 24 Bytes and a BigDecimal is about 64 Bytes so it seemed worth optimizing a bit)
Any BIF/UDF arg or component attribute using
numeric
ornumber
as its type.This means all BIFs using
numeric
can seamlessly handle huge numbers and high precision out of the box!The last thing I did was modify ALL language numeric operators and numeric BIFs to add special handling for BigDecimals, if they are passed in, but still reserving a simpler, faster code path for smaller numbers so we can keep stuff like
1 + 1
nice and fast with little overhead. Operations likePlus
are also smart enough to detect that1+1
can store its result in an integer but larger numbers need to "upgrade" their type so if you cross the threshold you seamlessly get the next "biggest" data type back. BoxLang code doesn’t not need to do ANYTHING special-- all math operations should “just work”. Only the internals of BoxLang needs to care about handing BigDecimals.And on a more technical note, we're using the
DECIMAL128
math context by default which follows the precision of the IEEE 754-2019 decimal128 format, 34 digits, and a rounding mode of HALF_EVEN. People can modify the default BigDecimal handling in BoxLang by importing theMathUtil
class and calling its methods. The change will apply to math operations right away.