BigDecimals unit
An uptodate version of this and other files can be found in my BigNumbers project on GitHub.
BigDecimals
Ten decimal places of π are sufficient to give the circumference of the earth to a fraction of an inch, and thirty decimal places would give the circumference of the visible universe to a quantity imperceptible to the most powerful microscope. — Simon Newcomb
Floating point arithmetic can be very useful for noninteger calculations. The fact that they are hardware supported makes them fast, but due to the fact that the currently usual IEEE754 types Single (binary32) and Double (binary64) are not only limited in size, they are also limited in precision and range.
Despite the statement in the quote above, sometimes a very high precision is needed. One example are certain financial calculations. The exact calculation of annual rates might require a precision of as much as 2191 digits, as described on this website. The exact calculation of certain physical or mathematical constants like π or e might even require a much higher precision.
I already implemented something every Delphi user should have at their disposal, BigIntegers
, and now I also implemented the logical next step: BigDecimals
. They are multiprecision, decimal floating point types. A few years ago, I implemented a Delphi version of the .NETcompatible Decimal
type. BigDecimal
is the big version of that, with an almost unlimited precision and almost unlimited range.
BigDecimal
has a range that can vary between 536,870,912 and +536,870,912, so the tiniest value that can be represented is 1 × 10^{536,870,912} and the largest is at least 999999… × 10^{+536,870,911}. The precision is around 10^{5,000,000,000}.
Usage
BigDecimals
are easy to use. They are value types, so you don’t have to worry about memory management. They can be used like:
var
A, B, C: BigDecimal;
begin
A := '1.3456e17'; // exact
B := 1234.197; // floating point approximation
C := A + B * '3.0';
Writeln(string(C));
While you can use a BigDecimal
like a simple floating point type, you should note that their real memory consumption can be much higher. A BigDecimal
only consists of a pointer and a few integers, so on your stack it won’t take up much memory, but the value it represents is allocated on the heap, and that can be many more bytes, depending on the precision of the number that is represented.
BigDecimals
are immutable, so any expression or public function that modifies the value returns a new BigDecimal
. The original is not modified.
Initialization
There are several ways to get values into a BigDecimal
. You can use constructors, like
A := BigDecimal.Create(1.79e308); // floating point value
B := BigDecimal.Create('1.79e308'); // string (exact)
or (implicit or explicit) conversion operators like
B := '3.141592653589793238462643383279'; // string
C := 17; // integral type
E := BigDecimal(6.345E30); // floating point type
Constructors
Although BigDecimal
is a record type and record constructors are no real constructors, and the syntax used can deceive you into thinking a new instance is allocated somewhere, I think records should have them, for initialization. But note that, if A
is a BigDecimal
, it doesn’t matter if you do:
A := BigDecimal.Create('1.7');
Or just:
A.Create('1.7');
Both will do exactly the same and initialize A with the value 1.7.
The constructors defined are:
constructor Create(const UnscaledValue: BigInteger; Scale: Integer); overload;  Creates a BigDecimal with given unscaled value and given scale. The sign is determined by the BigInteger . 
constructor Create(const E: Extended); overload;  Creates a BigDecimal with the same value as the given Extended parameter. 
constructor Create(const D: Double); overload;  Creates a BigDecimal with the same value as the given Double parameter. 
constructor Create(const S: Single); overload;  Creates a BigDecimal with the same value as the given Single parameter. 
For parameters of type Extended , Double or Single , an exception of type EInvalidArgument is raised if the parameter contains a NaN or +/ infinity. 

constructor Create(const S: string); overload;  Creates a BigDecimal with the value that results from parsing the given string parameter. 
For a parameter of type string , an exception of type EConvertError is raised if the string cannot be parsed to a valid BigDecimal . 

