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 Hooks 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
Hooks 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
boolvariable 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 propagateNaNdue to D'sopCmpsemantics.
Version
v0.5. Still work in progress. For missing features, see README.md
License
.
-
Declaration
structDecimal(Hook = Abort);A exact decimal type, accurate to
Hook.precisiondigits. Designed to be a drop in replacement for floating points.Discussion
Behavior is defined by
Hook. See the module overview for more information.-
Declaration
Hookhook;is a member variable if it has state, or an alias forhookHookotherwise. -
Declaration
boolclamped;
booldivisionByZero;
boolinexact;
boolinvalidOperation;
booloverflow;
boolrounded;
boolsubnormal;
boolunderflow;Public flags
-
Declaration
pure this(T)(const Tnum) if (isNumeric!T);Constructs an exact decimal type from a built in number
Parameters
Tnumthe number to convert to exact decimal
Note: Using
floattypes 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)(Sstr) 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
NaNandinvalidOperationistrue.
Implements specto-number.Parameters
SstrThe 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 autoopBinary(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 hookAbortis used for its decimal representation.Parameters
Trhsthe right-hand side of the operation
Exceptional Conditions:
invalidOperationis 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
divisionByZerois flagged when dividing anything but zero by zero -
Declaration
const intopCmp(T)(Td) if (isNumeric!T || is(Unqual!T == Decimal));-Infinityis less than all numbers,-NaNis greater than-Infinitybut less than all other numbers,NaNis greater than-NaNbut less than all other numbers andInfinityis greater than all numbers.-NaNandNaNare equal to themselves.Parameters
Tdthe decimal or built-in number to compare to
Return Value
Barring special values,
0if subtracting the two numbers yields0,-1if the result is less than0, and1if the result is greater than zero -
Declaration
const boolopEquals(T)(Td) if (isNumeric!T || is(Unqual!T == Decimal));Return Value
trueifopCmpwould return0 -
Declaration
pure nothrow @nogc @safe voidresetFlags();Convenience function to reset all exceptional condition flags to
falseat once -
Declaration
const Decimal!Hookdup()();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 boolisNaN();Return Value
If this decimal represents a positive or negative NaN
-
Declaration
const pure nothrow @nogc @property @safe boolisInfinity();Return Value
If this decimal represents positive or negative infinity
-
Declaration
@property Decimal!Hooknan()();Return Value
A decimal representing a positive NaN
-
Declaration
@property Decimal!Hookinfinity()();Return Value
A decimal representing positive Infinity
-
Declaration
aliastoString= toDecimalString; -
Declaration
const autotoDecimalString();
const voidtoDecimalString(Writer)(auto ref Writerw) if (isOutputRange!(Writer, char));Return Value
Returns the decimal string representation of this decimal.
-
-
Declaration
autodecimal(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
enumRounding: int;Controls what happens when the number of significant digits exceeds
Hook.precision-
Declaration
DownRound toward 0, a.k.a truncate. The discarded digits are ignored.
-
Declaration
HalfUpIf 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
HalfEvenIf 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
CeilingIf 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
FloorIf 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
HalfDownIf 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
structAbort;Will halt program on division by zero, invalid operations, overflows, and underflows.
Discussion
Has 9 significant digits, rounds half up
-
Declaration
enum RoundingroundingMode; -
Declaration
enum uintprecision;A
precisionof 9 allows all possible the results of +,-,*, and / to fit into aulongwith no issues. -
Declaration
voidonDivisionByZero(T)(Td) if (isInstanceOf!(Decimal, T)); -
Declaration
voidonInvalidOperation(T)(Td) if (isInstanceOf!(Decimal, T)); -
Declaration
voidonOverflow(T)(Td) if (isInstanceOf!(Decimal, T)); -
Declaration
voidonUnderflow(T)(Td) if (isInstanceOf!(Decimal, T));
-
-
Declaration
structHighPrecision;Same as abort, but offers 64 significant digits
Note: As noted in the module overview, using 64 significant digits is much slower than
9or19.-
Declaration
enum RoundingroundingMode; -
Declaration
enum uintprecision; -
Declaration
voidonDivisionByZero(T)(Td) if (isInstanceOf!(Decimal, T)); -
Declaration
voidonInvalidOperation(T)(Td) if (isInstanceOf!(Decimal, T)); -
Declaration
voidonOverflow(T)(Td) if (isInstanceOf!(Decimal, T)); -
Declaration
voidonUnderflow(T)(Td) if (isInstanceOf!(Decimal, T));
-
-
Declaration
structThrow;Will throw exceptions on division by zero, invalid operations, overflows, and underflows
Discussion
Has 9 significant digits, rounds half up
-
Declaration
enum RoundingroundingMode; -
Declaration
enum uintprecision; -
Declaration
voidonDivisionByZero(T)(Td) if (isInstanceOf!(Decimal, T)); -
Declaration
voidonInvalidOperation(T)(Td) if (isInstanceOf!(Decimal, T)); -
Declaration
voidonOverflow(T)(Td) if (isInstanceOf!(Decimal, T)); -
Declaration
voidonUnderflow(T)(Td) if (isInstanceOf!(Decimal, T));
-
-
Declaration
structNoOp;Does nothing on invalid operations except the proper flags
Discussion
Has 9 significant digits, rounds half up
-
Declaration
enum RoundingroundingMode; -
Declaration
enum uintprecision;
-
-
Declaration
classDivisionByZero: object.Exception;Thrown when using and division by zero occurs
-
Declaration
pure nothrow @nogc @safe this(stringmsg, stringfile= __FILE__, size_tline= __LINE__, Throwablenext= null);Parameters
stringmsgThe message for the exception.
stringfileThe
filewhere the exception occurred.size_tlineThe
linenumber where the exception occurred.ThrowablenextThe previous exception in the chain of exceptions, if any.
-
Declaration
pure nothrow @nogc @safe this(stringmsg, Throwablenext, stringfile= __FILE__, size_tline= __LINE__);Parameters
stringmsgThe message for the exception.
ThrowablenextThe previous exception in the chain of exceptions.
stringfileThe
filewhere the exception occurred.size_tlineThe
linenumber where the exception occurred.
-
-
Declaration
classInvalidOperation: object.Exception;Thrown when using and an invalid operation occurs
-
Declaration
pure nothrow @nogc @safe this(stringmsg, stringfile= __FILE__, size_tline= __LINE__, Throwablenext= null);Parameters
stringmsgThe message for the exception.
stringfileThe
filewhere the exception occurred.size_tlineThe
linenumber where the exception occurred.ThrowablenextThe previous exception in the chain of exceptions, if any.
-
Declaration
pure nothrow @nogc @safe this(stringmsg, Throwablenext, stringfile= __FILE__, size_tline= __LINE__);Parameters
stringmsgThe message for the exception.
ThrowablenextThe previous exception in the chain of exceptions.
stringfileThe
filewhere the exception occurred.size_tlineThe
linenumber where the exception occurred.
-
-
Declaration
classOverflow: object.Exception;Thrown when using and overflow occurs
-
Declaration
pure nothrow @nogc @safe this(stringmsg, stringfile= __FILE__, size_tline= __LINE__, Throwablenext= null);Parameters
stringmsgThe message for the exception.
stringfileThe
filewhere the exception occurred.size_tlineThe
linenumber where the exception occurred.ThrowablenextThe previous exception in the chain of exceptions, if any.
-
Declaration
pure nothrow @nogc @safe this(stringmsg, Throwablenext, stringfile= __FILE__, size_tline= __LINE__);Parameters
stringmsgThe message for the exception.
ThrowablenextThe previous exception in the chain of exceptions.
stringfileThe
filewhere the exception occurred.size_tlineThe
linenumber where the exception occurred.
-
-
Declaration
classUnderflow: object.Exception;Thrown when using and underflow occurs
-
Declaration
pure nothrow @nogc @safe this(stringmsg, stringfile= __FILE__, size_tline= __LINE__, Throwablenext= null);Parameters
stringmsgThe message for the exception.
stringfileThe
filewhere the exception occurred.size_tlineThe
linenumber where the exception occurred.ThrowablenextThe previous exception in the chain of exceptions, if any.
-
Declaration
pure nothrow @nogc @safe this(stringmsg, Throwablenext, stringfile= __FILE__, size_tline= __LINE__);Parameters
stringmsgThe message for the exception.
ThrowablenextThe previous exception in the chain of exceptions.
stringfileThe
filewhere the exception occurred.size_tlineThe
linenumber where the exception occurred.
-