View source code
Display the source code in std/experimental/checkedint.d from which this page was generated on github.
Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using local clone.

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.max for all operations that would cause an int to overflow toward infinity, and at int.min 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.defaultValue!T is used as the default initializer of the payload.
min If defined, Hook.min!T is used as the minimum value of the payload.
max If defined, Hook.max!T is used as the maximum value of the payload.
hookOpCast If defined, hook.hookOpCast!U(get) 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.hookOpEquals(get, rhs) 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.hookOpCmp(get, rhs) 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.hookOpUnary!op(get) (where op is the operator symbol) is forwarded to for unary operators - and ~. In addition, for unary operators ++ and --, hook.hookOpUnary!op(payload) is called, where payload is a reference to the value wrapped by Checked so the hook can change it.
hookOpBinary If defined, hook.hookOpBinary!op(get, rhs) (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.hookOpBinaryRight!op(lhs, get) (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.onOverflow!op(get) 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.onOverflow!op(get). When a binary operator overflows, the result of hook.onOverflow!op(get, rhs) is returned, but only if Hook does not define hookOpBinary.
hookOpOpAssign If defined, hook.hookOpOpAssign!op(payload, rhs) (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.onLowerBound(value, bound) (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.onUpperBound(value, bound) (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

NameDescription
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

NameDescription
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.CheckFailure. 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.min. For signed integrals, the reserved value is T.max.

Authors

License