constructor Create(const UnscaledValue: BigInteger); overload;  Creates a BigDecimal with the same value as the given BigInteger parameter. 
constructor Create(const U: UInt64); overload;  Creates a BigDecimal with the same value as the given unsigned 64 bit integer parameter. 
constructor Create(const I: Int64); overload;  Creates a BigDecimal with the same value as the given signed 64 bit integer parameter. 
constructor Create(U: UInt32); overload;  Creates a BigDecimal with the same value as the given unsigned 32 bit integer parameter. 
constructor Create(I: Int32); overload;  Creates a BigDecimal with the same value as the given signed 32 bit integer parameter. 
It may seem convenient to use floating point types to initialize a BigDecimal
, but note that this will not always give you what you like. There is a difference between
MyBigDecimal := BigDecimal.Create(1.7);
and
MyBigDecimal := BigDecimal.Create('1.7');
The former will create a BigDecimal
with the exact value of
1.70000000000000000004336808689942017736029811203479766845703125
This is due to the fact that most floating point numbers are only approximations of the decimal values you give them. The above is the exact value of the closest possible Extended
representation of the value 1.7
, so that is the value the BigDecimal
gets too. This is not the case for the latter piece of code, using a string. It will create a BigDecimal
with the exact value parsed from the string, i.e. 1.7
(or, to be exact, 17 × 10^{−1}).
Note that there are values a BigDecimal
can not represent exactly either, for instance, you cannot express 1/3 exactly in decimal notation. But while Decimal
and BigDecimal
are good at representing decimal values exactly, floating point types can only represent multiples of powers of two exactly, so they are fine for 0.5 (or 1/2) and 0.375 (or 3/8), but not for, e.g. 0.1 or 1.7.
Another problem with floating point types is that the range of a floating point type is not equally spaced, i.e. the next representable value above 1.79e300 is not 1.79e300 + 1, it is approx. 1.79e300 + 1e264, and any value inbetween is rounded to one of these. This is not the case for BigDecimal
. If you use BigDecimal
, 1.79e300 + 1 = 1790000…0001 (middle part omitted for legibility), in other words, exactly 1 more than the original value. There are no gaps in the range of a BigDecimal
.
More on floating point types in my article about them.
If you need accurate decimal values, avoid floating point types to initialize BigDecimals
. Use strings or predefined values instead.
Implicit conversions and class functions
The following implicit conversion operators are defined. Implicit means that you don’t have to cast, but can, if you want to. So you can either do:
MyBigDecimal := BigDecimal('3.141592');
or you can do:
MyBigDecimal := '3.141592';
and the result will be exactly the same.
class operator Implicit(const E: Extended): BigDecimal;  Returns a BigDecimal with the exact value of the given Extended parameter. 
class operator Implicit(const D: Double): BigDecimal;  Returns a BigDecimal with the exact value of the given Double parameter. 
class operator Implicit(const S: Single): BigDecimal;  Returns a BigDecimal with the exact value of the given Single parameter. 
class operator Implicit(const S: string): BigDecimal;  Returns a BigDecimal with the value parsed from the given string parameter. 
class operator Implicit( const UnscaledValue: BigInteger): BigDecimal;  Returns a BigDecimal with the value of the given BigInteger parameter. 
class operator Implicit(const U: UInt64): BigDecimal;  Returns a BigDecimal with the value of the given unsigned 64 bit integer parameter. 
class operator Implicit(const I: Int64): BigDecimal;  Returns a BigDecimal with the value of the given signed 64 bit integer parameter. 
The string
parameter can have the same contents as a floating point literal, i.e. it consists mainly of decimal digits ('0'..'9'
), it can have at most one decimal separator ('.'
), it can have one or more thousand separators (','
, or, alternatively, ' '
), an exponent part, consisting of an 'e'
or 'E'
, an optional sign (''
or '+'
), and an exponent consisting of decimal digits. The thousand separators can be anywhere. They are simply ignored.
Examples of valid strings are '1'
, '1.034'
, '1,000,000.345'
, '1.79e308'
and '3,456,456.4e13'
. But also '1,,,2,,3.4e99'
, which is simply interpreted as '123.4e99'
.
Note, however, that trailing zeroes matter. So, while '1.00'
and '1.0000'
will compare as equal, they are different values. One has two, the other has four decimals, and BigDecimal
remembers that. How this is done is explained later on (but know that the former is stored as 100 × 10^{2} while the latter is stored as 10,000 × 10^{4}, so they have different internal representations).
Internals
Before I continue with the other methods and operators, I first want to talk a little about the internals.
A BigDecimal
is a record type with only a few member fields. One is a BigInteger
, which contains the significant digits of the BigDecimal
. This is also called the unscaled value (internal field FValue
). The other important member field is the scale (internal field FScale
), which determines the power of 10 by which the unscaled value must be divided to get the nominal value of the BigDecimal
. This means that a value like 1.79 is stored as an unscaled value of 179 and a scale of 2. In other words, the value is stored as 179 / 10^{2} (which can also be seen as 179 × 10^{2}). As you see, a positive scale means dividing by a power of 10. But, unlike in my Decimal
type, the scale can be negative too, and then you multiply by a power of 10. So 1.79e+308 is stored as an unscaled value of 179 too, but now with a scale of 306.
For what it’s worth, in many programming languages, including Delphi, 1.79e30
or 1.79e+30
are the usual source code representations of the floating point number 1.79 × 10^{30}. E
or e
stand for “exponent”. Likewise, 3.45e8
stands for 3.45 × 10^{−8} (or 0.0000000345).
The usage of a scale allows BigDecimals
to have the same nominal values but different precisions. As I said before, 1.00 and 1.0000 compare both as exactly 1, but the former has a precision of 3, while the latter has a precision of 5 digits. The former is stored as 100 / 10^{2}, while the latter is stored as 10000 / 10^{4}.
The sign of the BigDecimal
is simply the sign of the contained BigInteger
.
Since BigInteger
already knows how to add, subtract, multiply or divide, these operations are done by the BigIntegers
. This does not mean that you can simply add two values with different scales. The scale must be adjusted to the larger of the two (by multiplying the BigInteger
of the BigDecimal
with the smallest scale by a power of 10 and adjusting the scale accordingly). Multiplication is similar: the BigIntegers
are multiplied and the scales are added. Division is a little more difficult. You can’t simply divide the BigIntegers
, because 1 div 3 returns 0, and you want something like 0.3333333…. That is where precision comes into play. The dividend is first multiplied by 10^{precision}, then it is divided by the divisor and the result is then rounded towards the target scale as much as possible. The target scale is the difference between dividend.Scale
and divisor.Scale
. How the rounding is done depends on the rounding mode.
Rounding and precision
First, let’s define precision as it is used here. Precision is the number of decimal digits the unscaled value (i.e. the BigInteger
) represents. This is equivalent to:
Precision := System.Math.Ceil(UnscaledValue.BitLength * Ln(2) / Ln(10));
if Precision = 0 then
Precision := 1;
where BitLength * Ln(2) / Ln(10)
is equivalent to Log10(N)
. If the BigInteger
is 0, then the result of the first line is 0 too, but that is seen as a precision of 1 anyway. So the precision of 1.79e+308 is only 3, not 308, because the BigInteger
is 179 and that has only three digits.
Rounding is cutting off the least significant digits of a value to obtain a number with a lower precision. If the precision you need is higher than the current precison, you simply multiply the unscaled value by the necessary power of 10 and adjust the scale accordingly. No rounding is required. But if the required precision is lower, you must cut off digits at the right and sometimes, you must adjust the unscaled value to do the required rounding.
BigDecimal
defines an enumeration type RoundingMode
, which governs how values are rounded, for instance after a division. It can can have the following values:
rmUp  Rounds up, away from zero 
rmDown  Rounds down towards zero, i.e. it truncates the least significant digits 
fmCeil  Rounds towards positive infinity 
rmFloor  Rounds towards negative infinity 
The following three modes round to the nearest digit, but if there is a tie, i.e. if the result is exactly halfway two next higher digits, they behave differently:  
rmNearestUp  Rounds to the nearest higher digit, but on a tie, it rounds to the nearest higher digit that is closer to zero 
rmNearestDown  Rounds to the nearest higher digit, but on a tie, it rounds to the nearest higher digit that is further away from zero 
rmNearestEven  Rounds to the nearest higher digit, but on a tie, it rounds towards the nearest higher even digit 
The following mode should only be used if you know that no rounding will take place:  
rmUnncessary  Rounding is not necessary. 
Raises an exception of type ERoundingNecessary if rounding turned out to be necessary after all. 
As you may or may not have noticed, I based most of the interface of BigDecimal
on the Java type of the same name. There, rounding and precision are stuck together in a MathContext
class, which must be passed in in most circumstances where rounding or a precision are required. So, in Java, to do a simple division, you either hope that the division does not cause a neverending recurrence of digits, e.g. what happens when you divide 1 by 3, or you pass a MathContext
with the required precision and rounding mode. Java does not have operator overloading, so that looks like:
MathContext mc = new MathContext(20, RoundingMode.HALF_DOWN); // precision, rounding mode
BigDecimal monthly = total.add(fixed).divide(BigDecimal.valueOf(12), mc);
BigDecimal additional = monthly.multiply(BigDecimal.valueOf("1.19")).add(BigDecimal.valueOf("3.2"));
But I wanted operator overloading, and then you can’t pass a precision or a rounding mode alongside the operands. That is why I gave BigDecimal
two class properties for this, aptly called DefaultPrecision
and DefaultRoundingMode
. These contain the defaults for all BigDecimal
operations. So in Delphi, you do:
BigDecimal.DefaultPrecision := 20;
BigDecimal.DefaultRoundingMode := rmNearestDown;
Monthly := (Total + Fixed) / 12;
Additional := Monthly * 1.19 + 3.2;
Back to Usage
Mathematical operations
A type like this is of no use if you can’t calculate with it. BigDecimal
defines the usual mathematical operators +
, 
, *
and /
. But it also defines div
and mod
. Of those two, the former returns an integral division result, the latter the remainder after that division. These operators have corresponding class functions as well, and the overloaded versions of these take rounding mode and precision parameters too. Here’s a table:
// Result := Left + Right; class operator Add( const Left, Right: BigDecimal): BigDecimal;  Adds two BigDecimals . The new scale is Max(Left.Scale, Right.Scale) . 
An exception of type EOverflow is raised if the result would become too big. 

