Library Documentation Index

Safe Numerics

PrevUpHomeNext

Implicit Conversions Can Lead to Erroneous Results

At CPPCon 2016 Jon Kalb gave a very entertaining (and disturbing example) lightening talk related to C++ expressions.

The talk included a very, very simple example similar to the following:

#include <exception>
#include <iostream>

#include "../include/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;
    
    // problem: implicit conversions change data values
    try{
        signed int   a{-1};
        unsigned int b{1};
        if(a < b){
            std::cout << "a is less than b\n";
        }
        else{
            std::cout << "b is less than a\n";
        }
    }
    catch(std::exception){
        // never arrive here - just produce the wrong answer!
        std::cout << "error detected!" << std::endl;
    }

    // solution: replace int with safe<int> and char with safe<char>
    std::cout << "Using safe numerics" << std::endl;
    try{
        using namespace boost::numeric;
        safe<signed int>   a{-1};
        safe<unsigned int> b{1};
        if(a < b){
            std::cout << "a is less than b\n";
        }
        else{
            std::cout << "b is less than a\n";
        }
    }
    catch(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 it's 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:

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 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 every one's pet suggestions. See for example a blog post with every one'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, there is absolutely no extra overhead in using the safe integer library. Code generated will either equal or exceed the efficiency of using primitive integer types.


PrevUpHomeNext