123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE section PUBLIC "-//Boost//DTD BoostBook XML V1.1//EN"
- "http://www.boost.org/tools/boostbook/dtd/boostbook.dtd">
- <section id="safe_numerics.eliminate_runtime_penalty">
- <title>Eliminating Runtime Penalty</title>
- <para>Up until now, we've mostly focused on detecting when incorrect results
- are produced and handling these occurrences either by throwing an exception
- or invoking some designated function. We've achieved our goal of detecting
- and handling arithmetically incorrect behavior - but at cost of checking
- many arithmetic operations at runtime. It is a fact that many C++
- programmers will find this trade-off unacceptable. So the question arises as
- to how we might minimize or eliminate this runtime penalty.</para>
- <para>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: <link
- linkend="safe_numerics.exception_policies.loose_trap_policy"><code>loose_trap_policy</code></link>.</para>
- <para><programlisting><xi:include href="../../example/example81.cpp"
- parse="text" xmlns:xi="http://www.w3.org/2001/XInclude"/></programlisting>Now,
- any expression which <emphasis><emphasis
- role="bold">might</emphasis></emphasis> fail at runtime is flagged with a
- compile time error. There is no longer any need for <code>try/catch</code>
- blocks. Since this program does not compile, the <emphasis
- role="bold">library absolutely <emphasis role="bold">guarantees that no
- arithmetic expression</emphasis> will yield incorrect results</emphasis>.
- Furthermore, it is <emphasis role="bold">absolutely guaranteed that no
- exception will ever be thrown</emphasis>. This is our original goal.</para>
- <para>Now all we need to do is make the program compile. There are a couple
- of ways to achieve this.</para>
- <section id="safe_numerics.eliminate_runtime_penalty.2">
- <title>Using <link linkend="safe_numerics.safe_range">safe_range</link>
- and <link linkend="safe_numerics.safe_literal">safe_literal</link></title>
- <para>When trying to avoid arithmetic errors of the above type,
- programmers will select data types which are wide enough to hold values
- large enough to be certain that results won't overflow, but are not so
- large as to make the program needlessly inefficient. In the example below,
- we presume we know that the values we want to work with fall in the range
- [-24,82]. So we "know" the program will always result in a correct result.
- But since we trust no one, and since the program could change and the
- expressions be replaced with other ones, we'll still use the <link
- linkend="safe_numerics.exception_policies.loose_trap_policy"><code>loose_trap_policy</code></link>
- exception policy to verify at compile time that what we "know" to be true
- is in fact true.</para>
- <programlisting><xi:include href="../../example/example83.cpp"
- parse="text" xmlns:xi="http://www.w3.org/2001/XInclude"/></programlisting>
- <para><itemizedlist>
- <listitem>
- <para><code><code>safe_signed_range</code></code> 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.</para>
- </listitem>
- <listitem>
- <para>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 <code>safe_signed_literal</code>
- does.</para>
- </listitem>
- <listitem>
- <para>Defining constants with <code>safe_signed_literal</code>
- enables the library to correctly anticipate the correct range of the
- results of arithmetic expressions at compile time.</para>
- </listitem>
- <listitem>
- <para>The usage of <code><link
- linkend="safe_numerics.exception_policies.loose_trap_policy"><code>loose_trap_policy</code></link></code>
- will mean that any assignment to z which could be outside its legal
- range will result in a compile time error.</para>
- </listitem>
- <listitem>
- <para>All safe integer operations are implemented as constant
- expressions. The usage of <code>constexpr</code> will guarantee that
- <code>z</code> will be available at compile time for any subsequent
- use.</para>
- </listitem>
- <listitem>
- <para>So if this program compiles, it's guaranteed to return a valid
- result.</para>
- </listitem>
- </itemizedlist>The output uses a custom output manipulator,
- <code>safe_format</code>, for safe types to display the underlying type
- and its range as well as current value. This program produces the
- following run time output.</para>
- <screen>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</screen>
- <para>Take note of the various variable types:<itemizedlist>
- <listitem>
- <para><code>x</code> and <code>y</code> 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.</para>
- </listitem>
- <listitem>
- <para><code>The sum x + y can also be determined at compile
- time.</code></para>
- </listitem>
- <listitem>
- <para>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 <code>z</code> is guaranteed to hold. If the
- sum could not be be guaranteed to fall in the range of
- <code>z</code>, we would get a compile time error due to the fact we
- are using the <code>loose_trap_policy</code> exception
- policy.</para>
- </listitem>
- </itemizedlist>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 <code>safe_t</code> and <code>const_safe_t</code>
- could be redefined to <code>int</code> and <code>const int</code>
- 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.</para>
- </section>
- <section id="safe_numerics.eliminate_runtime_penalty.1">
- <title>Using Automatic Type Promotion</title>
- <para>The C++ standard describes how binary operations on different
- integer types are handled. Here is a simplified version of the
- rules:</para>
- <itemizedlist>
- <listitem>
- <para>promote any operand smaller than <code>int</code> to an
- <code>int</code> or <code>unsigned int</code>.</para>
- </listitem>
- <listitem>
- <para>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.</para>
- </listitem>
- <listitem>
- <para>Convert the type each operand to the type of the result,
- expanding the size as necessary.</para>
- </listitem>
- <listitem>
- <para>Perform the operation the two resultant operands.</para>
- </listitem>
- </itemizedlist>
- <para>So the type of the result of some binary operation may be different
- than the types of either or both of the original operands.</para>
- <para>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.</para>
- <para>The complete signature for a safe integer type is:</para>
- <para><programlisting>template <
- class T, // underlying integer type
- class P = native, // type promotion policy class
- class E = default_exception_policy // error handling policy class
- >
- safe;
- </programlisting></para>
- <para>The promotion rules for arithmetic operations are implemented in the
- default <code><link
- linkend="safe_numerics.promotion_policies.native">native</link></code>
- type promotion policy are consistent with those of standard C++</para>
- <para>Up until now, we've focused on detecting when an arithmetic error
- occurs and invoking an exception or other kind of error handler.</para>
- <para>But now we look at another option. Using the <link
- linkend="safe_numerics.promotion_policies.automatic"><code>automatic</code></link>
- type promotion policy, we can change the rules of C++ arithmetic for safe
- types to something like the following:</para>
- <para><itemizedlist>
- <listitem>
- <para>for any C++ numeric type, we know from <ulink
- url="http://en.cppreference.com/w/cpp/types/numeric_limits"><code>std::numeric_limits</code></ulink>
- what the maximum and minimum values that a variable can be - this
- defines a closed interval.</para>
- </listitem>
- <listitem>
- <para>For any binary operation on these types, we can calculate the
- interval of the result at compile time.</para>
- </listitem>
- <listitem>
- <para>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:</para>
- <programlisting>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</programlisting>
- <para>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 <link
- linkend="safe_numerics.promotion_policies.automatic"><code>automatic</code></link>
- type promotion policy will achieve the equivalent result without
- these problems.</para>
- </listitem>
- </itemizedlist></para>
- <para>When using the <link
- linkend="safe_numerics.promotion_policies.automatic"><code>automatic</code></link>
- 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.</para>
- <para>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 <link
- linkend="safe_numerics.promotion_policies.automatic"><code>automatic</code></link>
- type promotion policy might eliminate exceptions in our example above, it
- wouldn't be guaranteed to eliminate them for all programs.</para>
- <para>Using the <link
- linkend="safe_numerics.exception_policies.loose_trap_policy"><code>loose_trap_policy</code></link>
- exception policy will produce a compile time error anytime it's possible
- for an error to occur.</para>
- <para>This small example illustrates how to use automatic type promotion
- to eliminate all runtime penalty.</para>
- <para><programlisting><xi:include href="../../example/example82.cpp"
- parse="text" xmlns:xi="http://www.w3.org/2001/XInclude"/></programlisting></para>
- <itemizedlist>
- <listitem>
- <para>the <link
- linkend="safe_numerics.promotion_policies.automatic"><code>automatic</code></link>
- type promotion policy has rendered the result of the sum of two
- <code>integers</code> as a <code>safe<long</code>> type.</para>
- </listitem>
- <listitem>
- <para>our program compiles without error - even when using the <link
- linkend="safe_numerics.exception_policies.loose_trap_policy"><code>loose_trap_policy</code></link>
- exception policy. This is because since a <code>long</code> can always
- hold the result of the sum of two integers.</para>
- </listitem>
- <listitem>
- <para>We do not need to use the <code>try/catch</code> idiom to handle
- arithmetic errors - we will have no exceptions.</para>
- </listitem>
- <listitem>
- <para>We only needed to change two lines of code to achieve our goal
- of guaranteed program correctness with no runtime penalty.</para>
- </listitem>
- </itemizedlist>
- <para>The above program produces the following output:</para>
- <para><screen>example 82:
- x = <int>[-2147483648,2147483647] = 2147483647
- y = <int>[-2147483648,2147483647] = 2
- x + y = <long>[-4294967296,4294967294] = 2147483649
- </screen></para>
- <para>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 <link
- linkend="safe_numerics.exception_policies.loose_trap_policy"><code>loose_trap_policy</code></link>
- 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 <link
- linkend="safe_numerics.exception_policies.default_exception_policy"><code>default_exception_policy</code></link>
- .</para>
- <para>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:</para>
- <programlisting>// 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
- </programlisting>
- </section>
- <section id="safe_numerics.eliminate_runtime_penalty.3">
- <title>Mixing Approaches</title>
- <para>For purposes of exposition, we've divided the discussion of how to
- eliminate runtime penalties by the different approaches available. A
- realistic program could likely include all techniques mentioned above.
- Consider the following:</para>
- <programlisting><xi:include href="../../example/example84.cpp"
- parse="text" xmlns:xi="http://www.w3.org/2001/XInclude"/></programlisting>
- <para><itemizedlist>
- <listitem>
- <para>As before, we define a type <code>safe_t</code> to reflect our
- view of legal values for this program. This uses the <link
- linkend="safe_numerics.promotion_policies.automatic"><code>automatic</code></link>
- type promotion policy as well as the <link
- linkend="safe_numerics.exception_policies.loose_trap_policy"><code>loose_trap_policy</code></link>
- exception policy to enforce elimination of runtime penalties.</para>
- </listitem>
- <listitem>
- <para>The function <code>f</code> accepts only arguments of type
- <code>safe_t</code> so there is no need to check the input values.
- This performs the functionality of <emphasis><emphasis
- role="bold">programming by contract</emphasis></emphasis> with no
- runtime cost.</para>
- </listitem>
- <listitem>
- <para>In addition, we define <code>input_safe_t</code> 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.</para>
- </listitem>
- <listitem>
- <para>On calling of the function <code>f</code>, arguments of type
- <code>input_safe_t</code> are converted to values of type
- <code>safe_t</code> . In this particular example, it can be
- determined at compile time that construction of an instance of a
- <code>safe_t</code> from an <code>input_safe_t</code> can never
- fail. Hence, no <code>try/catch</code> block is necessary. The usage
- of the <link
- linkend="safe_numerics.exception_policies.loose_trap_policy"><code>loose_trap_policy</code></link>
- policy for <code>safe_t</code> types guarantees this to be true at
- compile time.</para>
- </listitem>
- </itemizedlist>Here is the output from the program when values 12 and 32
- are input from the console:</para>
- <screen>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
- </screen>
- </section>
- </section>
|