// Result := Left – Right; class operator Subtract( const Left, Right: BigDecimal): BigDecimal;  Subtracts two BigDecimals . The new scale is Max(Left.Scale, Right.Scale) . 
An exception of type EOverflow is raised if the result would become too big. 

// Result := Left * Right; class operator Multiply( const Left, Right: BigDecimal): BigDecimal;  Multiplies two BigDecimals . The new scale is Left.Scale + Right.Scale . 
An exception of type EOverflow is raised if the result would become too big. 

An exception of type EUnderflow is raised if the result would become too small. 

// Result := Left / Right; class operator Divide( const Left, Right: BigDecimal): BigDecimal;  Divides two BigDecimals .Uses the default precision and rounding mode to obtain the result. The target scale is Left.Scale  Right.Scale . The result will approach this target scale as much as possible by removing any excessive trailing zeros. 
An exception of type EZeroDivide is raised if the divisor is zero. 

An exception of type EOverflow is raised if the result would become too big. 

An exception of type EUnderflow is raised if the result would become too small. 

// Result := Left div Right; class operator IntDivide( const Left, Right: BigDecimal): BigDecimal;  Divides two BigDecimals to obtain an integral result. 
An exception of type EZeroDivide is raised if the divisor is zero. 

An exception of type EOverflow is raised if the result would become too big. 

