-
Notifications
You must be signed in to change notification settings - Fork 0
big integer and big decimal datatype
Red
support integer!
and float!
datatype for basic numerical calculation.
The integer!
's precision range is [- 2^31 ~ 2^31 - 1] or [-2147483648 ~ 2147483647], so it has 9-10 digits precision.
The float!
like other language's double
datatype, it has 52 bit length for precision, 2^52 = 4503599627370496, so it has 15-16 digits precision. The float!
's precision is high, however, it has restrictions. For example, if we translate 0.1 to float!
, the actual value in memory is 0x3FB999999999999A, it mast accurate represents 1.00000000000000005551115123126E-1.
In some cases, this situation is not allowed, for example, in money apps. So if we want a high precision and not lost digits, we should support base10 datatype.
Most languages provide big integer and big decimal datatypes (or libs). For example, in java
, there are Biginteger
and Bigdecimal
to support large precision integer and float. In python
, the default integer is biginteger type, and there also exists Decimal
to support bigdecimal.
So, we implement bigint!
for big integer, and bigdecimal!
for big float (in base10)
bigint!: alias struct! [
size [integer!] ;-- size in integer!
used [integer!] ;-- used length in integer!
expo [integer!]
prec [integer!]
]
-
size
: for unit buffer size. After fieldprec
, there continues unit buffer. -
used
: the actual datas used length, if it's negative, the bigint! is a negative value. -
expo
: for bigdecimal! -
prec
: for bigdecimal!, and can be used to distinguish with bigdecimal!
| size | used | expo | prec | unit 1 | unit 2 | ... | unit `used` | ... | unit `size` |
For example, "0" will be saved in memory like this:
| 1 | 1 | 0 | 0 | 0 |
"-1" will be saved in memory like this:
| 1 | -1 | 0 | 0 | 1 |
"0x1122334455667788" will be saved in memory like this:
| 2 | 2 | 0 | 0 | 0x55667788 | 0x11223344 |
"-0x1122334455667788" will be saved in memory like this:
| 2 | -2 | 0 | 0 | 0x55667788 | 0x11223344 |
Assumed there exist big1 and big2 for bigint!
- first set carry flag = 0, position = unit 1
- add big1's unit and big2's unit and carry flag (if position overflowed, then break)
- if it overflowed, set carry flag = 1, position + 1, then loop 2
- first set carry flag = 0, position = unit 1
- sub big1's unit and big2's unit and carry flag (if position overflowed, then break)
- if it overflowed, set carry flag = 1, position + 1, then loop 2
It's a little complicated to explain this algorithm.
- get a unit from big2 data buffer tail (if no data, then break)
- use this unit mul big1's every data unit, and add result's buffer data
- if overflowed, process carry flag
- move result's buffer back one position, continue loop 1
It's very complicated to explain this, so i put it's origin web sites here HAC 14.20.
It's like integer! shift, just bit shift.
- We also provide
compare/zero?*/negative?*
and so on functions, these also very useful. -
modulo
is related to rounding mode(it will be explained below). So we implement 10 modes. -
load-str
can get bigint! from string format. For example, base16 "11AA22CC" and base10 "296362700" will produce same bigint! -
form
will translate bigint! to ascii string, then this string can be print out.
#enum ROUNDING! [
ROUND-UP ;Rounds away from zero
ROUND-DOWN ;Rounds towards zero
ROUND-CEIL ;Rounds towards Infinity
ROUND-FLOOR ;Rounds towards -Infinity
ROUND-HALF-UP ;Rounds towards nearest neighbour. If equidistant, rounds away from zero
ROUND-HALF-DOWN ;Rounds towards nearest neighbour. If equidistant, rounds towards zero
ROUND-HALF-EVEN ;Rounds towards nearest neighbour. If equidistant, rounds towards even neighbour
ROUND-HALF-ODD ;Rounds towards nearest neighbour. If equidistant, rounds towards odd neighbour
ROUND-HALF-CEIL ;Rounds towards nearest neighbour. If equidistant, rounds towards Infinity
ROUND-HALF-FLOOR ;Rounds towards nearest neighbour. If equidistant, rounds towards -Infinity
]
In bigint!
section, we saw expo
and prec
for bigdecimal!
. There is a main reason to explain.
bigdecimal!
also can be defined as single file not depended on bigint!
, but we will write many similar codes. To avoid this, prec
can be used to distinguish bigint!
and bigdecimal!
.
For example, add
or sub
depend on negative sign to call absolute-add
or absolute-sub
. So, we can define add/sub help function for decimal, then use prec
to decide which one to be choosed.
In bigdecimal!
module, digits add
can be defined like:
add-raw: func [
big1 [bigdecimal!]
big2 [bigdecimal!]
free? [logic!]
return: [bigdecimal!]
][
as bigdecimal! bigint/add as bigint! big1 as bigint! big2 free?
]
bigdecimal!: alias struct! [
size [integer!] ;-- size in integer!
used [integer!] ;-- used length in integer!
expo [integer!]
prec [integer!]
]
It's same with bigint!
, so we can convert between each other through as
| size | used | expo | prec | unit 1 | unit 2 | ... | unit `used` | ... | unit `size` |
For example, "0" will be saved in memory like this:
| 1 | 1 | 0 | 20 | 0 |
"-1E100" will be saved in memory like this:
| 1 | -1 | 100 | 20 | 1 |
"1234567890.123456789" will be saved in memory like this:
| 3 | 3 | -9 | 20 | 23456789 | 45678901 | 123 |
"-1234567890.123456789" will be saved in memory like this:
| 3 | -3 | -9 | 20 | 23456789 | 45678901 | 123 |
bigdecimal!
use 1e8 as base unit, for example, 10 + 99999999
will produce "1" for high unit, 9
for low unit. This "1" high unit like uint32's overflow(carry flag).
So, we need make sure every unit is base 1e8 (< 1e8), and process carry flag, and then we get bigdecimal!
's math calculation
-
NaN?
andINF?
like infloat!
-
bigdecimal!
's shift is differentbigint!
.bigdecimal!
'sshift
one digit is 10x. - there are many
*-raw
functions, and these are just suitable for digits calculation, not considering exponent. -
rounding mode
: as we define digits length (or precision), but the math calculation result maybe exceed this length (default 20 digits), so we need round it to <= digits length. There is definition forROUNDING!
inbigint!
section.
- set-max-size: max bit length, default 1024
- alloc*: allocate
bigint!
header unit buffer - free*: free
bigint!
- copy*: copy a
bigint!
- expand*: copy a
bigint!
and expand this size to a larger one - load-int: load a integer value to
bigint!
- load-uint: load an uint32 value to
bigint!
- zero?*: check a value of
bigint!
if it's zero - compare: compare two
bigint!
value - negative?*: check a value of
bigint!
if it's negative - absolute*: get an absolute
bigint!
- shrink: remove head zero units
- left-shift: left shift
- right-shift: right shift
- add: add two
bigint!
value - sub: sub two
bigint!
value - mul: mul two
bigint!
value - div: div two
bigint!
value (also return remainder) - modulo: modulo two
bigint!
value (depends on rounding-mode) - load-bin: load a byte array to
bigint!
- load-str: load a string to
bigint!
, support 2 ~ 16 radix, but 16 radix is very fast - form: form a
bigint!
value to string, support 2 ~ 16 radix, but 16 radix is very fast
- set-default-prec: set precision, default 20
- set-exp-min: set min exp, default -1000000000
- set-exp-max: set max exp, default 1000000000
- set-rounding-mode: set rounding mode, default ROUND-HALF-UP
- alloc*: allocate
bigdecimal!
header unit buffer - free*: free
bigdecimal!
- copy*: copy a
bigdecimal!
- expand*: copy a
bigdecimal!
and expand this size to a larger one - load-nan: create a NaN
bigdecimal!
- load-inf: create a INF
bigdecimal!
- NaN?: check a value of
bigdecimal!
if it's NaN - INF?: check a value of
bigdecimal!
if it's INF - load-int: load a integer value to
bigdecimal!
- load-uint: load an uint32 value to
bigdecimal!
- shrink: remove no used tail zero
- zero?*: check a value of
bigdecimal!
if it's zero - compare: compare two
bigdecimal!
value - load-str: load base10 digit string (only include "0"~"9") to
bigdecimal!
- load-float: load base10 float string (support "." and "E") to
bigdecimal!
- form:
format
abigdecimal!
value to base10 float string - left-shift: left shift
- right-shift: right shift
- add: add two
bigdecimal!
value - sub: sub two
bigdecimal!
value - mul: mul two
bigdecimal!
value - div: div two
bigdecimal!
value - remainder: remainder two
bigdecimal!
value - modulo: modulo two
bigdecimal!
value (depends on rounding-mode)
There are many examples in "tests/source/runtime/bigint-test.reds" and "tests/source/runtime/bigdecimal-test.reds"
-
bigint!
is for integer calculation,bigdecimal!
is for float calculation -
bigint!
is saved in uint32,bigdecimal!
is also saved in uint32, but the unit less than 1e8, you can call this base 1e8. -
bigint!
is suitable for base16 data calculation and show, if it shows in base10, theform
will cost manydiv 10
to get base10 string. -
bigdecimal!
is suitable for base10 data calculation and show, as one unit range is 0 ~ 99999999, it's easy toform
it in base10 string. -
bigint!
's precison is limit with bit length (default 1024),bigdecimal!
's precison is limit with digit length (default 20) -
bigint!
's (default 1024) range is [-2^1024, 2^1024],bigdecimal!
's (default 20 for max digits, -1000000000 for min exp, 1000000000 for max exp) range is [-99999999999999999999E-1000000000, 99999999999999999999E1000000000]