stdxdecimal
This module defines an exact decimal type, Decimal
, to a specific number of digits. This is designed to be a drop in replacement for built-in floating point numbers, allowing all the same possible operations.
Discussion
Floating point numbers (float
, double
, real
) are inherently inaccurate because they cannot represent all possible numbers with a decimal part. Decimal
on the other hand, is able to represent all possible numbers with a decimal part (limited by memory size and Hook.precision
).
Adapted from the specification of the General Decimal Arithmetic.
Basic Example:
import stdxdecimal;
void main()
{
auto d1 = decimal("1.23E-10");
d1 -= decimal("2.00E-10");
assert(d1.toString() == "-0.000000000077");
}
Custom Behavior: The behavior of Decimal
is controlled by the template parameter Hook
, which can be a user defined type or one of the Hook
s provided by this module.
The following behaviors are controlled by Hook
:
- The number of significant digits to store.
- The rounding method.
- The min and max exponents. (not implemented yet)
- What to do when exceptional conditions arise.
The following predefined
Hook
s are available:
Abort |
precision is set to 9, rounding is HalfUp , and the program will assert(0) on divsion by zero, overflow, underflow, and invalid operations.
|
Throw |
precision is set to 9, rounding is HalfUp , and the program will throw an exception on divsion by zero, overflow, underflow, and invalid operations.
|
HighPrecision |
precision is set to 64, rounding is HalfUp , and the program will assert(0) on divsion by zero, overflow, underflow, and invalid operations.
|
NoOp |
precision is set to 9, rounding is HalfUp , and nothing will happen on exceptional conditions.
|
Percision and Rounding:
Decimal
accurately stores as many as Hook.precision
significant digits. Once the number of digits > Hook.precision
, then the number is rounded. Rounding is performed according to the rules laid out in .
By default, the precision is 9, and the rounding mode is RoundingMode.HalfUp
.
Hook.precision
must be <= uint.max - 1
and > 1
.
Note: Increasing the number of possible significant digits can result in orders of magnitude slower behavior, as described below. Only ask for as many digits as you really need.
9 and below (default) | Baseline |
10-19 | 2x slower than baseline |
20-1000 | 20x slower than baseline |
1000 and above | 200x slower than baseline |
Exceptional Conditions: Certain operations will cause a Decimal
to enter into an invalid state, e.g. dividing by zero. When this happens, Decimal does two things
- Sets a public
bool
variable totrue
. - Calls a specific function in
Hook
, if it exists, with the operation's result as the only parameter.
The following table lists all of the conditions
Name | Flag | Method | Description |
---|---|---|---|
Clamped | clamped |
onClamped |
Occurs when the exponent has been altered to fit in-between Hook.maxExponent and Hook.minExponent . |
Inexact | inexact |
onInexact |
Occurs when the result of an operation is not perfectly accurate. Mostly occurs when rounding removed non-zero digits. |
Invalid Operation | invalidOperation |
onInvalidOperation |
Flagged when an operation makes no sense, e.g. multiplying 0 and Infinity or add -Infinity to Infinity. |
Division by Zero | divisionByZero |
onDivisionByZero |
Specific invalid operation. Occurs whenever the dividend of a division or modulo is equal to zero. |
Rounded | rounded |
onRounded |
Occurs when the Decimal 's result had more than Hook.precision significant digits and was reduced. |
Subnormal | subnormal |
onSubnormal |
Flagged when the exponent is less than Hook.maxExponent but the digits of the Decimal are not inexact. |
Overflow | overflow |
onOverflow |
Not to be confused with integer overflow, this is flagged when the exponent of the result of an operation would have been above Hook.maxExponent and the result is inexact. Inexact and Rounded are always set with this flag. |
Underflow | underflow |
onUnderflow |
Not to be confused with integer underflow, this is flagged when the exponent of the result of an operation would have been below Hook.minExponent . Inexact, Rounded, and Subnormal are always set with this flag. |
Each function documentation lists the specific states that will led to one of these flags.
Differences From The Specification:
- There's no concept of a Signaling NaN in the module.
compare
, implemented asopCmp
, does not propagateNaN
due to D'sopCmp
semantics.
Version
v0.5
. Still work in progress. For missing features, see README.md
License
.
-
Declaration
struct
Decimal
(Hook = Abort);A exact decimal type, accurate to
Hook.precision
digits. Designed to be a drop in replacement for floating points.Discussion
Behavior is defined by
Hook
. See the module overview for more information.-
Declaration
Hook
hook
;
is a member variable if it has state, or an alias forhook
Hook
otherwise. -
Declaration
bool
clamped
;
booldivisionByZero
;
boolinexact
;
boolinvalidOperation
;
booloverflow
;
boolrounded
;
boolsubnormal
;
boolunderflow
;Public flags
-
Declaration
pure this(T)(const T
num
) if (isNumeric!T);Constructs an exact decimal type from a built in number
Parameters
T
num
the number to convert to exact decimal
Note: Using
float
types for construction is less accurate than using a string representation due to floating point inaccuracy. If possible, it's always better to use string construction. -
Declaration
this(S)(S
str
) if (isForwardRange!S && isSomeChar!(ElementEncodingType!S) && !isInfinite!S && !isSomeString!S);
pure this(S)(Sstr
) if (isSomeString!S);Converts a string representing a number to an exact decimal.
Discussion
If the string does not represent a number, then the result is
NaN
andinvalidOperation
istrue
.
Implements specto-number
.Parameters
S
str
The string to convert from
String Spec:
sign ::= + | - digit ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 indicator ::= e | E digits ::= digit [digit]... decimal-part ::= digits . [digits] | [.] digits exponent-part ::= indicator [sign] digits infinity ::= Infinity | Inf nan ::= NaN [digits] numeric-value ::= decimal-part [exponent-part] | infinity numeric-string ::= [sign] numeric-value | [sign] nan
Exceptional Conditions: invalidOperation is flagged when
is not a valid stringstr
-
Declaration
const auto
opBinary
(string op, T)(Trhs
) if (op == "+" || op == "-" || op == "*" || op == "/");
ref Decimal!HookopOpAssign
(string op, T)(auto ref const Trhs
) if (op == "+" || op == "-" || op == "*" || op == "/");Performs a binary operation between two decimals, or a decimal and a built in number.
Discussion
The result has the hook of the left hand side. On non-assignment operations invalid operations do not effect the left hand side of the operation.
When the right hand side is a built-in numeric type, the default hookAbort
is used for its decimal representation.Parameters
T
rhs
the right-hand side of the operation
Exceptional Conditions:
invalidOperation
is flagged under the following conditions- Adding Infinity and -Infinity, and vice-versa
- Multiplying +/-Infinity by +/-zero
- Dividing anything but zero by zero
- Dividing +/-Infinity by +/-Infinity
divisionByZero
is flagged when dividing anything but zero by zero -
Declaration
const int
opCmp
(T)(Td
) if (isNumeric!T || is(Unqual!T == Decimal));-Infinity
is less than all numbers,-NaN
is greater than-Infinity
but less than all other numbers,NaN
is greater than-NaN
but less than all other numbers andInfinity
is greater than all numbers.-NaN
andNaN
are equal to themselves.Parameters
T
d
the decimal or built-in number to compare to
Return Value
Barring special values,
0
if subtracting the two numbers yields0
,-1
if the result is less than0
, and1
if the result is greater than zero -
Declaration
const bool
opEquals
(T)(Td
) if (isNumeric!T || is(Unqual!T == Decimal));Return Value
true
ifopCmp
would return0
-
Declaration
pure nothrow @nogc @safe void
resetFlags
();Convenience function to reset all exceptional condition flags to
false
at once -
Declaration
const Decimal!Hook
dup
()();Return Value
A mutable copy of this
Decimal
. Also copies current flags. -
Declaration
const immutable(Decimal!Hook)
idup
()();Return Value
An immutable copy of this
Decimal
. Also copies current flags. -
Declaration
const pure nothrow @nogc @property @safe bool
isNaN
();Return Value
If this decimal represents a positive or negative NaN
-
Declaration
const pure nothrow @nogc @property @safe bool
isInfinity
();Return Value
If this decimal represents positive or negative infinity
-
Declaration
@property Decimal!Hook
nan
()();Return Value
A decimal representing a positive NaN
-
Declaration
@property Decimal!Hook
infinity
()();Return Value
A decimal representing positive Infinity
-
Declaration
alias
toString
= toDecimalString; -
Declaration
const auto
toDecimalString
();
const voidtoDecimalString
(Writer)(auto ref Writerw
) if (isOutputRange!(Writer, char));Return Value
Returns the decimal string representation of this decimal.
-
-
Declaration
auto
decimal
(Hook = Abort, R)(Rr
) if (isForwardRange!R && isSomeChar!(ElementEncodingType!R) && !isInfinite!R || isNumeric!R);Factory function
Examples
auto d1 = decimal(5.5); assert(d1.toString == "5.5"); auto d2 = decimal("500.555");
-
Declaration
enum
Rounding
: int;Controls what happens when the number of significant digits exceeds
Hook.precision
-
Declaration
Down
Round toward 0, a.k.a truncate. The discarded digits are ignored.
-
Declaration
HalfUp
If the discarded digits represent greater than or equal to half (0.5) of the value of a one in the next left position then the result coefficient should be incremented by 1 (rounded up). Otherwise the discarded digits are ignored.
-
Declaration
HalfEven
If the discarded digits represent greater than half (0.5) the value of a one in the next left position then the result coefficient should be incremented by 1 (rounded up). If they represent less than half, then the result coefficient is not adjusted (that is, the discarded digits are ignored).
Discussion
Otherwise (they represent exactly half) the result coefficient is unaltered if its rightmost digit is even, or incremented by 1 (rounded up) if its rightmost digit is odd (to make an even digit).
-
Declaration
Ceiling
If all of the discarded digits are zero or if the sign is 1 the result is unchanged. Otherwise, the result coefficient should be incremented by 1 (rounded up).
-
Declaration
Floor
If all of the discarded digits are zero or if the sign is 0 the result is unchanged. Otherwise, the sign is 1 and the result coefficient should be incremented by 1.
-
Declaration
HalfDown
If the discarded digits represent greater than half (0.5) of the value of a one in the next left position then the result coefficient should be incremented by 1 (rounded up). Otherwise (the discarded digits are 0.5 or less) the discarded digits are ignored.
-
Declaration
Up
(Round away from 0.) If all of the discarded digits are zero the result is unchanged. Otherwise, the result coefficient should be incremented by 1 (rounded up).
-
Declaration
ZeroFiveUp
(Round zero or five away from 0.) The same as round-up, except that rounding up only occurs if the digit to be rounded up is 0 or 5, and after overflow the result is the same as for round-down.
-
-
Declaration
struct
Abort
;Will halt program on division by zero, invalid operations, overflows, and underflows.
Discussion
Has 9 significant digits, rounds half up
-
Declaration
enum Rounding
roundingMode
; -
Declaration
enum uint
precision
;A
precision
of 9 allows all possible the results of +,-,*, and / to fit into aulong
with no issues. -
Declaration
void
onDivisionByZero
(T)(Td
) if (isInstanceOf!(Decimal, T)); -
Declaration
void
onInvalidOperation
(T)(Td
) if (isInstanceOf!(Decimal, T)); -
Declaration
void
onOverflow
(T)(Td
) if (isInstanceOf!(Decimal, T)); -
Declaration
void
onUnderflow
(T)(Td
) if (isInstanceOf!(Decimal, T));
-
-
Declaration
struct
HighPrecision
;Same as abort, but offers 64 significant digits
Note: As noted in the module overview, using 64 significant digits is much slower than
9
or19
.-
Declaration
enum Rounding
roundingMode
; -
Declaration
enum uint
precision
; -
Declaration
void
onDivisionByZero
(T)(Td
) if (isInstanceOf!(Decimal, T)); -
Declaration
void
onInvalidOperation
(T)(Td
) if (isInstanceOf!(Decimal, T)); -
Declaration
void
onOverflow
(T)(Td
) if (isInstanceOf!(Decimal, T)); -
Declaration
void
onUnderflow
(T)(Td
) if (isInstanceOf!(Decimal, T));
-
-
Declaration
struct
Throw
;Will throw exceptions on division by zero, invalid operations, overflows, and underflows
Discussion
Has 9 significant digits, rounds half up
-
Declaration
enum Rounding
roundingMode
; -
Declaration
enum uint
precision
; -
Declaration
void
onDivisionByZero
(T)(Td
) if (isInstanceOf!(Decimal, T)); -
Declaration
void
onInvalidOperation
(T)(Td
) if (isInstanceOf!(Decimal, T)); -
Declaration
void
onOverflow
(T)(Td
) if (isInstanceOf!(Decimal, T)); -
Declaration
void
onUnderflow
(T)(Td
) if (isInstanceOf!(Decimal, T));
-
-
Declaration
struct
NoOp
;Does nothing on invalid operations except the proper flags
Discussion
Has 9 significant digits, rounds half up
-
Declaration
enum Rounding
roundingMode
; -
Declaration
enum uint
precision
;
-
-
Declaration
class
DivisionByZero
: object.Exception;Thrown when using and division by zero occurs
-
Declaration
pure nothrow @nogc @safe this(string
msg
, stringfile
= __FILE__, size_tline
= __LINE__, Throwablenext
= null);Parameters
string
msg
The message for the exception.
string
file
The
file
where the exception occurred.size_t
line
The
line
number where the exception occurred.Throwable
next
The previous exception in the chain of exceptions, if any.
-
Declaration
pure nothrow @nogc @safe this(string
msg
, Throwablenext
, stringfile
= __FILE__, size_tline
= __LINE__);Parameters
string
msg
The message for the exception.
Throwable
next
The previous exception in the chain of exceptions.
string
file
The
file
where the exception occurred.size_t
line
The
line
number where the exception occurred.
-
-
Declaration
class
InvalidOperation
: object.Exception;Thrown when using and an invalid operation occurs
-
Declaration
pure nothrow @nogc @safe this(string
msg
, stringfile
= __FILE__, size_tline
= __LINE__, Throwablenext
= null);Parameters
string
msg
The message for the exception.
string
file
The
file
where the exception occurred.size_t
line
The
line
number where the exception occurred.Throwable
next
The previous exception in the chain of exceptions, if any.
-
Declaration
pure nothrow @nogc @safe this(string
msg
, Throwablenext
, stringfile
= __FILE__, size_tline
= __LINE__);Parameters
string
msg
The message for the exception.
Throwable
next
The previous exception in the chain of exceptions.
string
file
The
file
where the exception occurred.size_t
line
The
line
number where the exception occurred.
-
-
Declaration
class
Overflow
: object.Exception;Thrown when using and overflow occurs
-
Declaration
pure nothrow @nogc @safe this(string
msg
, stringfile
= __FILE__, size_tline
= __LINE__, Throwablenext
= null);Parameters
string
msg
The message for the exception.
string
file
The
file
where the exception occurred.size_t
line
The
line
number where the exception occurred.Throwable
next
The previous exception in the chain of exceptions, if any.
-
Declaration
pure nothrow @nogc @safe this(string
msg
, Throwablenext
, stringfile
= __FILE__, size_tline
= __LINE__);Parameters
string
msg
The message for the exception.
Throwable
next
The previous exception in the chain of exceptions.
string
file
The
file
where the exception occurred.size_t
line
The
line
number where the exception occurred.
-
-
Declaration
class
Underflow
: object.Exception;Thrown when using and underflow occurs
-
Declaration
pure nothrow @nogc @safe this(string
msg
, stringfile
= __FILE__, size_tline
= __LINE__, Throwablenext
= null);Parameters
string
msg
The message for the exception.
string
file
The
file
where the exception occurred.size_t
line
The
line
number where the exception occurred.Throwable
next
The previous exception in the chain of exceptions, if any.
-
Declaration
pure nothrow @nogc @safe this(string
msg
, Throwablenext
, stringfile
= __FILE__, size_tline
= __LINE__);Parameters
string
msg
The message for the exception.
Throwable
next
The previous exception in the chain of exceptions.
string
file
The
file
where the exception occurred.size_t
line
The
line
number where the exception occurred.
-