An exception of type EUnderflow is raised if the result would become too small. 

// Result := Left mod Right; class operator Modulus( const Left, Right: BigDecimal): BigDecimal;  Returns the remainder after Left is divided by Right to an integral value. 
An exception of type EZeroDivide is raised if the divisor is zero. 

An exception of type EOverflow is raised if the result would become too big. 

An exception of type EUnderflow is raised if the result would become too small. 

// Result := Value; class operator Negative( const Value: BigDecimal): BigDecimal;  Negates the given BigDecimal . 
// Result := +Value; class operator Positive( const Value: BigDecimal): BigDecimal;  Called when a BigDecimal is preceded by a unary + . Currently a noop. 
// Result := Round(Value); class operator Round( const Value: BigDecimal): Int64;  Rounds the given BigDecimal to an Int64 . 
An exception of type EConvertError is raised if the result is too large to fit in an Int64 . 

// Result := Trunc(Value); class operator Trunc( const Value: BigDecimal): Int64;  Truncates (rounds down towards 0) the given BigDecimal to an Int64 . 
An exception of type EConvertError is raised if the result is too large to fit in an Int64 . 

class function Add( const Left, Right: BigDecimal): BigDecimal; overload; static;  See operator Add 
class function Subtract( const Left, Right: BigDecimal): BigDecimal; overload; static;  See operator Subtract 
class function Multiply( const Left, Right: BigDecimal): BigDecimal; overload; static;  See operator Multiply 
class function Divide( const Left, Right: BigDecimal): BigDecimal; overload; static;  See operator Divide 
class function Divide( const Left, Right: BigDecimal; Precision: Integer; ARoundingMode: RoundingMode): BigDecimal; overload; static;  Like operator Divide , but uses the given Precision and RoundingMode 
class function Divide( const Left, Right: BigDecimal; Precision: Integer): BigDecimal; overload; static;  Like operator Divide , but uses the given Precision and the default rounding mode 
class function Divide( const Left, Right: BigDecimal; ARoundingMode: RoundingMode): BigDecimal; overload; static;  Like operator Divide , but uses the given RoundingMode and the default precision 
class function Negate( const Value: BigDecimal): BigDecimal; overload; static;  See operator Negative 
class function Round( const Value: BigDecimal): Int64; overload; static;  See operator Round 
class function Round( const Value: BigDecimal; ARoundingMode: RoundingMode): Int64; overload; static;  Like operator Round , but uses the given RoundingMode 
// Result := Left – (Left div Right) * Right; class function Remainder( const Left, Right: BigDecimal): BigDecimal; static;  See operator Modulus 
class function Abs( const Value: BigDecimal): BigDecimal; overload; static;  Returns the absolute value of the given BigDecimal . 
class function Sqr( const Value: BigDecimal): BigDecimal; overload; static;  Returns the square (Value * Value ) of the given BigDecimal . 
class function Sqrt( const Value: BigDecimal; Precision: Integer): BigDecimal; overload; static;  Returns the square root of the given BigDecimal , using the given Precision . 
class function Sqrt( const Value: BigDecimal): BigDecimal; overload; static;  Returns the square root of the given BigDecimal, using the default precision. 
class function IntPower( const Base: BigDecimal; Exponent, Precision: Integer): BigDecimal; overload; static;  Returns Base raised to the integral power of Exponent , in the given Precision . This routine is optimized by limiting the precision of intermediate values. 
An exception of type EIntPowerExponent is raised if the exponent is outside the range 9999999..9999999. 

