Safe Numerics
Safe Numerics
Safe Numerics
Robert Ramey
Copyright © 2012-2018 Robert Ramey
Table of Contents
1. Introduction ...................................................................................................................................................... 4
1.1. Problem ................................................................................................................................................ 4
1.2. Solution ................................................................................................................................................ 5
1.3. How It Works ........................................................................................................................................ 5
1.4. Additional Features ................................................................................................................................. 6
1.5. Requirements ......................................................................................................................................... 6
1.6. Scope ................................................................................................................................................... 6
2. Tutorial and Motivating Examples ....................................................................................................................... 7
2.1. Arithmetic Expressions Can Yield Incorrect Results ..................................................................................... 7
2.2. Arithmetic Operations Can Overflow Silently .............................................................................................. 8
2.3. Arithmetic on Unsigned Integers Can Yield Incorrect Results ......................................................................... 9
2.4. Implicit Conversions Can Lead to Erroneous Results .................................................................................. 10
2.5. Mixing Data Types Can Create Subtle Errors ............................................................................................ 12
2.6. Array Index Value Can Exceed Array Limits ............................................................................................. 13
2.7. Checking of Input Values Can Be Easily Overlooked .................................................................................. 14
2.8. Cannot Recover From Arithmetic Errors ................................................................................................... 15
2.9. Compile Time Arithmetic is Not Always Correct ....................................................................................... 16
2.10. Programming by Contract is Too Slow ................................................................................................... 17
3. Eliminating Runtime Penalty ............................................................................................................................. 19
3.1. Using safe_range and safe_literal ............................................................................................................. 20
3.2. Using Automatic Type Promotion ............................................................................................................ 22
3.3. Mixing Approaches ............................................................................................................................... 24
4. Case Studies ................................................................................................................................................... 26
4.1. Composition with Other Libraries ............................................................................................................ 26
4.2. Safety Critical Embedded Controller ........................................................................................................ 28
How a Stepper Motor Works ................................................................................................................ 28
Updating the Code .............................................................................................................................. 29
Refactor for Testing ............................................................................................................................ 30
Compiling on the Desktop .................................................................................................................... 30
Trapping Errors at Compile Time .......................................................................................................... 35
Summary ........................................................................................................................................... 45
5. Background .................................................................................................................................................... 46
6. Type Requirements .......................................................................................................................................... 46
6.1. Numeric<T> ......................................................................................................................................... 46
Description ......................................................................................................................................... 46
Notation ............................................................................................................................................ 47
Associated Types ................................................................................................................................ 47
Valid Expressions ............................................................................................................................... 47
Models .............................................................................................................................................. 49
Header ............................................................................................................................................... 49
Note on Usage of std::numeric_limits ............................................................................................. 49
6.2. Integer<T> ........................................................................................................................................... 49
1
Safe Numerics
Description ......................................................................................................................................... 49
Refinement of ..................................................................................................................................... 50
Notation ............................................................................................................................................ 50
Valid Expressions ............................................................................................................................... 50
Models .............................................................................................................................................. 51
Header ............................................................................................................................................... 51
6.3. PromotionPolicy<PP> ............................................................................................................................ 51
Description ......................................................................................................................................... 51
Notation ............................................................................................................................................ 51
Valid Expressions ............................................................................................................................... 52
Models .............................................................................................................................................. 52
Header ............................................................................................................................................... 53
6.4. ExceptionPolicy<EP> ............................................................................................................................ 53
Description ......................................................................................................................................... 53
Notation ............................................................................................................................................ 54
Valid Expressions ............................................................................................................................... 54
dispatch<EP>(const safe_numerics_error & e, const char * msg) ................................................................. 54
Models .............................................................................................................................................. 54
Header ............................................................................................................................................... 55
7. Types ............................................................................................................................................................ 55
7.1. safe<T, PP, EP> ................................................................................................................................... 55
Description ......................................................................................................................................... 55
Notation ............................................................................................................................................ 55
Associated Types ................................................................................................................................ 55
Template Parameters ............................................................................................................................ 56
Model of ........................................................................................................................................... 56
Valid Expressions ............................................................................................................................... 56
Examples of use ................................................................................................................................. 56
Header ............................................................................................................................................... 59
7.2. safe_signed_range<MIN, MAX, PP, EP> and safe_unsigned_range<MIN, MAX, PP, EP> ................................. 59
Description ......................................................................................................................................... 59
Notation ............................................................................................................................................ 59
Associated Types ................................................................................................................................ 60
Template Parameters ............................................................................................................................ 60
Model of ........................................................................................................................................... 60
Valid Expressions ............................................................................................................................... 60
Example of use ................................................................................................................................... 60
Header ............................................................................................................................................... 62
7.3. safe_signed_literal<Value, PP , EP> and safe_unsigned_literal<Value, PP, EP> ............................................... 62
Description ......................................................................................................................................... 62
Associated Types ................................................................................................................................ 62
Template Parameters ............................................................................................................................ 62
Model of ........................................................................................................................................... 62
Inherited Valid Expressions .................................................................................................................. 62
Example of use ................................................................................................................................... 63
make_safe_literal(n, PP, EP) ..................................................................................................... 63
Header ............................................................................................................................................... 63
7.4. exception ............................................................................................................................................. 63
Description ......................................................................................................................................... 63
enum class safe_numerics_error ............................................................................................................. 63
enum class safe_numerics_actions .......................................................................................................... 64
See Also ............................................................................................................................................ 64
Header ............................................................................................................................................... 64
7.5. exception_policy<AE, IDB, UB, UV> ...................................................................................................... 65
2
Safe Numerics
Description ......................................................................................................................................... 65
Notation ............................................................................................................................................ 65
Template Parameters ............................................................................................................................ 65
Model of ........................................................................................................................................... 65
Inherited Valid Expressions .................................................................................................................. 65
Function Objects ................................................................................................................................. 65
Policies Provided by the library ............................................................................................................. 66
Header ............................................................................................................................................... 67
7.6. Promotion Policies ................................................................................................................................ 67
native ................................................................................................................................................ 67
automatic ........................................................................................................................................... 68
cpp<int C, int S, int I, int L, int LL> ..................................................................................................... 69
8. Exception Safety ............................................................................................................................................. 70
9. Library Implementation .................................................................................................................................... 70
9.1. checked_result<R> ................................................................................................................................ 72
Description ......................................................................................................................................... 72
Notation ............................................................................................................................................ 72
Template Parameters ............................................................................................................................ 73
Model of ........................................................................................................................................... 73
Valid Expressions ............................................................................................................................... 73
Example of use ................................................................................................................................... 74
See Also ............................................................................................................................................ 74
Header ............................................................................................................................................... 74
9.2. Checked Arithmetic ............................................................................................................................... 74
Description ......................................................................................................................................... 74
Type requirements ............................................................................................................................... 75
Complexity ........................................................................................................................................ 75
Example of use ................................................................................................................................... 75
Notes ................................................................................................................................................ 75
Synopsis ............................................................................................................................................ 75
See Also ............................................................................................................................................ 76
Header ............................................................................................................................................... 76
9.3. interval<R> .......................................................................................................................................... 76
Description ......................................................................................................................................... 76
Template Parameters ............................................................................................................................ 76
Notation ............................................................................................................................................ 77
Associated Types ................................................................................................................................ 77
Valid Expressions ............................................................................................................................... 77
Example of use ................................................................................................................................... 78
Header ............................................................................................................................................... 79
9.4. safe_compare<T, U> ............................................................................................................................. 79
Synopsis ............................................................................................................................................ 79
Description ......................................................................................................................................... 79
Type requirements ............................................................................................................................... 79
Complexity ........................................................................................................................................ 79
Example of use ................................................................................................................................... 79
Header ............................................................................................................................................... 80
10. Performance Tests .......................................................................................................................................... 80
11. Rationale and FAQ ........................................................................................................................................ 80
12. Pending Issues ............................................................................................................................................... 83
12.1. safe_base Only Works for Scalar Types ............................................................................................... 83
12.2. Other Pending Issues ........................................................................................................................... 84
13. Acknowledgements ........................................................................................................................................ 84
14. Release Log .................................................................................................................................................. 85
3
Safe Numerics
1. Introduction
This library is intended as a drop-in replacement for all built-in integer types in any program which must:
1.1. Problem
Arithmetic operations in C/C++ are NOT guaranteed to yield a correct mathematical result. This feature is inherited from the
early days of C. The behavior of int, unsigned int and others were designed to map closely to the underlying hardware.
Computer hardware implements these types as a fixed number of bits. When the result of arithmetic operations exceeds this
number of bits, the result will not be arithmetically correct. The following example illustrates just one example where this causes
problems.
It is incumbent upon the C/C++ programmer to guarantee that this behavior does not result in incorrect or unexpected operation
of the program. There are no language facilities which implement such a guarantee. A programmer needs to examine each
expression individually to know that his program will not return an invalid result. There are a number of ways to do this. In the
above instance, [INT32-C] seems to recommend the following approach:
This will indeed trap the error. However, it would be tedious and laborious for a programmer to alter his code in this manner.
Altering code in this way for all arithmetic operations would likely render the code unreadable and add another source of potential
programming errors. This approach is clearly not functional when the expression is even a little more complex as is shown in the
following example.
This example addresses only the problem of undefined/erroneous behavior related to overflow of the addition operation as applied
to the type int. Similar problems occur with other built-in integer types such as unsigned, long, etc. And it also applies to
other operations such as subtraction, multiplication etc. . C/C++ often automatically and silently converts some integer types
to others in the course of implementing binary operations. Sometimes such conversions can silently change arithmetic values
which inject errors. The C/C++ standards designate some behavior such as right shifting a negative number as "implementation
defined behavior". These days machines usually do what the programmer expects - but such behavior is not guaranteed. Relying
4
Safe Numerics
on such behavior will create a program which cannot be guaranteed to be portable. And then there is undefined behavior. In this
case, compiler writer is under no obligation to do anything in particular. Sometimes this will unexpectedly break the program.
At the very least, the program is rendered non-portable. Finally there is the case of behavior that is arithmetically wrong to begin
with - for example divide by zero. Some runtime environments will just terminate the program, others may throw some sort of
exception. In any case, the execution has failed in a manner from which there is no recovery.
All of the above conditions are obstacles to creation of a program which will never fail. The Safe Numerics Library addresses all
of these conditions, at least as far as integer operations are concerned.
Since the problems and their solution are similar, we'll confine the current discussion to just the one example shown above.
1.2. Solution
This library implements special versions of int, unsigned, etc. which behave exactly like the original ones except that the
results of these operations are guaranteed to be either to be arithmetically correct or invoke an error. Using this library, the above
example would be rendered as:
#include <boost/safe_numerics/safe_integer.hpp>
using namespace boost::safe_numerics;
safe<int> f(safe<int> x, safe<int> y){
return x + y; // throw exception if correct result cannot be returned
}
Note
Library code in this document resides in the namespace boost::safe_numerics. This namespace has
generally been eliminated from text, code and examples in order to improve readability of the text.
The addition expression is checked at runtime or (if possible) at compile time to trap any possible errors resulting in incorrect
arithmetic behavior. Arithmetic expressions will not produce an erroneous result. Instead, one and only one of the following is
guaranteed to occur.
In other words, the library absolutely guarantees that no integer arithmetic expression will yield incorrect results.
Note that the library addresses arithmetical errors generated by straightforward C/C++ expressions. Some of these arithmetic
errors are defined as conforming to the C/C++ standards while others are not. So characterizing this library as only addressing
undefined behavior of C/C++ numeric expressions would be misleading.
Facilities particular to C++14 are employed to minimize any runtime overhead. In many cases there is no runtime overhead at all.
In other cases, a program using the library can be slightly altered to achieve the above guarantee without any runtime overhead.
5
Safe Numerics
• Trap at compile time all operations which could possibly fail at runtime.
• Specify custom functions which should be called in case errors are detected at runtime.
• Select or define a promotion policy class to alter the C/C++ type promotion rules. This can be used to
• Use C/C++ native type promotion rules so that, except for throwing/trapping of exceptions on operations resulting in
incorrect arithmetic behavior, programs will operate identically when using/not using safe types. This might be used if safe
types are only enabled during debug and testing.
• Replace C/C++ native promotion rules with ones which are arithmetically equivalent but minimize the need for runtime
checking of arithmetic results. Such a policy will effectively change the semantics of a C++ program. It's not really C++ any
more. The program cannot be expected to function the same as when normal integer types are used.
• Replace C/C++ native promotion rules with ones which emulate other machine architectures. This is designed to permit the
testing of C/C++ code destined to be run on another machine on one's development platform. Such a situation often occurs
while developing code for embedded systems.
• Enforce other program requirements using bounded integer types. The library includes the types for ranges and literals.
Operations which violate these requirements will be trapped at either compile time or runtime and not silently return invalid
values. These types can be used to improve program correctness and performance.
1.5. Requirements
This library is composed entirely of C++ Headers. It requires a compiler compatible with the C++14 standard.
The following Boost Libraries must be installed in order to use this library
• mp11
• integer
• config
• tribool
• enable_if
• type_traits
The Safe Numerics library is delivered with an exhaustive suite of test programs.
1.6. Scope
This library currently applies only to built-in integer types. Analogous issues arise for floating point types but they are not
currently addressed by this version of the library. User or library defined types such as arbitrary precision integers can also have
6
Safe Numerics
this problem. Extension of this library to these other types is not currently under development but may be addressed in the future.
This is one reason why the library name is "safe numeric" rather than "safe integer" library.
This program demonstrates this problem. The solution is to replace instances of built in integer types with corresponding safe
types.
#include <iostream>
#include <boost/safe_numerics/safe_integer.hpp>
7
Safe Numerics
}
return 0;
}
#include <iostream>
#include <boost/safe_numerics/safe_integer.hpp>
8
Safe Numerics
When variables of unsigned integer type are decremented below zero, they "roll over" to the highest possible unsigned version of
that integer type. This is a common problem which is generally never detected.
This program demonstrates this problem. The solution is to replace instances of built in integer types with corresponding safe
types.
#include <iostream>
#include <boost/safe_numerics/safe_integer.hpp>
9
Safe Numerics
safe<unsigned int> y = 2;
safe<unsigned int> z;
// rather than producing an invalid result an exception is thrown
z = y - x;
std::cout << "error NOT detected!" << std::endl;
std::cout << z << " != " << y << " - " << x << std::endl;
}
catch(const std::exception & e){
// which we can catch here
std::cout << "error detected:" << e.what() << std::endl;
}
return 0;
}
The talk included a very, very simple example similar to the following:
#include <iostream>
#include <boost/safe_numerics/safe_integer.hpp>
int main(){
std::cout << "example 4: ";
std::cout << "implicit conversions change data values" << std::endl;
std::cout << "Not using safe numerics" << std::endl;
10
Safe Numerics
// solution: replace int with safe<int> and unsigned int with safe<unsigned int>
std::cout << "Using safe numerics" << std::endl;
try{
using namespace boost::safe_numerics;
safe<signed int> a{-1};
safe<unsigned int> b{1};
std::cout << "a is " << a << " b is " << b << '\n';
if(a < b){
std::cout << "a is less than b\n";
}
else{
std::cout << "b is less than a\n";
}
std::cout << "error NOT detected!" << std::endl;
return 1;
}
catch(const std::exception & e){
// never arrive here - just produce the correct answer!
std::cout << e.what() << std::endl;
std::cout << "error detected!" << std::endl;
}
return 0;
}
A normal person reads the above code and has to be dumbfounded by this. The code doesn't do what the text - according to the
rules of algebra - says it does. But C++ doesn't follow the rules of algebra - it has its own rules. There is generally no compile
time error. You can get a compile time warning if you set some specific compile time switches. The explanation lies in reviewing
how C++ reconciles binary expressions (a < b is an expression here) where operands are different types. In processing this
expression, the compiler:
• Determines the "best" common type for the two operands. In this case, application of the rules in the C++ standard dictate that
this type will be an unsigned int.
• Converts each operand to this common type. The signed value of -1 is converted to an unsigned value with the same bit-wise
contents, 0xFFFFFFFF, on a machine with 32 bit integers. This corresponds to a decimal value of 4294967295.
• Performs the calculation - in this case it's <, the "less than" operation. Since 1 is less than 4294967295 the program prints "b is
less than a".
In order for a programmer to detect and understand this error he should be pretty familiar with the implicit conversion rules of
the C++ standard. These are available in a copy of the standard and also in the canonical reference book The C++ Programming
Language (both are over 1200 pages long!). Even experienced programmers won't spot this issue and know to take precautions to
11
Safe Numerics
avoid it. And this is a relatively easy one to spot. In the more general case this will use integers which don't correspond to easily
recognizable numbers and/or will be buried as a part of some more complex expression.
This example generated a good amount of web traffic along with everyone's pet suggestions. See for example a blog post with
everyone's favorite "solution". All the proposed "solutions" have disadvantages and attempts to agree on how handle this are
ultimately fruitless in spite of, or maybe because of, the emotional content. Our solution is by far the simplest: just use the safe
numerics library as shown in the example above.
Note that in this particular case, usage of the safe types results in no runtime overhead in using the safe integer library. Code
generated will either equal or exceed the efficiency of using primitive integer types.
#include <iostream>
#include <cstdint>
#include <boost/safe_numerics/safe_integer.hpp>
int main(){
cout << "example 4: ";
cout << "mixing types produces surprising results" << endl;
try {
std::cout << "Not using safe numerics" << std::endl;
// problem: mixing types produces surprising results.
f(100, 100); // works as expected
f(100, -100); // wrong result - unnoticed
cout << "error NOT detected!" << endl;;
}
catch(const std::exception & e){
// never arrive here
cout << "error detected:" << e.what() << endl;;
}
try {
// solution: use safe types
12
Safe Numerics
#include <stdexcept>
#include <iostream>
#include <array>
#include <boost/safe_numerics/safe_integer_range.hpp>
13
Safe Numerics
Collections like standard arrays and vectors do array index checking in some function calls and not in others so this may not
be the best example. However it does illustrate the usage of safe_range<T> for assigning legal ranges to variables. This will
guarantee that under no circumstances will the variable contain a value outside of the specified range.
#include <stdexcept>
#include <sstream>
#include <iostream>
#include <boost/safe_numerics/safe_integer.hpp>
try{
int x, y;
is >> x >> y; // get integer values from the user
std::cout << x << ' ' << y << std::endl;
14
Safe Numerics
Without safe integer, one will have to insert new code every time an integer variable is retrieved. This is a tedious and error prone
procedure. Here we have used program input. But in fact this problem can occur with any externally produced input.
• invocation of some configuration functions which convert these hardware events into C++ exceptions
It's not all that clear how one would detect and recover from a divide by zero error in a simple portable way. Usually, users just
ignore the issue which usually results in immediate program termination when this situation occurs.
This library will detect divide by zero errors before the operation is invoked. Any errors of this nature are handled according to
the exception_policy selected by the library user.
15
Safe Numerics
#include <stdexcept>
#include <iostream>
#include <boost/safe_numerics/safe_integer.hpp>
try{
// can't do this as it will crash the program with no
// opportunity for recovery - comment out for example
// int x = 1;
// int y = 0;
// std::cout << x / y;
std::cout << "error cannot be handled at runtime!" << std::endl;
}
catch(const std::exception &){
std::cout << "error handled at runtime!" << std::endl;
}
// solution: replace int with safe<int>
std::cout << "Using safe numerics" << std::endl;
try{
using namespace boost::safe_numerics;
const safe<int> x = 1;
const safe<int> y = 0;
std::cout << x / y;
std::cout << "error NOT detected!" << std::endl;
}
catch(const std::exception & e){
std::cout << "error handled at runtime!" << e.what() << std::endl;
}
return 0;
}
• The compiler emits a warning but otherwise calculates the wrong result.
• Replacing int with safe<int> will guarantee that the error is detected at runtime
• Operations using safe types are marked constexpr. So we can force the operations to occur at runtime by marking the results as
constexpr. This will result in an error at compile time if the operations cannot be correctly calculated.
16
Safe Numerics
#include <stdexcept>
#include <iostream>
#include <boost/safe_numerics/safe_integer.hpp>
try{
const int x = 1;
const int y = 0;
// will emit warning at compile time
// will leave an invalid result at runtime.
std::cout << x / y; // will display "0"!
std::cout << "error NOT detected!" << std::endl;
}
catch(const std::exception &){
std::cout << "error detected!" << std::endl;
}
// solution: replace int with safe<int>
std::cout << "Using safe numerics" << std::endl;
try{
using namespace boost::safe_numerics;
const safe<int> x = 1;
const safe<int> y = 0;
// constexpr const safe<int> z = x / y; // note constexpr here!
std::cout << x / y; // error would be detected at runtime
std::cout << " error NOT detected!" << std::endl;
}
catch(const std::exception & e){
std::cout << "error detected:" << e.what() << std::endl;
}
return 0;
}
17
Safe Numerics
only during debugging and testing which defeats the guarantee of correctness which we are seeking here! Programming by
Contract will never be accepted by programmers as long as it is associated with significant additional runtime cost.
The Safe Numerics Library has facilities which, in many cases, can check guaranteed parameter requirements with little or no
runtime overhead. Consider the following example:
#include <cassert>
#include <stdexcept>
#include <sstream>
#include <iostream>
#include <boost/safe_numerics/safe_integer_range.hpp>
18
Safe Numerics
try {
total_minutes = test3(17, 83);
std::cout << "total minutes = " << total_minutes << std::endl;
}
catch(const std::exception & e){
std::cout << "parameter error detected" << std::endl;
}
try {
total_minutes = test3(17, 10);
std::cout << "total minutes = " << total_minutes << std::endl;
}
catch(const std::exception & e){
// should never arrive here
std::cout << "parameter error erroneously detected" << std::endl;
return 1;
}
return 0;
}
example 7:
enforce contracts with zero runtime cost
parameter error detected
In the example above, the function convert incurs significant runtime cost every time the function is called. By using "safe"
types, this cost is moved to the moment when the parameters are constructed. Depending on how the program is constructed, this
may totally eliminate extraneous computations for parameter requirement type checking. In this scenario, there is no reason to
suppress the checking for release mode and our program can be guaranteed to be always arithmetically correct.
The first step is to determine what parts of a program might invoke exceptions. The following program is similar to previous
examples but uses a special exception policy: loose_trap_policy.
19
Safe Numerics
#include <iostream>
#include <boost/safe_numerics/safe_integer.hpp>
#include <boost/safe_numerics/exception_policies.hpp> // include exception policies
int main(){
std::cout << "example 81:\n";
safe_t x(INT_MAX);
safe_t y(2);
safe_t z = x + y; // will fail to compile !
return 0;
}
Now, any expression which might fail at runtime is flagged with a compile time error. There is no longer any need for try/
catch blocks. Since this program does not compile, the library absolutely guarantees that no arithmetic expression will yield
incorrect results. Furthermore, it is absolutely guaranteed that no exception will ever be thrown. This is our original goal.
Now all we need to do is make the program compile. There are a couple of ways to achieve this.
#include <iostream>
#include <boost/safe_numerics/safe_integer_range.hpp>
#include <boost/safe_numerics/safe_integer_literal.hpp>
#include <boost/safe_numerics/exception.hpp>
#include <boost/safe_numerics/native.hpp>
#include "safe_format.hpp" // prints out range and value of any type
20
Safe Numerics
// We "know" that C++ type promotion rules will work such that
// addition will never overflow. If we change the program to break this,
// the usage of the loose_trap_policy promotion policy will prevent compilation.
int main(int, const char *[]){
std::cout << "example 83:\n";
• safe_signed_range defines a type which is limited to the indicated range. Out of range assignments will be detected at
compile time if possible (as in this case) or at run time if necessary.
• A safe range could be defined with the same minimum and maximum value effectively restricting the type to holding one
specific value. This is what safe_signed_literal does.
• Defining constants with safe_signed_literal enables the library to correctly anticipate the correct range of the results of
arithmetic expressions at compile time.
• The usage of loose_trap_policy will mean that any assignment to z which could be outside its legal range will result in a
compile time error.
• All safe integer operations are implemented as constant expressions. The usage of constexpr will guarantee that z will be
available at compile time for any subsequent use.
The output uses a custom output manipulator, safe_format, for safe types to display the underlying type and its range as well as
current value. This program produces the following run time output.
example 83:
x = <signed char>[10,10] = 10
y = <signed char>[67,67] = 67
x + y = <int>[77,77] = 77
z = <signed char>[-24,82] = 77
21
Safe Numerics
• x and y are safe types with fixed ranges which encompass one single value. They can hold only that value which they have
been assigned at compile time.
• The type of z is defined so that It can hold only values in the closed range -24,82. We can assign the sum of x + y because it is
in the range that z is guaranteed to hold. If the sum could not be be guaranteed to fall in the range of z, we would get a compile
time error due to the fact we are using the loose_trap_policy exception policy.
All this information regarding the range and values of variables has been determined at compile time. There is no runtime
overhead. The usage of safe types does not alter the calculations or results in anyway. So safe_t and const_safe_t could be
redefined to int and const int respectively and the program would operate identically - although it might We could compile
the program for another machine - as is common when building embedded systems and know (assuming the target machine
architecture was the same as our native one) that no erroneous results would ever be produced.
• if the size of the signed operand is larger than the size of the signed operand, the type of the result will be signed. Otherwise,
the type of the result will be unsigned.
• Convert the type each operand to the type of the result, expanding the size as necessary.
So the type of the result of some binary operation may be different than the types of either or both of the original operands.
If the values are large, the result can exceed the size that the resulting integer type can hold. This is what we call "overflow".
The C/C++ standard characterizes this as undefined behavior and leaves to compiler implementors the decision as to how such a
situation will be handled. Usually, this means just truncating the result to fit into the result type - which sometimes will make the
result arithmetically incorrect. However, depending on the compiler and compile time switch settings, such cases may result in
some sort of run time exception or silently producing some arbitrary result.
template <
class T, // underlying integer type
class P = native, // type promotion policy class
class E = default_exception_policy // error handling policy class
>
safe;
The promotion rules for arithmetic operations are implemented in the default native type promotion policy are consistent with
those of standard C++
Up until now, we've focused on detecting when an arithmetic error occurs and invoking an exception or other kind of error
handler.
But now we look at another option. Using the automatic type promotion policy, we can change the rules of C++ arithmetic for
safe types to something like the following:
22
Safe Numerics
• for any C++ numeric type, we know from std::numeric_limits what the maximum and minimum values that a variable
can be - this defines a closed interval.
• For any binary operation on these types, we can calculate the interval of the result at compile time.
• From this interval we can select a new type which can be guaranteed to hold the result and use this for the calculation. This is
more or less equivalent to the following code:
int x, y;
int z = x + y // could overflow
// so replace with the following:
int x, y;
long z = (long)x + (long)y; // can never overflow
One could do this by editing his code manually as above, but such a task would be tedious, error prone, non-portable and leave
the resulting code hard to read and verify. Using the automatic type promotion policy will achieve the equivalent result
without these problems.
When using the automatic type promotion policy, with a given a binary operation, we silently promote the types of the operands
to a wider result type so the result cannot overflow. This is a fundamental departure from the C++ Standard behavior.
If the interval of the result cannot be guaranteed to fit in the largest type that the machine can handle (usually 64 bits these days),
the largest available integer type with the correct result sign is used. So even with our "automatic" type promotion scheme, it's
still possible to overflow. So while our automatic type promotion policy might eliminate exceptions in our example above, it
wouldn't be guaranteed to eliminate them for all programs.
Using the loose_trap_policy exception policy will produce a compile time error anytime it's possible for an error to occur.
This small example illustrates how to use automatic type promotion to eliminate all runtime penalty.
#include <iostream>
#include <boost/safe_numerics/safe_integer.hpp>
#include <boost/safe_numerics/exception_policies.hpp>
#include <boost/safe_numerics/automatic.hpp>
#include "safe_format.hpp" // prints out range and value of any type
23
Safe Numerics
• the automatic type promotion policy has rendered the result of the sum of two integers as a safe<long> type.
• our program compiles without error - even when using the loose_trap_policy exception policy. This is because since a
long can always hold the result of the sum of two integers.
• We do not need to use the try/catch idiom to handle arithmetic errors - we will have no exceptions.
• We only needed to change two lines of code to achieve our goal of guaranteed program correctness with no runtime penalty.
example 82:
x = <int>[-2147483648,2147483647] = 2147483647
y = <int>[-2147483648,2147483647] = 2
x + y = <long>[-4294967296,4294967294] = 2147483649
Note that if any time in the future we were to change safe<int> to safe<long long> the program could now overflow. But
since we're using loose_trap_policy the modified program would fail to compile. At this point we'd have to alter our yet
program again to eliminate run time penalty or set aside our goal of zero run time overhead and change the exception policy to
default_exception_policy .
Note that once we use automatic type promotion, our programming language isn't C/C++ anymore. So don't be tempted to so
something like the following:
// DON'T DO THIS !
#if defined(NDEBUG)
using safe_t = boost::numeric::safe<
int,
boost::numeric::automatic, // note use of "automatic" policy!!!
boost::numeric::loose_trap_policy
>;
#else
using safe_t = boost::numeric::safe<int>;
#endif
#include <stdexcept>
#include <iostream>
#include <sstream>
#include <boost/safe_numerics/safe_integer.hpp>
24
Safe Numerics
#include <boost/safe_numerics/safe_integer_range.hpp>
#include <boost/safe_numerics/native.hpp>
#include <boost/safe_numerics/exception.hpp>
int main(){
std::cout << "example 84:\n";
bool result =
! test("123 125")
&& test("0 0")
&& test("-24 82")
;
std::cout << (result ? "Success!" : "Failure") << std::endl;
return result ? EXIT_SUCCESS : EXIT_FAILURE;
25
Safe Numerics
• As before, we define a type safe_t to reflect our view of legal values for this program. This uses the automatic type
promotion policy as well as the loose_trap_policy exception policy to enforce elimination of runtime penalties.
• The function f accepts only arguments of type safe_t so there is no need to check the input values. This performs the
functionality of programming by contract with no runtime cost.
• In addition, we define input_safe_t to be used when reading variables from the program console. Clearly, these can only
be checked at runtime so they use the throw_exception policy. When variables are read from the console they are checked
for legal values. We need no ad hoc code to do this, as these types are guaranteed to contain legal values and will throw an
exception when this guarantee is violated. In other words, we automatically get checking of input variables with no additional
programming.
• On calling of the function f, arguments of type input_safe_t are converted to values of type safe_t . In this particular
example, it can be determined at compile time that construction of an instance of a safe_t from an input_safe_t can never
fail. Hence, no try/catch block is necessary. The usage of the loose_trap_policy policy for safe_t types guarantees
this to be true at compile time.
Here is the output from the program when values 12 and 32 are input from the console:
example 84:
type in values in format x y:33 45
x<signed char>[-24,82] = 33
y<signed char>[-24,82] = 45
z = <short>[-48,164] = 78
(x + y) = <short>[-48,164] = 78
(x - y) = <signed char>[-106,106] = -12
<short>[-48,164] = 78
4. Case Studies
4.1. Composition with Other Libraries
For many years, Boost has included a library to represent and operate on rational numbers. Its well crafted, has good
documentation and is well maintained. Using the rational library is as easy as construction an instance with the expression
rational r(n, d) where n and d are integers of the same type. From then on it can be used pretty much as any other number.
Reading the documentation with safe integers in mind one finds
Limited-precision integer types [such as int] may raise issues with the range sizes of their allowable negative
values and positive values. If the negative range is larger, then the extremely-negative numbers will not have
an additive inverse in the positive range, making them unusable as denominator values since they cannot be
normalized to positive values (unless the user is lucky enough that the input components are not relatively
prime pre-normalization).
Which hints of trouble. Examination of the code reveals which suggest that care has been taken implement operations so as to
avoid overflows, catch divide by zero, etc. But the code itself doesn't seem to consistently implement this idea. So we make a
small demo program:
26
Safe Numerics
#include <iostream>
#include <limits>
#include <boost/rational.hpp>
#include <boost/safe_numerics/safe_integer.hpp>
return 0;
}
r = 1/2
q = -1/2
r * q = -1/4
c = 1/2147483647
d = 1/2
c * d = 1/-2
27
Safe Numerics
c = 1/2147483647
d = 1/2
c * d = multiplication overflow: positive overflow error
The rational library documentation is quite specific as to the type requirements placed on the underlying type. Turns out the our
own definition of an integer type fulfills (actually surpasses) these requirements. So we have reason to hope that we can use
safe<int> as the underlying type to create what we might call a "safe_rational". This we have done in the above example
where we demonstrate how to compose the rational library with the safe numerics library in order to create what amounts
to a safe_rational library - all without writing a line of code! Still, things are not perfect. Since the rational numbers library
implements its own checking for divide by zero by throwing an exception, the safe numerics code for handling this - included
exception policy will not be respected. To summarize:
• In some cases safe types can be used as template parameters to other types to inject the concept of "no erroneous results" into
the target type.
• Such composition is not guaranteed to work. The target type must be reviewed in some detail.
A stepper motor controller emits a pulse which causes the motor to move one step. It seems simple, but in practice it turns out
to be quite intricate to get right as one has to time the pulses individually to smoothly accelerate the rotation of the motor from a
standing start until it reaches the some maximum velocity. Failure to do this will either limit the stepper motor to very low speed
or result in skipped steps when the motor is under load. Similarly, a loaded motor must be slowly decelerated down to a stop.
28
Safe Numerics
This implies the the width of the pulses must decrease as the motor accelerates. That is the pulse with has to be computed while
the motor is in motion. This is illustrated in the above drawing. A program to accomplish this might look something like the
following:
On interrupt
if at target position
disable interrupts and return
calculate width of next step
change current winding according to motor direction
set delay time to next interrupt to width of next step
Already, this is turning it to a much more complex project than it first seemed. Searching around the net, we find a popular article
on the operation of stepper motors using simple micro controllers. The algorithm is very well explained and it includes complete
code we can test. The engineers are still debugging the prototype boards and hope to have them ready before the product actually
ships. But this doesn't have to keep us from working on our code.
• Factor into motor1.c which contains the motor driving code and motor_test1.c which tests that code.
29
Safe Numerics
• Include header <xc.h> which contains constants for the PIC18F2520 processor
• The original has some anomalies in the names of types. For example, int16 is assumed to be unsigned. This is an artifact of
the original C compiler being used. So type names in the code were altered to standard ones while retaining the intent of the
original code.
The resulting program can be checked to be identical to the original but compiles on with the Microchip XC8 compiler. Given
a development board, we could hook it up to a stepper motor, download and boot the code and verify that the motor rotates 5
revolutions in each direction with smooth acceleration and deceleration. We don't have such a board yet, but the engineers have
promised a working board real soon now.
motor_test2.c example92.cpp
//////////////////////////////////////////////////////////////////
// example92.cpp
//
// Copyright (c) 2015 Robert Ramey
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <iostream>
// ***************************
30
Safe Numerics
// ***************************
// 2. specify a promotion policy to support proper emulation of
// PIC types on the desktop
using pic16_promotion = boost::safe_numerics::cpp<
8, // char 8 bits
16, // short 16 bits
16, // int 16 bits
16, // long 16 bits
32 // long long 32 bits
>;
// ***************************
// 3. define PIC integer type names to be safe integer types of he same size.
// ***************************
// 4. emulate PIC features on the desktop
31
Safe Numerics
uint8 TRISC;
uint8 T3CON;
uint8 T1CON;
uint8 CCPR2H;
uint8 CCPR2L;
uint8 CCPR1H;
uint8 CCPR1L;
uint8 CCP1CON;
uint8 CCP2CON;
uint8 TMR1H;
uint8 TMR1L;
// ***************************
// 5. include the environment independent code we want to test
#include "motor2.c"
#include <chrono>
32
Safe Numerics
#include <thread>
int main(){
std::cout << "start test\n";
result_t result = success;
try{
initialize();
// move motor to position 1000
result &= test(1000);
// move motor to position 200
result &= test(200);
// move motor to position 200 again! Should result in no movement.
result &= test(200);
// move back to position 0
result &= test(0);
// ***************************
// 6. error detected here! data types can't handle enough
// steps to move the carriage from end to end! Suppress this
// test for now.
// move motor to position 50000.
33
Safe Numerics
Here are the essential features of the desktop version of the test program.
2. Specify a promotion policy to support proper emulation of PIC types on the desktop.
The C language standard doesn't specify sizes for primitive data types like int. They can and do differ between environments.
Hence, the characterization of C/C++ as "portable" languages is not strictly true. Here we choose aliases for data types so
that they can be defined to be the same in both environments. But this is not enough to emulate the PIC18F2520 on the
desktop. The problem is that compilers implicitly convert arguments of C expressions to some common type before performing
arithmetic operations. Often, this common type is the native int and the size of this native type is different in the desktop and
embedded environment. Thus, many arithmetic results would be different in the two environments.
But now we can specify our own implicit promotion rules for test programs on the development platform that are identical to
those on the target environment! So unit testing executed in the development environment can now provide results relevant to
the target environment.
3. Define PIC integer type aliases to be safe integer types of he same size.
Code tested in the development environment will use safe numerics to detect errors. We need these aliases to permit the code
in motor2.c to be tested in the desktop environment. The same code run in the target system without change.
The PIC processor, in common with most micro controllers these days, includes a myriad of special purpose peripherals to
handle things like interrupts, USB, timers, SPI bus, I^2C bus, etc.. These peripherals are configured using special 8 bit words
in reserved memory locations. Configuration consists of setting particular bits in these words. To facilitate configuration
operations, the XC8 compiler includes a special syntax for setting and accessing bits in these locations. One of our goals is to
permit the testing of the identical code with our desktop C++ compiler as will run on the micro controller. To realize this goal,
we create some C++ code which implements the XC8 C syntax for setting bits in particular memory locations.
5. include motor1.c
6. Add test to verify that the motor will be able to keep track of a position from 0 to 50000 steps. This will be needed to maintain
the position of out linear stage across a range from 0 to 500 mm.
Our first attempt to run this program fails by throwing an exception from motor1.c indicating that the code attempts to left shift a
negative number at the statements:
34
Safe Numerics
According to the C/C++ standards this is implementation defined behavior. But in practice with all modern platforms (as far
as I know), this will be equivalent to a multiplication by 4. Clearly the intent of the original author is to "micro optimize" the
operation by substituting a cheap left shift for a potentially expensive integer multiplication. But on all modern compilers, this
substitution will be performed automatically by the compiler's optimizer. So we have two alternatives here:
This will work when the code is run on the PIC. But, in order to permit testing on the desktop, we need to inhibit the error
detection in that environment. With safe numerics, error handling is determined by specifying an exception policy. In this
example, we've used the default exception policy which traps implementation defined behavior. To ignore this kind of behavior
we could define our own custom exception policy.
• change the << 2 to * 4. This will produce the intended result in an unambiguous, portable way. For all known compilers, this
change should not affect runtime performance in any way. It will result in unambiguously portable code.
• Alter the code so that the expression in question is never negative. Depending on sizes of the operands and the size of the
native integer, this expression might return convert the operands to int or result in an invalid result.
Of these alternatives, the third seems the more definitive fix so we'll choose that one. We also decide to make a couple of minor
changes to simplify the code and make mapping of the algorithm in the article to the code more transparent. With these changes,
our test program runs to the end with no errors or exceptions. In addition, I made a minor change which simplifies the handling of
floating point values in format of 24.8. This results in motor2.c which makes the above changes. It should be easy to see that these
two versions are otherwise identical.
Finally our range test fails. In order to handle the full range we need, we'll have to change some data types used for holding step
count and position. We won't do that here as it would make our example too complex. We'll deal with this on the next version.
• This system detects errors and exceptions on the test machine - but it fails to address and detect such problems on the target
system. Since the target system is compiles only C code, we can't use the exception/error facilities of this library at runtime.
• Testing shows the presence, not the absence of bugs. Can we not prove that all integer arithmetic is correct?
• For at least some operations on safe integers there is runtime cost in checking for errors. In this example, this is not really a
problem as the safe integer code is not included when the code is run on the target - it's only a C compiler after all. But more
generally, using safe integers might incur an undesired runtime cost.
Can we catch all potential problems at compiler time and therefore eliminate all runtime cost?
Our first attempt consists of simply changing default exception policy from the default runtime checking to the compile time
trapping one. Then we redefine the aliases for the types used by the PIC to use this exception policy.
When we compile now, any expressions which could possibly fail will be flagged as syntax errors. This occurs 11 times when
compiling the motor2.c program. This is fewer than one might expect. To understand why, consider the following example:
safe<std::int8_t> x, y;
...
35
Safe Numerics
safe<std::int16_t> z = x + y;
C promotion rules and arithmetic are such that the z will always contain an arithmetically correct result regardless of what values
are assigned to x and y. Hence there is no need for any kind of checking of the arithmetic or result. The Safe Numerics library
uses compile time range arithmetic, C++ template multiprogramming and other techniques to restrict invocation of checking code
to only those operations which could possible fail. So the above code incurs no runtime overhead.
Now we have 11 cases to consider. Our goal is to modify the program so that this number of cases is reduced - hopefully to zero.
Initially I wanted to just make a few tweaks in the versions of example92.c, motor2.c and motor_test2.c above without
actually having to understand the code. It turns out that one needs to carefully consider what various types and variables are
used for. This can be a good thing or a bad thing depending on one's circumstances, goals and personality. The programs above
evolved into example93.c, motor3.c and motor_test3.c. First we'll look at example93.c:
//////////////////////////////////////////////////////////////////
// example93.cpp
//
// Copyright (c) 2015 Robert Ramey
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <iostream>
// ***************************
// 1. Specify exception policies so we will generate a
// compile time error whenever an operation MIGHT fail.
// ***************************
// generate runtime errors if operation could fail
using exception_policy = boost::safe_numerics::default_exception_policy;
// ***************************
// 2. Create a macro named literal an integral value
// that can be evaluated at compile time.
#define literal(n) make_safe_literal(n, pic16_promotion, void)
36
Safe Numerics
static_assert(
C0 < make_safe_literal(0xffffff, pic16_promotion,trap_policy),
"Largest step too long"
);
static_assert(
C_MIN > make_safe_literal(0, pic16_promotion,trap_policy),
"Smallest step must be greater than zero"
);
// ***************************
// 3. Create special ranged types for the motor program
// These wiil guarantee that values are in the expected
// ranges and permit compile time determination of when
// exceptional conditions might occur.
// position
using position_t = boost::safe_numerics::safe_unsigned_range<
0,
50000, // 500 mm / 2 mm/rotation * 200 steps/rotation
pic16_promotion,
exception_policy
>;
37
Safe Numerics
// direction of rotation
using direction_t = boost::safe_numerics::safe_signed_range<
-1,
+1,
pic16_promotion,
trap_policy
>;
// ***************************
// emulate PIC features on the desktop
38
Safe Numerics
pic_register_t RCON;
pic_register_t INTCON;
pic_register_t CCP1IE;
pic_register_t CCP2IE;
pic_register_t PORTC;
pic_register_t TRISC;
pic_register_t T3CON;
pic_register_t T1CON;
pic_register_t CCPR2H;
pic_register_t CCPR2L;
pic_register_t CCPR1H;
pic_register_t CCPR1L;
pic_register_t CCP1CON;
pic_register_t CCP2CON;
pic_register_t TMR1H;
pic_register_t TMR1L;
// ***************************
// special checked type for bits - values restricted to 0 or 1
using safe_bit_t = boost::safe_numerics::safe_unsigned_range<
0,
1,
pic16_promotion,
trap_policy
>;
39
Safe Numerics
#include "motor3.c"
#include <chrono>
#include <thread>
int main(){
std::cout << "start test\n";
result_t result = success;
try {
40
Safe Numerics
initialize();
// move motor to position 1000
result &= test(literal(9000));
// move to the left before zero position
// fails to compile !
// result &= ! test(-10);
// move motor to position 200
result &= test(literal(200));
// move motor to position 200 again! Should result in no movement.
result &= test(literal(200));
// move motor to position 50000.
result &= test(literal(50000));
// move motor back to position 0.
result &= test(literal(0));
}
catch(...){
std::cout << "test interrupted\n";
return EXIT_FAILURE;
}
std::cout << "end test\n";
return result == success ? EXIT_SUCCESS : EXIT_FAILURE;
}
Here are the changes we've made int the desktop test program
1. Specify exception policies so we can generate a compile time error whenever an operation MIGHT fail. We've aliased this
policy with the name trap_policy. The default policy of which throws a runtime exception when an error is countered is
aliased as exception_policy. When creating safe types, we'll now specify which type of checking, compile time or runtime,
we want done.
2. Create a macro named "literal" an integral value that can be evaluated at compile time.
"literal" values are instances of safe numeric types which are determined at compile time. They are constexpr values. When
used along with other instances of safe numeric types, the compiler can calculate the range of the result and verify whether or
not it can be contained in the result type. To create "literal" types we use the macro make_safe_literal(n, p, e) where n
is the value, p is the promotion policy and e is the exception policy.
When all the values in an expression are safe numeric values, the compiler can calculate the narrowest range of the result.
If all the values in this range can be represented by the result type, then it can be guaranteed that an invalid result cannot be
produced at runtime and no runtime checking is required.
Make sure that all literal values are x are replaced with the macro invocation "literal(x)".
It's unfortunate that the "literal" macro is required as it clutters the code. The good news is that is some future version of C++,
expansion of constexpr facilities may result in elimination of this requirement.
3. Create special types for the motor program. These will guarantee that values are in the expected ranges and permit compile
time determination of when exceptional conditions might occur. In this example we create a special type c_t to the width of
the pulse applied to the motor. Engineering constraints (motor load inertia) limit this value to the range of C0 to C_MIN. So
we create a type with those limits. By using limits no larger than necessary, we supply enough information for the compiler to
determine that the result of a calculation cannot fall outside the range of the result type. So less runtime checking is required.
In addition, we get extra verification at compile time that values are in reasonable ranges for the quantity being modeled.
And we've made changes consistent with the above to motor3.c as well
/*
41
Safe Numerics
* david austin
* http://www.embedded.com/design/mcus-processors-and-socs/4006438/Generate-stepper-motor-speed-profiles-i
* DECEMBER 30, 2004
*
* Demo program for stepper motor control with linear ramps
* Hardware: PIC18F252, L6219
*
* Copyright (c) 2015 Robert Ramey
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
#include <assert.h>
// ***************************
// 1. Define state variables using custom strong types
// initial setup
enum ramp_state ramp_sts;
position_t motor_position;
position_t m; // target position
position_t m2; // midpoint or point where acceleration changes
direction_t d; // direction of traval -1 or +1
// ***************************
// 2. Surround all literal values with the "literal" keyword
// ***************************
// 3. Refactor code to make it easier to understand
// and relate to the documentation
bool busy(){
42
Safe Numerics
43
Safe Numerics
}
// equation 14
// *** possible positive overflow on update of c
// note: re-arrange expression to avoid negative result
// from difference of two unsigned values
{
// testing discovered that this can overflow. It's not easy to
// avoid so we'll use a temporary unsigned variable 32 bits wide
const temp_t x = c + literal(2) * c / (literal(4) * (m - i) - literal(1));
c = x > C0 ? C0 : x;
}
break;
default:
// should never arrive here!
assert(false);
} // switch (ramp_sts)
break;
}
assert(c <= C0 && c >= C_MIN);
// *** possible exception
ccpr = literal(0xffffff) & (ccpr + c);
phase_ix = (phase_ix + d) & literal(3);
update(ccpr, phase_ix);
} // isr_motor_step()
i = literal(0);
m2 = m / literal(2);
c = C0;
ccpr = (TMR1H << literal(8) | TMR1L) + C0 + literal(1000);
phase_ix = d & literal(3);
update(ccpr, phase_ix);
44
Safe Numerics
void initialize() {
di(); // disable_interrupts(GLOBAL);
motor_position = literal(0);
CCP1IE = literal(0); // disable_interrupts(INT_CCP1);
CCP2IE = literal(0); // disable_interrupts(INT_CCP2);
PORTC = literal(0); // output_c(0);
TRISC = literal(0); // set_tris_c(0);
T3CON = literal(0);
T1CON = literal(0x35);
INTCONbits.PEIE = literal(1);
INTCONbits.RBIF = literal(0);
ei(); // enable_interrupts(GLOBAL);
} // initialize()
3. Re-factor code to make it easier to understand and compare with the algorithm as described in the original article.
4. Rewrite interrupt handler in a way which mirrors the original description of the algorithm and minimizes usage of state
variable, accumulated values, etc.
5. Distinguish all the statements which might invoke a runtime exception with a comment. There are 12 such instances.
Finally we make a couple minor changes in motor_test3.c to verify that we can compile the exact same version of motor3.c on the
PIC as well as on the desktop.
Summary
The intent of this case study is to show that the Safe Numerics Library can be an essential tool in validating the correctness of C/C
++ programs in all environments - including the most restricted.
• We started with a program written for a tiny micro controller for controlling the acceleration and deceleration of a stepper
motor. The algorithm for doing this is very non-trivial and difficult prove that it is correct.
• We used the type promotion policies of the Safe Numerics Library to test and validate this algorithm on the desk top. The
tested code is also compiled for the target micro controller.
• We used strong typing features of Safe Numerics to check that all types hold the values expected and invoke no invalid implicit
conversions. Again the tested code is compiled for the target processor.
What we failed to do is to create a version of the program which uses the type system to prove that no results can be invalid. I
turns out that states such as
++i;
c = f(c);
can't be proved not to overflow with this system. So we're left with having to depend upon exhaustive testing. It's not what we
hoped, but it's the best we can do.
45
Safe Numerics
5. Background
This library started out as a re-implementation of the facilities provided by David LeBlanc's SafeInt Library. I found this library
to be well done in every way. My main usage was to run unit tests for my embedded systems projects on my PC. Still, from my
perspective it had a few issues.
• It was a lot of code in one header - 6400 lines. Very unwieldy to understand, modify and maintain.
• I couldn't find separate documentation other than that in the header file.
Using later versions of C++ and the its standard library, template metaprogramming and Boost libraries I managed to
(re)implement similar functionality in under 2000 ? lines of code. I promoted this version as a possible submission to the
Boost. The feedback I received convinced me that no such library would be considered acceptable to the large majority of C++
programmers. It seems that the desire for maximum performance overrides any requirement that a program be known to be free
of bugs. By this time I had a better idea of the opportunities available with the latest version of C++ (C++14) and resolved to
address this issue by creating a library which would provide all the facilities of safe numerics at minimal runtime cost. The result
is what you see here. The library now consists of 7000 lines of code, approximately 50 separate tests and more than 60 pages of
documentation and examples.
Since I wrote the above, I've been contacted by David LeBlanc. He's been updating his package and informs me that the latest
version:
• SafeInt does not require porting for different compilers, is fully supported on gcc, clang, and Visual Studio.
• The library has had a test suite since before it was public, and is now located here:
• SafeInt also has no external dependencies other than standard library files, and doesn't need anything else installed to work.
6. Type Requirements
6.1. Numeric<T>
Description
A type is Numeric if it has the properties of a number.
More specifically, a type T is Numeric if there exists a specialization of std::numeric_limits<T>. See the documentation
for the standard library class numeric_limits. The standard library includes such specializations for all the built-in numeric
types. Note that this concept is distinct from the C++ standard library type traits is_integral and is_arithmetic. These
46
Safe Numerics
latter fulfill the requirement of the concept Numeric. But there may types which fulfill the concept of Numeric for which
std::is_arithmetic<T> == false.
There are multiple reasons that we cannot use std::is_arithmetic<T> to identify number-like types for our purposes:
• The main purpose of our concept of Numeric<T> is to indicate that the range of type T is available to be queried. Since all
specializations of Numeric<T> have a member in std::numeric_limits<T> we have access to this information when needed
through the members std::numeric_limits<T>::min() and std::numeric_limits<T>::max().
• There are types which do not fulfill std::arithmetic<T> (that is are not built-in numeric types) but still fulfill the concept of
Numeric<T> (number like "things").
• The library creates new result types for every expression which should also fulfill this concept Numeric. But is_arithmetic<T>
cannot be specialized for user types.
So is_arithmetic<T> does not correspond to the same set of types as Numeric<T> does.
Notation
T, U, V A type that is a model of a Numeric type
Associated Types
std::numeric_limits<T> The numeric_limits class template provides a C++ program with information about various
properties of the implementation's representation of the arithmetic types. See C++ standard
18.3.2.2.
Valid Expressions
In addition to the expressions defined in Assignable the following expressions must be valid. In the safe_numerics library, a type
is considered Numeric if and only if it it has an entry in the std::numeric_limits table with the following members. Note that this
is different from the the definition of std::is_arithmetic in that the later is required to support all valid expressions which Numeric
does not require support for all these expressions but only requires that they be correctly implemented if they are defined. Also
is_arithmetic is only defined for built in numeric types while Numeric applies to any user types which "look like" a number.
Numeric<T> true_type
47
Safe Numerics
Any or all of the following unary operators MAY be defined. Any such defined operators shall implement the semantics as
described below
-t T Invert sign
+t T unary plus - a no op
Any or all of the following binary operators MAY be defined. Any defined operators shall implement the semantics as described
bellow
t - u V subtract u from t
t + u V add u to t
t * u V multiply t by u
t / u T divide t by u
t = u T assign value of u to t
48
Safe Numerics
Models
int, float, safe_signed_integer<int>, safe_signed_range<int>, checked_result<int>, etc.
Header
#include <boost/safe_numerics/concepts/numeric.hpp>
• is-it-ok-to-specialize-stdnumeric-limitst-for-user-defined-number-like-class
• cppreference.com/w/cpp/types/numeric_limits
In any case, it seems pretty clear that no harm will come of it. In spite of the consideration given to this issue, it turns out that
the found no real need to implement these predicates. For example, there is no "is_numeric<T>" implemented as part of the safe
numerics library. This may change in the future though. Even if not used, defining and maintaining these type requirements in this
document has been very valuable in keeping the concepts and code more unified and understandable.
Remember that above considerations apply to other numeric types used in this library even though we don't explicitly repeat this
information for every case.
6.2. Integer<T>
Description
A type fulfills the requirements of an Integer if it has the properties of a integer.
More specifically, a type T is Integer if there exists a specialization of std::numeric_limits<T> for which
std::numeric_limits<T>::is_integer is equal to true. See the documentation for standard library class
numeric_limits. The standard library includes such specializations for all built-in numeric types. Note that this concept is
distinct from the C++ standard library type traits is_integral and is_arithmetic. These latter fulfill the requirements of
the concept Numeric. But there are types which fulfill this concept for which is_arithmetic<T>::value == false. For
example see safe<int>.
49
Safe Numerics
Refinement of
Numeric
Notation
Valid Expressions
In addition to the expressions defined in Integer, the following expression must be true.
Expression Value
std::numeric_limits<T>::is_integer true
Any or all of the following unary operators MAY be defined. Any such defined operators shall implement the semantics as
described below
~t T bitwise complement
Any or all of the following binary operators MAY be defined. Any defined operators shall implement the semantics as described
bellow
50
Safe Numerics
Models
int, safe<int>, safe_unsigned_range<0, 11>, checked_result<int> etc.
Header
#include <boost/safe_numerics/concepts/integer.hpp>
6.3. PromotionPolicy<PP>
Description
In C++ expressions, arguments must be of the same type. If they are not, all arguments are converted to a common type in
accordance with rules of the C++ standard. For some applications of the safe numerics library, we might want conversions which
are different than the standard ones. This class type implements the conversion functions that are to be used with the safe numeric
type being instantiated.
int x;
char y;
auto z = x + y
the type of z will be an int. This is a consequence for the standard rules for type promotion for C/C++ arithmetic. A key feature
of library permits one to specify his own type promotion rules via a PromotionPolicy class.
Notation
R An object of type modeling Numeric which can be used to construct a SafeNumeric type.
51
Safe Numerics
Valid Expressions
Any operations which result in integers which cannot be represented as some Numeric type will throw an exception.These
expressions return a type which can be used as the basis create a SafeNumeric type.
Models
The library contains a number of pre-made promotion policies:
• boost::numeric::native
int x;
char y;
auto z = x + y; // could result in overflow
safe<int, native> sx;
auto sz = sx + y; // standard C++ code which detects errors
Type sz will be a SafeNumeric type based on int. If the result exceeds the maximum value that can be stored in an int, an
error is detected.
• boost::numeric::automatic
Use optimizing expression type promotion rules. These rules replace the normal C/C++ type promotion rules with other rules
which are designed to result in more efficient computations. Expression types are promoted to the smallest type which can be
guaranteed to hold the result without overflow. If there is no such type, the result will be checked for overflow. Consider the
following example:
52
Safe Numerics
int x;
char y;
auto z = x + y; // could result in overflow
safe<int, automatic> sx;
auto sz = sx + y;
// sz is a safe type based on long
// hence sz is guaranteed not to overflow.
safe_unsigned_range<1, 4> a;
safe_unsigned_range<2, 4> b;
auto c = a + b; // c will be a safe type with a range [3,8] and cannot overflow
Type sz will be a SafeNumeric type which is guaranteed to hold he result of x + y. In this case that will be a long int (or
perhaps a long long) depending upon the compiler and machine architecture. In this case, there will be no need for any special
checking on the result and there can be no overflow.
Type of c will be a signed character as that type can be guaranteed to hold the sum so no overflow checking is done.
• boost::numeric::cpp
Use expression type promotion rules to emulate another processor. When this policy is used, C++ type for safe integers follows
the rules that a compiler on the target processor would use. This permits one to test code destined for a one processor on the
another one. One situation there this can be very, very useful is when testing code destined for a micro controller which doesn't
have the logging, debugging, input/output facilities of a desktop.
Header
#include <boost/safe_numerics/concepts/promotion_policy.hpp>
6.4. ExceptionPolicy<EP>
Description
The exception policy specifies what is to occur when a safe operation cannot return a valid result. A type is an ExceptionPolicy if
it has functions for handling exceptional events that occur in the course of safe numeric operations.
53
Safe Numerics
Notation
message A const char * which refers to a text message about the cause of an exception
Valid Expressions
Whenever an operation yield an invalid result, one of the following functions will be invoked.
Expression Return
Invoked when:
Value
EP::on_arithmetic_error(e, message) void The operation cannot produce valid arithmetic result such as
overflows, divide by zero, etc.
Synopsis
template<class EP>
constexpr void
dispatch<EP>(const boost::numeric::safe_numerics_error & e, char const * const & msg);
Example of use
#include <boost/safe_numerics/exception_policies.hpp"
dispatch<boost::numeric::loose_exception_policy>(
boost::numeric::safe_numerics_error::positive_overflow_error,
"operation resulted in overflow"
);
Models
The library header <boost/safe_numerics/exception_policies.hpp> contains a number of pre-made exception policies:
• boost::numeric::loose_exception_policy
54
Safe Numerics
Throw on arithmetic errors, ignore other errors. Some applications ignore these issues and still work and we don't want to
update them.
• boost::numeric::loose_trap_policy
Same as above in that it doesn't check for various undefined behaviors but traps at compile time for hard arithmetic errors. This
policy would be suitable for older embedded systems which depend on bit manipulation operations to work.
• boost::numeric::strict_exception_policy
Permit just about anything, throw at runtime on any kind of error. Recommended for new code. Check everything at compile
time if possible and runtime if necessary. Trap or Throw as appropriate. Should guarantee code to be portable across
architectures.
• boost::numeric::strict_trap_policy
Same as above but requires code to be written in such a way as to make it impossible for errors to occur. This naturally will
require extra coding effort but might be justified for embedded and/or safety critical systems.
• boost::numeric::default_exception_policy
Alias for strict_exception_policy, One would use this first. After experimentation, one might switch to one of the above
policies or perhaps use a custom policy.
Header
#include <boost/safe_numerics/concepts/exception_policy.hpp>
7. Types
7.1. safe<T, PP, EP>
Description
A safe<T, PP , EP> can be used anywhere a type T can be used. Any expression which uses this type is guaranteed to return
an arithmetically correct value or to trap in some way.
Notation
Symbol Description
Associated Types
PP Promotion Policy. A type which specifies the result type of an expression using safe types.
EP Exception Policy. A type containing members which are called when a correct result cannot be returned
55
Safe Numerics
Template Parameters
T std::is_integer<T> The underlying type. Currently only built-in integer types are supported
Model of
Numeric
Integer
Valid Expressions
Implements all expressions and only those expressions supported by the base type T. Note that all these expressions are
constexpr. The result type of such an expression will be another safe type. The actual type of the result of such an expression
will depend upon the specific promotion policy template parameter.
When a binary operand is applied to two instances of safe<T, PP, EP>one of the following must be true:
• The promotion policies of the two operands must be the same or one of them must be void
• The exception policies of the two operands must be the same or one of them must be void
Examples of use
The most common usage would be safe<T> which uses the default promotion and exception policies. This type is meant to be a
"drop-in" replacement of the intrinsic integer types. That is, expressions involving these types will be evaluated into result types
which reflect the standard rules for evaluation of C++ expressions. Should it occur that such evaluation cannot return a correct
result, an exception will be thrown.
There are two aspects of the operation of this type which can be customized with a policy. The first is the result type of an
arithmetic operation. C++ defines the rules which define this result type in terms of the constituent types of the operation.
Here we refer to these rules as "type promotion" rules. These rules will sometimes result in a type which cannot hold the actual
arithmetic result of the operation. This is the main motivation for making this library in the first place. One way to deal with this
problem is to substitute our own type promotion rules for the C++ ones.
#include <exception>
#include <iostream>
56
Safe Numerics
#include <safe_integer.hpp>
void f(){
using namespace boost::safe_numerics;
safe<int> j;
try {
safe<int> i;
std::cin >> i; // could overflow !
j = i * i; // could overflow
}
catch(std::exception & e){
std::cout << e.what() << std::endl;
}
std::cout << j;
}
The term "drop-in replacement" reveals the aspiration of this library. In most cases, this aspiration is realized. In the following
example, the normal implicit conversions function the same for safe integers as they do for built-in integers.
#include <boost/safe_numerics/safe_integer.hpp>
using namespace boost::safe_numerics;
int main(){
const long x = 97;
f(x); // OK - implicit conversion to int
const safe_t y = 97;
f(y); // Also OK - checked implicit conversion to int
return 0;
}
When the safe<long> is implicitly converted to an int when calling f, the value is checked to be sure that it is within the legal
range of an int and will invoke an exception if it cannot. We can easily verify this by altering the exception handling policy in
the above example to loose_trap_policy. This will invoke a compile time error on any conversion might invoke a runtime
exception.
#include <boost/safe_numerics/safe_integer.hpp>
#include <cstdint> // uint8_t
using namespace boost::safe_numerics;
57
Safe Numerics
int main(){
const long x = 97;
f(x); // OK - implicit conversion to int can never fail
const safe_t y = 97;
f(y); // could overflow so trap at compile time
return 0;
}
But this raises it's own questions. We can see that in this example, the program can never fail:
• y is converted to an int
The conversion can never fail because the value of 97 can always fit into an int. But the library code can't detect this and emits the
checking code even though it's not necessary.
This can be addressed by using a safe_literal. A safe literal can contain one and only one value. All the functions in this
library are marked constexpr. So it can be determined at compile time that conversion to an int can never fail and no runtime
checking code need be emitted. Making this small change will permit the above example to run with zero runtime overhead while
guaranteeing that no error can ever occur.
#include <boost/safe_numerics/safe_integer.hpp>
#include <boost/safe_numerics/safe_integer_literal.hpp>
template<intmax_t N>
using safe_literal = safe_signed_literal<N, native, loose_trap_policy>;
int main(){
const long x = 97;
f(x); // OK - implicit conversion to int
const safe_literal<97> y;
f(y); // OK - y is a type with min/max = 97;
return 0;
}
With this trivial example, such efforts would hardly be deemed necessary. But in a more complex case, perhaps including compile
time arithmetic expressions, it could be much more difficult to verify that the constant is valid and/or no checking code is needed.
58
Safe Numerics
And there is also possibility that over the life time of the application, the compile time constants might change, thus rendering any
ad hoc analyse obsolete. Using safe_literal will future-proof your code against well-meaning, but code-breaking updates.
Stepping back, we can see that many of the cases of invalid arithmetic wouldn't exist if the result types were larger. So we
can avoid these problems by replacing the C++ type promotion rules for expressions with our own rules. This can be done
by specifying a promotion policy automatic. The policy stores the result of an expression in the smallest size type that can
accommodate the largest value that an expression can yield. No checking for exceptions is necessary. The following example
illustrates this.
#include <boost/safe_numerics/safe_integer.hpp>
#include <iostream>
return 0;
}
Header
#include <boost/safe_numerics/safe_integer.hpp>
Notation
Symbol Description
MIN, MAX Minimum and maximum values that the range can represent.
59
Safe Numerics
Associated Types
PP Promotion Policy. A type which specifies the result type of an expression using safe types.
EP Exception Policy. A type containing members which are called when a correct result cannot be returned
Template Parameters
T std::is_integer<T> The underlying type. Currently only built-in integer types are supported
MIN must be a non-negative The minimum non-negative integer value that this type may hold
literal
MAX must be a non-negative The maximum non-negative integer value that this type may hold
literal
Model of
Numeric
Integer
Valid Expressions
Implements all expressions and only those expressions supported by the base type T. Note that all these expressions are
constexpr. The result type of such an expression will be another safe type. The actual type of the result of such an expression
will depend upon the specific promotion policy template parameter.
When a binary operand is applied to two instances of A safe_signed_range<MIN, MAX, PP, EP> or
safe_unsigned_range<MIN, MAX, PP, EP> one of the following must be true:
• The promotion policies of the two operands must be the same or one of them must be void
• The exception policies of the two operands must be the same or one of them must be void
Example of use
60
Safe Numerics
#include <exception>
#include <iostream>
#include <type_traits>
#include <boost/safe_numerics/safe_integer.hpp>
#include <boost/safe_numerics/safe_integer_range.hpp>
#include <boost/safe_numerics/safe_integer_literal.hpp>
#include <boost/safe_numerics/utility.hpp>
int main(){
safe_unsigned_range<7, 24> i;
// if either or both types are safe types, the result is a safe type
// determined by promotion policy. In this instance
// the range of i is [7, 24] and the range of j is [0,255].
// so the type of k will be a safe type with a range of [7,279]
static_assert(
is_safe<decltype(k)>::value
&& std::numeric_limits<decltype(k)>::min() == 7
&& std::numeric_limits<decltype(k)>::max() == 279,
"k is a safe range of [7,279]"
);
return 0;
61
Safe Numerics
Header
#include <boost/numeric/safe_numerics/safe_range.hpp>
Associated Types
PP A type which specifies the result type of an expression using safe types.
EP A type containing members which are called when a correct result cannot be returned
Template Parameters
Parameter Type Requirements Description
Model of
Numeric
Integer
template<typename T>
using compile_time_value = safe_signed_literal<T>;
constexpr compile_time_value<1000> x;
constexpr compile_time_value<0> y;
62
Safe Numerics
// all the following statements should fail to compile because there are
// no promotion and exception policies specified.
constexpr safe<int> z = x / y;
Example of use
#include <boost/safe_numerics/safe_integer_literal.hp>
constexpr boost::safe_numerics::safe_signed_literal<42> x;
This is a macro which returns an instance of a safe literal type. This instance will hold the value n. The type of the value returned
will be the smallest safe type which can hold the value n.
Header
#include <boost/safe_numerics/safe_integer_literal.hpp>
7.4. exception
Description
Here we describe the data types used to refer to exceptional conditions which might occur. Note that when we use the word
"exception", we don't mean the C++ term which refers to a data type, but rather the colloquial sense of a anomaly, irregularity,
deviation, special case, isolated example, peculiarity, abnormality, oddity; misfit, aberration or out of the ordinary occurrence.
This concept of "exception" is more complex that one would think and hence is not manifested by a single simple type. A small
number of types work together to implement this concept within the library.
We've leveraged on the std::error_code which is part of the standard library. We don't use all the facilities that it offers so it's not
an exact match, but it's useful and works for our purposes.
Symbol Description
negative_overflow_error The absolute value of a negative number is too large to be represented by the data type.
63
Safe Numerics
Symbol Description
domain_error the result of an operation is outside the legal range of the result.
range_error an argument to a function or operator is outside the legal range - e.g. sqrt(-1).
uninitialized_value According to the C++ standard, the result may be defined by the application. e.g. 16 >> 10
will result the expected result of 0 on most machines.
The above listed codes can be transformed to a instance of type std::error_code with the function:
std::error_code make_error_code(safe_numerics_error e)
Symbol Description
report an operation which the C++ standard permits but fails to specify
implementation_defined_behavior
Translation of a safe_numerics_error into the corresponding safe_numerics_action can be accomplished with the
following function:
See Also
• C++ Standard Library version The C++ standard error handling utilities.
• Thinking Asynchronously in C++ Another essential reference on the design and usage of the error_code
Header
#include <boost/safe_numerics/exception.hpp>
64
Safe Numerics
Notation
Symbol Description
Template Parameters
Parameter
Type Requirements Invoked when:
AE Function object callable with the expression The operation cannot produce valid arithmetic result such as
AE()(e, message) overflows, divide by zero, etc.
UB Function object callable with the expression The result is undefined by the C++ standard
UB()(e, message)
IDB Function object callable with the expression The result depends upon implementation defined behavior according
IDB()(e, to the C++ standard
Model of
ExceptionPolicy
Function Objects
In order to create an exception policy, one needs some function objects. The library includes some appropriate examples of these:
Name Description
ignore_exception Ignore any runtime exception and just return - thus propagating the error. This is what would
happen with unsafe data types
65
Safe Numerics
Name Description
trap_exception Invoke a function which is undefined. Compilers will include this function if and only if there
is a possibility of a runtime error. Conversely, This will create a compile time error if there
is any possibility that the operation will fail at runtime. Use the action to guarantee that your
application will never produce an invalid result. Any operation invoke
But of course one is free to provide his own. Here is an example of a function object which would could be used exception
conditions.
// log an exception condition but continue processing as though nothing has happened
// this would emulate the behavior of an unsafe type.
struct log_runtime_exception {
log_runtime_exception() = default;
void operator () (
const boost::safe_numerics::safe_numerics_error & e,
const char * message
){
std::cout
<< "Caught system_error with code "
<< boost::safe_numerics::literal_string(e)
<< " and message " << message << '\n';
}
};
Name Description
loose_exception_policy Throws runtime exception on any arithmetic error. Undefined and implementation defined
behavior is permitted as long as it does not produce an arithmetic error.
loose_trap_policy Invoke a compile time error in any case where it's possible to result in an arithmetic error.
strict_exception_policy Throws runtime exception on any arithmetic error. Any undefined or implementation defined
behavior also results in throwing an exception.
strict_trap_policy Invoke a compile time error in any case where it's possible to result in an arithmetic error,
undefined behavior or implementation defined behavior
If none of the above suit your needs, you're free to create your own. Here is one where use the logging function object defined
above as a component in a loose exception policy which logs any arithmetic errors and ignores any other types of errors.
// logging policy
// log arithmetic errors but ignore them and continue to execute
66
Safe Numerics
Header
#include <boost/numeric/safe_numerics/exception_policies.hpp>
Usage of this policy with safe types will produce the exact same arithmetic results that using normal unsafe integer types will.
Hence this policy is suitable as a drop-in replacement for these unsafe types. Its main function is to trap incorrect arithmetic
results when using C++ for integer arithmetic.
Model of
PromotionPolicy
As an example of how this works consider C++ rules from section 5 of the standard - "usual arithmetic conversions".
According to these rules, z will be of type int. Depending on the values of x and y, z may or may not contain the correct
arithmetic result of the operation x + y.
Example of use
The following example illustrates the native type being passed as a template parameter for the type safe<int>. This example
is slightly contrived in that safe<int> has native as its default promotion parameter so explicitly using native is not
necessary.
#include <cassert>
#include <boost/safe_numerics/safe_integer.hpp>
67
Safe Numerics
#include <boost/safe_numerics/native.hpp>
int main(){
using namespace boost::numeric;
// use native promotion policy where C++ standard arithmetic
// might lead to incorrect results
using safe_int8 = safe<std::int8_t, native>;
try{
safe_int8 x = 127;
safe_int8 y = 2;
safe_int8 z;
// rather than producing an invalid result an exception is thrown
z = x + y;
assert(false); // never arrive here
}
catch(std::exception & e){
// which we can catch here
std::cout << e.what() << std::endl;
}
return 0;
}
Notes
See Chapter 5, Expressions, C++ Standard
Header
#include <boost/numeric/safe_numerics/native.hpp>
automatic
Description
This type contains the meta-functions to return a type with sufficient capacity to hold the result of a given binary arithmetic
operation.
The standard C/C++ procedure for executing arithmetic operations on operands of different types is:
• Convert operands to some common type using a somewhat elaborate elaborate rules defined in the C++ standard.
• If the result of the operation cannot fit in the common type of the operands, discard the high order bits.
The automatic promotion policy replaces the standard C/C++ procedure for the following one:
• For addition. If the operands are both unsigned the common type will be unsigned. Otherwise it will be signed.
68
Safe Numerics
• For left/right shift, the sign of the result will be the sign of the left operand.
• For all other types of operants, if both operands are unsigned the common type will be unsigned. Otherwise, it will be signed.
• Determine the smallest size of the signed or unsigned type which can be guaranteed hold the result.
• If this size exceeds the maximum size supported by the compiler, use the maximum size supported by the compiler.
• If the result cannot be contained in the result type as above, invoke an error procedure.
This type promotion policy is applicable only to safe types whose base type is an Integer type.
Model of
PromotionPolicy
Example of use
The following example illustrates the automatic type being passed as a template parameter for the type safe<int>.
#include <boost/safe_numerics/safe_integer.hpp>
#include <boost/safe_numerics/automatic.hpp>
int main(){
using namespace boost::numeric;
// use automatic promotion policy where C++ standard arithmetic
// might lead to incorrect results
using safe_t = safe<std::int8_t, automatic>;
Header
#include <boost/safe_numerics/automatic.hpp>
69
Safe Numerics
This policy is useful for running test programs which use C++ portable integer types but which are destined to run on an
architecture which is different than the one on which the test program is being built and run. This can happen when developing
code for embedded systems. Algorithms developed or borrowed from one architecture but destined for another can be tested on
the desktop.
Note that this policy is only applicable to safe types whose base type is a type fulfilling the type requirements of Integer.
Template Parameters
Model of
PromotionPolicy
Example of Use
Consider the following problem. One is developing software which uses a very small microprocessor and a very limited C
compiler. The chip is so small, you can't print anything from the code, log, debug or anything else. One debugs this code by using
the "burn" and "crash" method - you burn the chip (download the code), run the code, observe the results, make changes and try
again. This is a crude method which is usually the one used. But it can be quite time consuming.
Consider an alternative. Build and compile your code in testable modules. For each module write a test which exercises all the
code and makes it work. Finally download your code into the chip and - voilà - working product. This sounds great, but there's
one problem. Our target processor - in this case a PIC162550 from Microchip Technology is only an 8 bit CPU. The compiler
we use defines INT as 8 bits. This (and a few other problems), make our algorithm testing environment differ from our target
environment. We can address this by defining INT as a safe integer with a range of 8 bits. By using a custom promotion policy,
we can force the evaluation of C++ expressions in the test environment to be the same as that in the target environment. Also
in our target environment, we can trap any overflows or other errors. So we can write and test our code on our desktop system
and download the code to the target knowing that it just has to work. This is a huge time saver and confidence builder. For an
extended example on how this is done, look at Safety Critical Embedded Controller .
Header
#include <boost/numeric/safe_numerics/cpp.hpp>
8. Exception Safety
All operations in this library are exception safe and meet the strong guarantee.
9. Library Implementation
This library should compile and run correctly on any conforming C++14 compiler.
70
Safe Numerics
The Safe Numerics library is implemented in terms of some more fundamental software components described here. It is not
necessary to know about these components to use the library. This information has been included to help those who want to
understand how the library works so they can extend it, correct bugs in it, or understand its limitations. These components are also
interesting and likely useful in their own right. For all these reasons, they are documented here.
At compile time:
• The library defines "safe" versions of C++ primitive arithmetic types such as int, unsigned int, etc.
• Arithmetic operators are defined for these "safe" types. These operators are enhanced versions of the standard C/C++
implementations. These operators are declared and implemented in the files "safe_base.hpp" and "safe_base_operations.hpp".
• For binary operators, verify that both operands have the same promotion and and exception handling policies. If they don't,
invoke compilation error.
• Invoke the promotion policy to determine the result type R of the operation.
• For each operand of type T retrieve the range of values from std::numeric_limits<T>::min() and
std::numeric_limits<T>::max(). A range is a pair of values representing a closed interval with a minimum and
maximum value.
• These ranges are cast to equivalent values of the result type, R. It's possible that values cannot be cast to the result type so the
result of the cast is returned as a variant type, checked_result<R>. checked_result<R> may hold either a value of type
R or a safe_numerics_error value indicating why the cast could not be accomplished. Ranges are represented as a pair of
values of the type checked_result<R>.
• checked_result<R> can be considered enhanced versions of the underlying type R. Operations which are legal on values of
type R such as +, -, ... are also legal on values of checked_result<R>. The difference is that the latter can record operation
failures and propagate such failures to subsequent operations.checked_result<R> is implemented in the header file
"checked_result.hpp". Operations on such types are implemented in "checked_result_operations.hpp".
• Given the ranges of the operands, determine the range of the result of the operation using compile-time interval arithmetic.
The constexpr facility of C++14 permits the range of the result to be calculated at compile time. Interval arithmetic is
implemented in the header file "interval.hpp". The range of the result is also represented as a pair of values of the type
checked_result<R>.
• Operations on primitives are implemented via free standing functions described as checked arithmetic. These operations will
return instances of checked_result<R> .
At run time:
• If the range of the result type includes only arithmetically valid values, the operation is guaranteed to produce an arithmetically
correct result and no runtime checking is necessary. The operation invokes the original built-in C/C++ operation and returns the
result value.
• Otherwise, operands are cast to the result type, R, according to the selected promotion policy. These "checked" cast operations
return values of type checked_result<R>.
• If either of the casting operations fails, an exception is handled in accordance with the exception policy.
• Otherwise, the operation is performed using "checked arithmetic". These free functions mirror the normal operators +, -, *, ...
except that rather than returning values of type R, they return values of the type checked_result<R>. They are defined in
files "checked_default.hpp", "checked_integer.hpp" ,"checked_float.hpp".
71
Safe Numerics
• If the operation is not successful, the designated exception policy function is invoked.
• Otherwise, the result value is returned as a safe<R> type with the above calculated result range.
9.1. checked_result<R>
Description
checked_result is a special kind of variant class designed to hold the result of some operation. It can hold either the result of the
operation or information on why the operation failed to produce a valid result. It is similar to other types proposed for and/or
included to the C++ standard library or Boost such as expected, variant, optional and outcome. In some circumstances it
may be referred to as a "monad".
• All instances of checked_result<R> are immutable. That is, once constructed, they cannot be altered.
• Binary operations can be invoked on a pair of checked_result<R> instances if and only if the underlying type (R) is
identical for both instances. They will return a value of type checked_result<R>.
• Unary operations can be invoked on checked_result<R> instances. They will return a value of type checked_result<R>.
• Comparison operations will return a boost::logic::tribool. Other binary operations will a value of the same type as the
arguments.
Think of checked<R> as an "extended" version of R which can hold all the values that R can hold in addition other "special
values". For example, consider checked<int>.
Notation
Symbol Description
R Underlying type
r An instance of type R
72
Safe Numerics
Template Parameters
R must model the type requirements of Numeric
Parameter Description
R Underlying type
Model of
Numeric
Valid Expressions
All expressions are constexpr.
static_cast<const char *>(c) const char * returns pointer to the included error message
73
Safe Numerics
Example of use
#include <iostream>
#include <boost/safe_numerics/checked_result.hpp>
#include <boost/safe_numerics/checked_result_operations.hpp>
int main(){
using ext_uint = boost::safe_numerics::checked_result<unsigned int>;
const ext_uint x{4};
const ext_uint y{3};
// operation is a success!
std::cout << "success! x - y = " << x - y;
// subtraction would result in -1, and invalid result for an unsigned value
std::cout << "problem: y - x = " << y - x;
const ext_uint z = y - x;
std::cout << "z = " << z;
// sum of two negative overflows is a negative overflow.
std::cout << "z + z" << z + z;
return 0;
}
See Also
ExceptionPolicy
Header
#include <boost/numeric/safe_numerics/checked_result.hpp>
#include <boost/numeric/safe_numerics/checked_result_operations.hpp>
74
Safe Numerics
Type requirements
All template parameters of the functions must model Numeric type requirements.
Complexity
Each function performs one and only one arithmetic operation.
Example of use
#include <boost/numeric/safe_numerics/checked_default.hpp>
Notes
Some compilers have command line switches (e.g. -ftrapv) which enable special behavior such that erroneous integer operations
are detected at run time. The library has been implemented in such a way that these facilities are not used. It's possible they might
be helpful in particular environment. These could be be exploited by re-implementing some functions in this library.
Synopsis
// safe casting on primitive types
template<class R, class T>
checked_result<R> constexpr checked::cast(const T & t);
75
Safe Numerics
template<class R>
bool constexpr checked::greater_than(const R & t, const R & u);
// left shift
template<class R>
checked_result<R> constexpr checked::left_shift(const R & t, const R & u);
// right shift
template<class R>
checked_result<R> constexpr checked::right_shift(const R & t, const R & u);
// bitwise operations
template<class R>
checked_result<R> constexpr checked::bitwise_or(const R & t, const R & u);
template<class R>
checked_result<R> constexpr checked::bitwise_and(const R & t, const R & u);
template<class R>
checked_result<R> constexpr checked::bitwise_xor(const R & t, const R & u);
See Also
checked_result<R>
Header
#include <boost/numeric/safe_numerics/checked_default.hpp>
#include <boost/numeric/safe_numerics/checked_integer.hpp>
#include <boost/numeric/safe_numerics/checked_float.hpp>
9.3. interval<R>
Description
A closed arithmetic interval represented by a pair of elements of type R. In principle, one should be able to use Boost.Interval
library for this. But the functions in this library are not constexpr. Also, this Boost.Interval is more complex and does not
support certain operations such bit operations. Perhaps some time in the future, Boost.Interval will be used instead of this
interval<R> type.
Template Parameters
R must model the type requirements of Numeric. Note this in principle includes any numeric type including floating point
numbers and instances of checked_result<R>.
76
Safe Numerics
Notation
Symbol Description
I An interval type
r An instance of type R
Associated Types
Valid Expressions
Note that all expressions are constexpr.
make_interval<R>(const
interval<R> return new interval with std::numric_limits<R>::min() and
R &r) std::numric_limits<R>::max()
77
Safe Numerics
i % j interval<R> calculate modulus of one interval by another and return the result
i << j interval<R> calculate the range that would result from shifting one interval by another
i >> j interval<R> calculate the range that would result from shifting one interval by another
i | j interval<R> range of values which can result from applying | to any pair of operands
from I and j
i & j interval<R> range of values which can result from applying & to any pair of operands
from I and j
i ^ j interval<R> range of values which can result from applying ^ to any pair of operands
from I and j
Example of use
#include <iostream>
#include <cstdint>
#include <cassert>
#include <boost/numeric/safe_numerics/interval.hpp>
int main(){
std::cout << "test1" << std::endl;
interval<std::int16_t> x = {-64, 63};
std::cout << "x = " << x << std::endl;
interval<std::int16_t> y(-128, 126);
78
Safe Numerics
Header
#include <boost/numeric/safe_numerics/interval.hpp>
Description
Compare two primitive integers. These functions will return a correct result regardless of the type of the operands. Specifically it
is guaranteed to return the correct arithmetic result when comparing signed and unsigned types of any size. It does not follow the
standard C/C++ procedure of converting the operands to some common type then doing the compare. So it is not equivalent to the
C/C++ binary operations <, >, >=, <=, ==, != and shouldn't be used by user programs which should be portable to standard C/C++
integer arithmetic. The functions are free functions defined inside the namespace boost::numeric::safe_compare.
Type requirements
All template parameters of the functions must be C/C++ built-in integer types, char, int ....
Complexity
Each function performs one and only one arithmetic operation.
Example of use
#include <cassert>
#include <safe_compare.hpp>
79
Safe Numerics
Header
#include <boost/numeric/safe_numerics/safe_compare.hpp>
So far we've only run one explicit performance test - test_performance.cpp. This runs a test from the Boost Multiprecision
library to count prime numbers and makes extensive usage of integer arithmetic. We've run the tests with unsigned integers and
with safe<unsigned> on two different compilers.. No other change was made to the program. We list the results without further
comment.
clang-802.0.41
Testing type unsigned:
time = 16.9174
count = 1857858
Testing type safe<unsigned>:
time = 36.5166
count = 1857858
This surprised me when it was first raised. But some of the feedback I've received makes me think that it's a widely
held view. The best answer is to consider the examples in the Tutorials and Motivating Examples section of the library
documentation. I believe they convincingly demonstrate that any program which does not use this library must be assumed
to contain arithmetic errors.
11.2. Can safe types be used as drop-in replacements for built-in types?
Almost. Replacing all built-in types with their safe counterparts should result in a program that will compile and run as
expected. Occasionally compile time errors will occur and adjustments to the source code will be required. Typically these
will result in code which is more correct.
80
Safe Numerics
11.3. Why are there special types for literal such as safe_signed_literal<42>? Why not just use std::integral_const<int,
42>?
By defining our own "special" type we can simplify the interface. Using std::integral_const requires one to specify
both the type and the value. Using safe_signed_literal<42> doesn't require a parameter for the type. So the library
can select the best type to hold the specified value. It also means that one won't have the opportunity to specify a type-
value pair which are inconsistent.
11.4. Why is safe...literal needed at all? What's the matter with const safe<int>(42)?
const safe<int>(42) looks like it might be what we want: An immutable value which invokes the "safe" operators
when used in an expression. But there is one problem. The std::numeric_limits<safe<int>> is a range from
INTMIN to INTMAX even though the value is fixed to 42 at compile time. It is this range which is used at compile time
to calculate the range of the result of the operation.
So when an operation is performed, the range of the result is calculated from [INTMIN, INTMAX] rather than from
[42,42].
11.5. Are safe type operations constexpr? That is, can they be invoked at compile time?
Yes. safe type construction and calculations are all constexpr. Note that to get maximum benefit, you'll have to use
safe...literal to specify the primitive values at compile time.
Almost, but there are still good reasons to create a different type.
• std::integral_constant<int, 42> requires specification of type as well as value so it's less convenient than
safe_signed_literal which maps to the smallest type required to hold the value.
• But globally altering the traits of std::integral_constant might have unintended side-effects related to other code.
These might well be surprises which are create errors which are hard to find and hard to work around.
11.8. Why is the library named "safe ..." rather than something like "checked ..." ?
I used "safe" in large part because this is what has been used by other similar libraries. Maybe a better word might have
been "correct" but that would raise similar concerns. I'm not inclined to change this. I've tried to make it clear in the
documentation what the problem that the library addressed is.
11.9. Given that the library is called "numerics" why is floating point arithmetic not addressed?
Actually, I believe that this can/should be applied to any type T which satisfies the type requirement Numeric type as
defined in the documentation. So there should be specializations safe<float> and related types as well as new types like
safe<fixed_decimal> etc. But the current version of the library only addresses integer types. Hopefully the library will
evolve to match the promise implied by its name.
11.10. Isn't putting a defensive check just before any potential undefined behavior often considered a bad practice?
81
Safe Numerics
By whom? Is leaving code which can produce incorrect results better? Note that the documentation contains references
to various sources which recommend exactly this approach to mitigate the problems created by this C/C++ behavior. See
[Seacord]
11.11. It looks like the implementation presumes two's complement arithmetic at the hardware level. So this library is not
portable - correct? What about other hardware architectures?
As far as is known as of this writing, the library does not presume that the underlying hardware is two's complement.
However, this has yet to be verified in any rigorous way.
11.12. According to C/C++ standards, unsigned integers cannot overflow - they are modular integers which "wrap around".
Yet the safe numerics library detects and traps this behavior as errors. Why is that?
The guiding purpose of the library is to trap incorrect arithmetic behavior - not just undefined behavior. Although a savvy
user may understand and keep present in his mind that an unsigned integer is really a modular type, the plain reading of an
arithmetic expression conveys the idea that all operands are common integers. Also in many cases, unsigned integers
are used in cases where modular arithmetic is not intended, such as array indices. Finally, the modulus for such an integer
would vary depending upon the machine architecture. For these reasons, in the context of this library, an unsigned
integer is considered to be a representation of a subset of integers. Note that this decision is consistent with [INT30-C],
“Ensure that unsigned integer operations do not wrap” in the CERT C Secure Coding Standard [Seacord].
The original version of the library used C++11. Feedback from CPPCon, Boost Library Incubator and Boost developer's
mailing list convinced me that I had to address the issue of run-time penalty much more seriously. I resolved to eliminate
or minimize it. This led to more elaborate meta-programming. But this wasn't enough. It became apparent that the only
way to really minimize run-time penalty was to implement compile-time integer range arithmetic - a pretty elaborate sub
library. By doing range arithmetic at compile-time, I could skip runtime checking on many/most integer operations. While
C++11 constexpr wasn't quite powerful enough to do the job, C++14 constexpr is. The library currently relies very
heavily on C++14 constexpr. I think that those who delve into the library will be very surprised at the extent that minor
changes in user code can produce guaranteed correct integer code with zero run-time penalty.
11.14. This is a C++ library - yet you refer to C/C++. Which is it?
C++ has evolved way beyond the original C language. But C++ is still (mostly) compatible with C. So most C programs
can be compiled with a C++ compiler. The problems of incorrect arithmetic afflict both C and C++. Suppose we have a
legacy C program designed for some embedded system.
• Replace all int declarations with int16_t and all long declarations with int32_t.
• Create a file containing something like the following and include it at the beginning of every source file.
#ifdef TEST
// using C++ on test platform
#include <cstdint>
#include <boost/numeric/safe_numerics/safe_integer.hpp>
#include <cpp.hpp>
using pic16_promotion = boost::numeric::cpp<
8, // char
8, // short
8, // int
16, // long
32 // long long
>;
// define safe types used in the desktop version of the program.
template <typename T> // T is char, int, etc data type
using safe_t = boost::numeric::safe<
82
Safe Numerics
T,
pic16_promotion,
boost::numeric::default_exception_policy // use for compiling and running tests
>;
typedef safe_t<std::int_least16_t> int16_t;
typedef safe_t<std::int_least32_t> int32_t;
#else
/* using C on embedded platform */
typedef int int_least16_t;
typedef long int_least16_t;
#endif
• Compile tests on the desktop with a C++14 compiler and with the macro TEST defined.
• Run the tests and change the code to address any thrown exceptions.
• Compile for the target C platform with the macro TEST undefined.
This example illustrates how this library, implemented with C++14 can be useful in the development of correct code for
programs written in C.
11.15. Some compilers (including gcc and clang) include builtin functions for checked addition, multiplication, etc. Does this
library use these intrinsics?
No. I attempted to use these but they are currently not constexpr. So I couldn't use these without breaking constexpr
compatibility for the safe numerics primitives.
11.16. Some compilers (including gcc and clang) included a builtin function for detecting constants. This seemed attractive
to eliminate the requirement for the safe_literal type. Alas, these builtin functions are defined as macros. Constants
passed through functions down into the safe numerics library cannot be detected as constants. So the opportunity to
make the library even more efficient by moving more operations to compile time doesn't exist - contrary to my hopes and
expections.
“In the current implementation safe<T> will only work with T being a C++ scalar type. Therefore making a general type
requirements that say what operations are allowed is superfluous, and confusing (because it implies that safe<> is more generic.”
When I started out, It became clear that I wanted "safe" types to look like "numeric" types. It also became clear pretty soon that
there was going to be significant template meta-programming in the implementation. Normal type traits like std::is_integer are
defined in the std namespace and one is discouraged from extending it. Also I needed some compile time "max" and "lowest"
values. This lead me to base the design on std::numeric_limits. But std::numeric limits is inherently extensible to any "numeric"
type. For example, money is a numeric type but not an intrinsic types. So it seemed that I needed to define a "numeric" concept
which required that there be an implementation of std::numeric_limits for any type T - such as money in this case. When I'm
doubt - I tend to think big.
For now though I'm not going to address it. For what it's worth, my preference would be to do something like:
83
Safe Numerics
template<typename T>
struct range {
T m_lowest;
T m_highest;
// default implementation
range(
const & T t_min,
const & T t_max
) :
m_lowest(std::numeric_limits<T>::lowest(t_min),
m_highest(std::numeric_limits<T>::max(t_max)
{}
};
Also note that for C++20, template value parameters are no longer restricted to integer primitive types but may be class types as
well. This means the library maybe extended to user class types without changing the current template signatures.
• Although care has been taken to make the library portable, at least some parts of the implementation - particularly checked
integer arithmetic - depend upon two's complement representation of integers. Hence the library is probably not currently
portable to all other possible C++ architectures. These days, this is unlikely to be a limitation in practice. Starting with C++20,
integer arithmetic will be guaranteed by the C++ standard to be two's complement.
• std::common_type is used in a variety of generic libraries, including std::chrono. Without a specialization for safe<T>s one
cannot use the safe wrappers e.g. as a representation for std::chrono::duration.
13. Acknowledgements
This library would never have been created without inspiration, collaboration and constructive criticism from multiple sources.
David LeBlanc This library is inspired by David LeBlanc's SafeInt Library . I found this library very well
done in every way and useful in my embedded systems work. This motivated me to take it to
the "next level".
Andrzej Krzemienski Andrzej Commented and reviewed the library as it was originally posted on the Boost Library
Incubator. The consequent back and forth motivated me to invest more effort in developing
documentation and examples to justify the utility, indeed the necessity, for this library. He
also noted many errors in code, documentation, and tests. Without his interest and effort, I do
not believe the library would have progressed beyond its initial stages.
Boost As always, the Boost Developer's mailing list has been the source of many useful
observations from potential users and constructive criticism from very knowledgeable
developers. During the Boost formal review, reviews and comments were posted by the
following persons:
• Paul A. Bristow
• Steven Watanabe
• John Maddock
84
Safe Numerics
• Antony Polukhin
• Barrett Adair
• John McFarlane
• Peter Dimov
Revision History
Revision 1.69 29 September 2018
First Boost Release
Revision 1.70 9 March 2019
Fixed Exception Policies for trap and ignore.
15. Bibliography
Bibliography
[Coker] Zack Coker. Samir Hasan. Jeffrey Overbey. Munawar Hafiz. Christian Kästner. Integers In C: An Open Invitation To
Security Attacks? . JTC1/SC22/WG21 - The C++ Standards Committee - ISOCPP . January 15, 2012.
[Crowl] Lawrence Crowl. C++ Binary Fixed-Point Arithmetic . JTC1/SC22/WG21 - The C++ Standards Committee - ISOCPP
. January 15, 2012.
[Crowl & Ottosen] Lawrence Crowl. Thorsten Ottosen. Proposal to add Contract Programming to C++ . WG21/N1962 and
J16/06-0032 - The C++ Standards Committee - ISOCPP . February 25, 2006.
[Dietz] Will Dietz. Peng Li. John Regehr. Vikram Adve. Understanding Integer Overflow in C/C++ . Proceedings of the 34th
International Conference on Software Engineering (ICSE), Zurich, Switzerland . June 2012.
[Garcia] J. Daniel Garcia. C++ language support for contract programming . WG21/N4293 - The C++ Standards Committee -
ISOCPP . December 23, 2014.
[Goldberg] David Goldberg. What Every Computer Scientist Should Know About Floating-Point Arithmetic . ACM Computing
Surveys . March, 1991.
[keaton] David Keaton. Thomas Plum. Robert C. Seacord. David Svoboda. Alex Volkovitsky. Timothy Wilson. As-if Infinitely
Ranged Integer Model . Software Engineering Institute . CMU/SEI-2009-TN-023.
[LeBlanc] David LeBlanc. Integer Handling with the C++ SafeInt Class . Microsoft Developer Network . January 7, 2004.
[Lions] Jacques-Louis Lions. Ariane 501 Inquiry Board report . Wikisource . July 19, 1996.
85
Safe Numerics
[Matthews] Hubert Matthews. CheckedInt: A Policy-Based Range-Checked Integer . Overload Journal #58 . December 2003.
[Mouawad] Jad Mouawad. F.A.A Orders Fix for Possible Power Loss in Boeing 787 . New York Times. April 30, 2015.
[Plakosh] Daniel Plakosh. Safe Integer Operations . U.S. Department of Homeland Security . May 10, 2013.
[Seacord] Robert C. Seacord. Secure Coding in C and C++ . 2nd Edition. Addison-Wesley Professional. April 12, 2013.
978-0321822130.
[INT30-C] Robert C. Seacord. INT30-C. Ensure that operations on unsigned integers do not wrap . Software Engineering
Institute, Carnegie Mellon University . August 17, 2014.
[INT32-C] Robert C. Seacord. INT32-C. Ensure that operations on signed integers do not result in overflow . Software
Engineering Institute, Carnegie Mellon University . August 17, 2014.
[Stroustrup] Bjarn Stroustrup. The C++ Programming Language. Fourth Edition. Addison-Wesley . Copyright © 2014 by
Pearson Education, Inc.. January 15, 2012.
[Forum] Forum Posts. C++ Binary Fixed-Point Arithmetic . ISO C++ Standard Future Proposals .
86