Module std.experimental.checkedint
This module defines facilities for efficient checking of integral operations
against overflow, casting with loss of precision, unexpected change of sign,
etc. The checking (and possibly correction) can be done at operation level, for
example opChecked
!"+"(x, y, overflow)
adds two integrals x
and
y
and sets overflow
to true
if an overflow occurred. The flag overflow
(a bool
passed by reference) is not touched if the operation succeeded, so the
same flag can be reused for a sequence of operations and tested at the end.
Issuing individual checked operations is flexible and efficient but often
tedious. The Checked
facility offers encapsulated integral wrappers that
do all checking internally and have configurable behavior upon erroneous
results. For example, Checked!int
is a type that behaves like int
but aborts
execution immediately whenever involved in an operation that produces the
arithmetically wrong result. The accompanying convenience function checked
uses type deduction to convert a value x
of integral type T
to
Checked!T
by means of checked(x)
. For example:
void main()
{
import std .experimental .checkedint, std .stdio;
writeln((checked(5) + 7) .get); // 12
writeln((checked(10) * 1000 * 1000 * 1000) .get); // Overflow
}
Similarly, checked(-1) > uint(0)
aborts execution (even though the built-in
comparison int(-1) > uint(0)
is surprisingly true due to language's
conversion rules modeled after C). Thus, Checked!int
is a virtually drop-in
replacement for int
useable in debug builds, to be replaced by int
in
release mode if efficiency demands it.
Checked
has customizable behavior with the help of a second type parameter,
Hook
. Depending on what methods Hook
defines, core operations on the
underlying integral may be verified for overflow or completely redefined. If
Hook
defines no method at all and carries no state, there is no change in
behavior, i.e. Checked!(int, void)
is a wrapper around int
that adds no
customization at all.
This module provides a few predefined hooks (below) that add useful behavior to
Checked
:
Abort | fails every incorrect operation with a message to stderr followed by a call to assert(0) . It is the default
second parameter, i.e. Checked!short is the same as
Checked!(short, Abort) .
|
Throw | fails every incorrect operation by throwing an exception. |
Warn | prints incorrect operations to stderr
but otherwise preserves the built-in behavior.
|
ProperCompare | fixes the comparison operators == , != , < , <= , > , and >=
to return correct results in all circumstances,
at a slight cost in efficiency. For example,
Checked!(uint, ProperCompare)(1) > -1 is true ,
which is not the case for the built-in comparison. Also, comparing
numbers for equality with floating-point numbers only passes if the
integral can be converted to the floating-point number precisely,
so as to preserve transitivity of equality.
|
WithNaN | reserves a special "Not a Number" (NaN) value akin to the homonym value
reserved for floating-point values. Once a Checked!(X, WithNaN)
gets this special value, it preserves and propagates it until
reassigned. isNaN can be used to query whether the object
is not a number.
|
Saturate | implements saturating arithmetic, i.e. Checked!(int, Saturate)
"stops" at int for all operations that would cause an int to
overflow toward infinity, and at int for all operations that would
correspondingly overflow toward negative infinity.
|
These policies may be used alone, e.g. Checked!(uint, WithNaN)
defines a
uint
-like type that reaches a stable NaN state for all erroneous operations.
They may also be "stacked" on top of each other, owing to the property that a
checked integral emulates an actual integral, which means another checked
integral can be built on top of it. Some combinations of interest include:
Checked!(Checked!int, ProperCompare) |
defines an int with fixed
comparison operators that will fail with assert(0) upon overflow. (Recall that
Abort is the default policy.) The order in which policies are combined is
important because the outermost policy (ProperCompare in this case) has the
first crack at intercepting an operator. The converse combination Checked!(Checked!(int, ProperCompare)) is meaningless because Abort will
intercept comparison and will fail without giving ProperCompare a chance to
intervene.
|
Checked!(Checked!(int, ProperCompare), WithNaN) |
defines an int -like
type that supports a NaN value. For values that are not NaN, comparison works
properly. Again the composition order is important; Checked!(Checked!(int,
WithNaN), ProperCompare) does not have good semantics because ProperCompare
intercepts comparisons before the numbers involved are tested for NaN.
|
The hook's members are looked up statically in a Design by Introspection manner
and are all optional. The table below illustrates the members that a hook type
may define and their influence over the behavior of the Checked
type using it.
In the table, hook
is an alias for Hook
if the type Hook
does not
introduce any state, or an object of type Hook
otherwise.
Hook member | Semantics in Checked!(T, Hook) |
---|---|
defaultValue | If defined, Hook is used as the
default initializer of the payload. |
min | If defined, Hook is used as the minimum value of
the payload. |
max | If defined, Hook is used as the maximum value of
the payload. |
hookOpCast | If defined, hook is forwarded
to unconditionally when the payload is to be cast to type U . |
onBadCast | If defined and hookOpCast is not defined,
onBadCast!U(get) is forwarded to when the payload is to be cast to type U
and the cast would lose information or force a change of sign. |
hookOpEquals | If defined, hook is
forwarded to unconditionally when the payload is compared for equality against
value rhs of integral, floating point, or Boolean type. |
hookOpCmp | If defined, hook is
forwarded to unconditionally when the payload is compared for ordering against
value rhs of integral, floating point, or Boolean type. |
hookOpUnary | If defined, hook (where op
is the operator symbol) is forwarded to for unary operators - and ~ . In
addition, for unary operators ++ and -- , hook is
called, where payload is a reference to the value wrapped by Checked so the
hook can change it. |
hookOpBinary | If defined, hook
(where op is the operator symbol and rhs is the right-hand side operand) is
forwarded to unconditionally for binary operators + , - , * , / , % ,
^^ , & , | , ^ , << , >> , and >>> . |
hookOpBinaryRight | If defined, hook (where op is the operator symbol and
lhs is the left-hand side operand) is forwarded to unconditionally for binary
operators + , - , * , / , % , ^^ , & , | , ^ , << , >> , and >>> . |
onOverflow | If defined, hook is forwarded
to for unary operators that overflow but only if hookOpUnary is not defined.
Unary ~ does not overflow; unary - overflows only when the most negative
value of a signed type is negated, and the result of the hook call is returned.
When the increment or decrement operators overflow, the payload is assigned the
result of hook . When a binary operator overflows, the
result of hook is returned, but only if Hook does
not define hookOpBinary . |
hookOpOpAssign | If defined, hook (where op is the operator symbol and rhs is the right-hand side
operand) is forwarded to unconditionally for binary operators += , -= , *= , /= , %= ,
^^= , &= , |= , ^= , <<= , >>= , and >>>= . |
onLowerBound | If defined, hook
(where value is the value being assigned) is forwarded to when the result of
binary operators += , -= , *= , /= , %= , ^^= , &= , |= , ^= , <<= , >>= ,
and >>>= is smaller than the smallest value representable by T . |
onUpperBound | If defined, hook
(where value is the value being assigned) is forwarded to when the result of
binary operators += , -= , *= , /= , %= , ^^= , &= , |= , ^= , <<= , >>= ,
and >>>= is larger than the largest value representable by T . |
Example
int[] concatAndAdd(int[] a, int[] b, int offset)
{
// Aborts on overflow on size computation
auto r = new int[(checked(a .length) + b .length) .get];
// Aborts on overflow on element computation
foreach (i; 0 .. a .length)
r[i] = (a[i] + checked(offset)) .get;
foreach (i; 0 .. b .length)
r[i + a .length] = (b[i] + checked(offset)) .get;
return r;
}
writeln(concatAndAdd([1, 2, 3], [4, 5], -1)); // [0, 1, 2, 3, 4]
Example
Saturate
stops at an overflow
auto x = (cast(byte) 127) .checked!Saturate;
writeln(x); // 127
x++;
writeln(x); // 127
Example
WithNaN
has a special "Not a Number" (NaN) value akin to the homonym value reserved for floating-point values
auto x = 100 .checked!WithNaN;
writeln(x); // 100
x /= 0;
assert(x .isNaN);
Example
ProperCompare
fixes the comparison operators ==, !=, <, <=, >, and >= to return correct results
uint x = 1;
auto y = x .checked!ProperCompare;
assert(x < -1); // built-in comparison
assert(y > -1); // ProperCompare
Example
Throw
fails every incorrect operation by throwing an exception
import std .exception : assertThrown;
auto x = -1 .checked!Throw;
assertThrown(x / 0);
assertThrown(x + int .min);
assertThrown(x == uint .max);
Functions
Name | Description |
---|---|
checked(value)
|
Convenience function that turns an integral into the corresponding Checked
instance by using template argument deduction. The hook type may be specified
(by default Abort ).
|
isNaN(x)
|
Queries whether a Checked!(T, WithNaN) object is not a number (NaN).
|
opChecked(lhs, rhs, overflow)
|
Defines binary operations with overflow checking for any two integral types.
The result type obeys the language rules (even when they may be
counterintuitive), and overflow is set if an overflow occurs (including
inadvertent change of signedness, e.g. -1 is converted to uint ).
Conceptually the behavior is:
|
Structs
Name | Description |
---|---|
Abort
|
Force all integral errors to fail by printing an error message to stderr and
then abort the program. Abort is the default second argument for Checked .
|
Checked
|
Checked integral type wraps an integral T and customizes its behavior with the
help of a Hook type. The type wrapped must be one of the predefined integrals
(unqualified), or another instance of Checked .
|
ProperCompare
|
Hook that provides arithmetically correct comparisons for equality and ordering.
Comparing an object of type Checked!(X, ProperCompare) against another
integral (for equality or ordering) ensures that no surprising conversions from
signed to unsigned integral occur before the comparison. Using Checked!(X,
ProperCompare) on either side of a comparison for equality against a
floating-point number makes sure the integral can be properly converted to the
floating point type, thus making sure equality is transitive.
|
Saturate
|
Hook that implements saturation, i.e. any arithmetic operation that would
overflow leaves the result at its extreme value (min or max depending on the
direction of the overflow).
|
Throw
|
Force all integral errors to fail by throwing an exception of type
Throw . The message coming with the error is similar to the one
printed by Warn .
|
Warn
|
Hook that prints to stderr a trace of all integral errors, without affecting
default behavior.
|
WithNaN
|
Hook that reserves a special value as a "Not a Number" representative. For
signed integrals, the reserved value is T . For signed integrals, the
reserved value is T .
|