class function IntPower( const Base: BigDecimal; Exponent: Integer): BigDecimal; overload; static;  Returns Base raised to the integral power of Exponent , in unlimited precision. 
An exception of type EIntPowerExponent is raised if the exponent is outside the range 9999999..9999999. 

function Abs: BigDecimal; overload;  Returns the absolute value of the current BigDecimal . 
function Int: BigDecimal;  Returns a BigDecimal with any fraction (digits after the decimal point) removed from the current BigDecimal . 
function Trunc: Int64;  Returns a signed 64 bit integer with any fraction (digits after the decimal point) removed from the current BigDecimal . 
function Frac: BigDecimal;  Returns a BigDecimal containing only the fractional part (digits after the decimal point) of the current BigDecimal . 
function Reciprocal(Precision: Integer): BigDecimal; overload;  Returns the reciprocal of the current BigDecimal , using the given Precision . 
An exception of type EZeroDivide is raised if the current BigDecimal is zero. 

function Reciprocal: BigDecimal; overload;  Returns the reciprocal of the current BigDecimal , using the default precision. 
An exception of type EZeroDivide is raised if the current BigDecimal is zero. 

function Sqrt(Precision: Integer): BigDecimal; overload;  Returns the square root of the current BigDecimal, with the given precision 
function Sqrt: BigDecimal; overload;  Returns the square root of the current BigDecimal, with the default precision. 
function Sqr: BigDecimal; overload;  Returns the square (Self * Self ) of the current BigDecimal . 
function IntPower( Exponent, Precision: Integer): BigDecimal; overload;  Returns the current BigDecimal raised to the integral power of Exponent , in the given Precision . 
An exception of type EIntPowerExponent is raised if the exponent is outside the range 9999999..9999999. 

function IntPower( Exponent: Integer): BigDecimal; overload;  Returns the current BigDecimal raised to the integral power of Exponent , in unlimited precision. 
An exception of type EIntPowerExponent is raised if the exponent is outside the range 9999999..9999999. 
Comparison operations
All comparison operations base on the Compare
function. Here they are:
// Result := (Left <= Right); class operator LessThanOrEqual( const Left, Right: BigDecimal): Boolean;  Returns True only if Left is mathematically less than or equal to Right . 
// Result := (Left < Right); class operator LessThan( const left, Right: BigDecimal): Boolean;  Returns True only if Left is mathematically less than Right . 
// Result := (Left >= Right); class operator GreaterThanOrEqual( const Left, Right: BigDecimal): Boolean;  Returns True only if Left is mathematically greater than or equal to Right . 
// Result := (Left > Right); class operator GreaterThan( const Left, Right: BigDecimal): Boolean;  Returns True only if Left is mathematically greater than Right . 
// Result := (Left = Right); class operator Equal( const Left, Right: BigDecimal): Boolean;  Returns True only if Left is mathematically equal to Right . 
// Result := (Left <> Right); class operator NotEqual( const Left, Right: BigDecimal): Boolean;  Returns True only if Left is mathematically not equal to Right . 
class function Compare( const Left, Right: BigDecimal): TValueSign; static;  Returns 1 if Left is mathematically greater than Right , 0 if Left is mathematically equal to Right and 1 if Left is mathematically less than Right . 
class function Max( const Left, Right: BigDecimal): BigDecimal; static;  Returns the maximum of the two given BigDecimal values. 
class function Min( const Left, Right: BigDecimal): BigDecimal; static;  Returns the minimum of the two given BigDecimal values. 
Just like in mathematics, Compare
compares two values with different scale but same nominal value as equal, so
X := BigDecimal.Compare('1.10', '1.1000');
returns 0, meaning equality. In the same sense,
Y := BigDecimal('1.2300') < BigDecimal('1.23');
returns False
.
In Java, if two BigDecimals
have the same nominal value, but different scales, the equals()
function returns false
. And if you use ==
, they must even be identical, i.e. have the same reference (address). To compare the numerical value of two BigDecimals
, let’s call them a
and b
, you must do something like areTheyEqual = (a.compareTo(b) == 0);
. This is not necessary for the Delphi BigDecimals
described here. You can simply use =
to compare two BigDecimals
for numerical equality, even if they have different scales.
Explicit conversions
Explicit conversions were made to be silent, so if this is possible, they don’t raise exceptions. In the following piece of code (without BigDecimals
):
var
I64: Int64;
I32: Integer;
begin
I64 := 10000000000000;
I32 := Integer(I64);
I32
ends up with a value of 1316134912, because that is the value of the low 32 bit part of the Int64
. The same principle applies to BigDecimal
, so instead of raising an exception because the value in the BigDecimal
does not fit in the target type, a silent conversion is performed that makes it fit.
class operator Explicit( const Value: BigDecimal): Extended;  Returns an Extended with the best possible approximation of the given BigDecimal value.The conversion uses the default rounding mode. 
An exception of type ERoundingNecessary is raised if a rounding mode rmUnnecessary was specified but rounding is necessary after all. 

class operator Explicit( const Value: BigDecimal): Double;  Returns a Double with the best possible approximation of the given BigDecimal value.The conversion uses the default rounding mode. 
An exception of type ERoundingNecessary is raised if a rounding mode rmUnnecessary was specified but rounding is necessary after all. 

class operator Explicit( const Value: BigDecimal): Single;  Returns a Single with the best possible approximation of the given BigDecimal value.The conversion uses the default rounding mode. 
An exception of type ERoundingNecessary is raised if a rounding mode rmUnnecessary was specified but rounding is necessary after all. 

class operator Explicit( const Value: BigDecimal): string;  Returns a string representation of the given BigDecimal value. This uses ToString , which generally returns the shortest possible string representation of the BigDecimal . 
class operator Explicit( const Value: BigDecimal): BigInteger;  Returns a BigInteger with the rounded value of the given BigDecimal .The conversion uses the rounding mode rmDown , i.e. it truncates. 
class operator Explicit( const Value: BigDecimal): UInt64;  Returns an unsigned 64 bit integer with the rounded value of the given BigDecimal value.The conversion uses the rounding mode rmDown , i.e. it truncates. 
class operator Explicit( const Value: BigDecimal): Int64;  Returns a signed 64 bit integer with the rounded value of the given BigDecimal value.The conversion uses the rounding mode rmDown , i.e. it truncates. 
String conversion
There are a few routines for conversion to and from a string. A valid string is like a valid floating point literal:
 an optional sign (
'+'
or''
)  a significand, consisting of
 an integral part, consisting of one or more decimal digits (
'0'..'9'
)  an optional decimal point
 an optional fractional part, consisting of one or more decimal digits
 optional thousands separators or spaces — these are ignored
 an integral part, consisting of one or more decimal digits (
 an optional exponent, consisting of
 an exponent delimiter, either
'e'
or'E'
 an optional sign (
'+'
or''
)  an exponent value, consisting of one or more decimal digits
 an exponent delimiter, either
Here are a few examples of valid (locale invariant) strings representing a BigDecimal
:
string  unscaled value  scale  string  unscaled value  scale  

'0' 
0  0  '0.00' 
0  2  
'179' 
179  0  '179' 
179  0  
'1.79e3' 
179  1  '1.79e+3' 
179  1  
'17.9e+7' 
179  6  '17.0' 
170  1  
'17.9' 
179  1  '0.00179' 
179  5  
'1.79e12' 
179  14  '1,798.1e4' 
17981  5  
'0e+7' 
0  7  '0' 
0  0  
'123,456.78' 
12345678  2  '1 234e+8' 
1234  8 
Parsing
There are four routines for parsing a string into a BigDecimal
. Two use the invariant format settings, where '.'
is the decimal separator and ','
is the thousands separator. The other two use explicit TFormatSettings
parameters, e.g. for Germany, where I live, I could use TFormatSettings.Create('de_DE')
.
class function TryParse( const S: string; const Settings: TFormatSettings; out Value: BigDecimal): Boolean; overload; static;  Tries to parse the given string as a BigDecimal into Value , using the given format settings.Returns True if the function was successful. 
class function TryParse( const S: string; out Value: BigDecimal): Boolean; overload; static;  Tries to parse the given string as a BigDecimal into Value , using the system invariant format settings.Returns True if the function was successful. 
class function Parse( const S: string; const Settings: TFormatSettings): BigDecimal; overload; static;  Returns the BigDecimal with a value as parsed from the given string, using the given format settings. 
An exception of type EConvertError is raised if the string cannot be parsed to a valid BigDecimal . 

class function Parse( const S: string): BigDecimal; overload; static;  Returns the BigDecimal with a value as parsed from the given string, using the system invariant format settings. 
An exception of type EConvertError is raised if the string cannot be parsed to a valid BigDecimal . 
Conversion to string
There are four routines that convert a BigDecimal
to a string. The routines ToString
generally return the shortest possible string representation. If necessary, the value is represented in scientific notation, i.e. with an exponent. So 1.79e30 is represented as '1.79e30'
. The routines ToPlainString
always use the plain notation and don’t use scientific. This means that a value of 1.79e30 is represented as '1790000000000000000000000000000'
. But note that both representations do not lose precision, so if the precision requires thirty decimals, even ToString
will represent all of them. An example:
var
D1, D2: BigDecimal;
begin
D1 := '1.7900000000000000000000e30'; // note: 20 trailing zeros
D2 := '1.79e30';
Writeln(Format('D1 = %s, D2 = %s', [D1.ToString, D2.ToString]));
Writeln(Format('D1 = %s, D2 = %s', [D1.ToPlainString, D2.ToPlainString]));
end;
The output is
D1 = 1.7900000000000000000000e+30, D2 = 1.79e+30
D1 = 1790000000000000000000000000000, D2 = 1790000000000000000000000000000
In other words, '1.7900000000000000000000e+30'
is the shortest representation D1.ToString
can generate, because the trailing zeros are part of the precision and they can not be cut off.
There is a way to cut off the trailing zeros of a BigDecimal
, using RemoveTrailingZeros
. This is discussed later on.
function ToString: string; overload;  Returns the short notation of the current BigDecimal in the system invariant format settings. If necessary, scientific notation is used. 
Because this does not use TFormatSettings , the result is roundtrip, so it is a valid string that can be parsed using Parse() or TryParse() . 

function ToString( const Settings: TFormatSettings): string; overload;  Returns the short notation of the current BigDecimal , using the given TFormatSettings to obtain the decimal point Char . If necessary, scientific notation is used. 
function ToPlainString: string; overload;  Returns a plain string of the current BigDecimal , using the system invariant format settings. This plain notation is sometimes called “decimal notation”, and represents the value without the use of exponents. 
function ToPlainString( const Settings: TFormatSettings): string; overload;  Returns a plain string of the current BigDecimal , using the given TFormatSettings to obtain the decimal point Char . 
Miscellaneous functions
There are a number of functions that return information about a BigDecimal
, or return modified versions of the original BigDecimal
.
Rounding and scaling
Rounding and scaling are closely related. Scaling down often requires rounding. The following functions return rounded or truncated versions of the original values.
function RoundTo(Digits: Integer; ARoundingMode: RoundingMode): BigDecimal; overload;  Rounds the current BigDecimal to a value with at most Digits fractional digits, using the given rounding mode. This is more or less equivalent to the RoundTo function for floating point types. 
An exception of type ERoundingNecessary is raised if a rounding mode rmUnnecessary was specified but rounding is necessary after all. 

The System.Math.RoundTo function uses the floating point rounding mode equivalent of rmNearestEven , while System.Math.SimpleRoundTo uses the equivalent of rmNearestUp . This function is more versatile. 

This is exactly equivalent to RoundToScale(Digits, ARoundingMode) . 

function RoundTo(Digits: Integer): BigDecimal; overload;  Rounds the current BigDecimal to a value with at most Digits fractional digits, using the default rounding mode. 
An exception of type ERoundingNecessary is raised if a rounding mode rmUnnecessary was specified but rounding is necessary after all. 

function RoundToScale(NewScale: Integer; ARoundingMode: RoundingMode): BigDecimal;  Rounds the current BigDecimal to a value with the given scale, using the given rounding mode. 
An exception of type ERoundingNecessary is raised if a rounding mode rmUnnecessary was specified but rounding is necessary after all. 

function RoundToPrecision( APrecision: Integer): BigDecimal; overload;  Rounds the current BigDecimal to a certain precision (number of significant digits). 
An exception of type ERoundingNecessary is raised if a rounding mode rmUnnecessary was specified but rounding is necessary after all. 

function RemoveTrailingZeros( TargetScale: Integer): BigDecimal;  Returns a new BigDecimal with all trailing zeroes (up to the target scale) removed from the current BigDecimal . No significant digits will be removed and the numerical value of the result compares as equal to the original value.TargetScale is the scale up to which trailing zeroes can be removed. It is possible that fewer zeroes are removed, but never more than necessary to reach the target scale. 
Example: BigDecimal('1234.5678900000').RemoveTrailingZeros(6) results in BigDecimal('1234.567890') . 
Information
The following instance methods return information about the current BigDecimal
.
function IsZero: Boolean;  Returns True if the current BigDecimal ’s value equals BigDecimal.Zero . 
function Sign: TValueSign;  Returns the sign of the current BigDecimal : 1 if negative, 0 if zero, 1 if positive. 
function Precision: Integer;  Returns the number of significant digits of the current BigDecimal . 
function ULP: BigDecimal;  Returns the unit of least precision of the current BigDecimal . 
Properties
Predefined BigDecimals
The unit defines a few useful constants for often needed BigDecimal
values. These should be used preferrably to newly created BigDecimals
with the same values. That avoids duplication of the payload of the contained BigInteger
.
class property MinusOne: BigDecimal;  A BigDecimal representing 1. 
class property Zero: BigDecimal;  A BigDecimal representing 0. 
class property One: BigDecimal;  A BigDecimal representing 1. 
class property Two: BigDecimal;  A BigDecimal representing 2. 
class property Ten: BigDecimal;  A BigDecimal representing 10. 
class property Half: BigDecimal;  A BigDecimal representing 0.5. 
class property OneTenth: BigDecimal;  A BigDecimal representing 0.1. 
Systemwide defaults
Beside the already mentioned defaults for precision and rounding mode, there is also a default for the exponent delimiter used in string output. The default is 'e'
and not, as in most other implementations, 'E'
, because to me, a lower case letter between digits that generally have the height of upper case letters, is more clearly visible than an upper case 'E'
. In other words, I prefer '1.798765432102345e+30'
over '1.798765432102345E+30'
, because I find it more readable.
class property DefaultRoundingMode: RoundingMode read … write …;  The rounding mode to be used if no specific mode is indicated, e.g. in expressions using overloaded operators. 
class property DefaultPrecision: Integer read … write …;  The (maximum) precision to be used for e.g. division if the operation would otherwise result in a nonterminating decimal expansion,
i.e. if there is no exact representable decimal result, e.g. when dividing BigDecimal.One / BigDecimal(3) , resulting in the neverending 0.3333333… 
class property ExponentDelimiter: Char read … write …;  The string to be used to delimit the exponent part in scientific notation output. 
Currently, only 'e' and 'E' are allowed. Setting any other value will be ignored. The default is 'e' . 
Access to internals
The fields of a BigDecimal
can be accessed readonly:
property UnscaledValue: BigInteger read …;  The unscaled value of the current BigDecimal . This is the BigInteger that contains the
significant digits and the sign of the BigDecimal . To obtain the nominal value, it is then scaled (in powers of ten) by Scale . 
property Scale: Integer read …;  The scale of the current BigDecimal . This is the power of ten by which the UnscaledValue must be divided to get the nominal value of the BigDecimal . If it is positive, it represents the number of digits after the decimal point. A negative scale value stands for multiplying by a power of ten. 
A negative scale my be a little hard to understand. Note that 1e+3, which has a precision of 1, will be represented by an UnscaledValue
of 1, but to make it get the value of 1 × 10^{3}, the scale will have to be set to 3. Mathematically, that is the same as 1000, but internally, it isn’t. That is because 1e+3 has a precision of 1, while 1000 has a precision of 4.
Visualizer
Since I had to write a simple parser for the debugger visualizer for BigInteger
anyway, I amended it to parse debug output for BigDecimal
too, so now there is a debugger visualizer for both BigInteger
and BigDecimal
, in the same unit. See here how to get and install it.
Notes
FPrecision
Currently, there is a private FPrecision
instance field. This is meant to serve as a cache for the Precision
function. BigDecimals
are immutable, so the value of the record never changes. The idea is that if FPrecision
is 0, it is uninitialized and the Precision
function must calculate the precision. But once that is done, it can’t change, so it could be stored in FPrecision
and the function could return this cached value. The problem is, however, that unlike classes, records are not zeroed out on automatic initialization. So if, in the Precision
function, I read a value that is not 0, I can’t be sure if it was calculated before, or if it is garbage resulting from previous use of the memory. All routines that initialize or modify the internals of a new BigDecimal currently initialize FPrecision
to 0, but the uncertainty remains.
On the other hand, if you use an Integer
or a Double
without initializing it, you get garbage and undefined behaviour too. So I could blame undefined values of FPrecision on the same undefined behaviour you get when you don’t initialize variables of the builtin types and simply use FPrecision
as it was intended.
Mathematics
Currently, there are no functions that provide higher mathematical functions (except Sqrt
) for BigDecimals
. I intend to write a new unit that provides functions like Cos
, ArcTan
, SinH
, Ln
, Exp
or Pi
with a set precision, but that is still in the planning stage. Such functions will probably be extremely slow, compared to hardware supported floating point, but much more accurate.
Number formatter
I also plan to implement a unit that provides a general number formatter. To this, you pass a record containing a string of digits, a scale and some more information, and it uses a format string like '#,##000.e+000'
, more or less like Delphi’s FloatToStr
does, to format the output. I intend to make it able to format the builtin floating point types, my Decimal
type and my BigDecimal
type by default, but it should also be able to format other types, if you can pass the data in the required record.
Conclusion
I hope this code is useful to you. If you use some of it, please credit me. If you modify or improve the unit, please send me the modifications at this email address.
I may improve or enhance the unit myself, and I will try to post changes here. But this is not a promise. Please don’t request features.
Rudy Velthuis
Standard Disclaimer for External Links
These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval of any of the products, services or opinions of the corporation or organization or individual. I bear no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.
Disclaimer and Copyright
The coding examples presented here are for illustration purposes only. The author takes no responsibility for enduser use. All content herein is copyrighted by Rudy Velthuis, and may not be reproduced in any form without the author's permission. Source code written by Rudy Velthuis presented as download is subject to the license in the files.