Component of the Week #12: bdldfp_decimal
- Summary:
Accurately represent base-10 floating-point numbers.
As humans we tend naturally to thinking of numbers in base 10 — this can probably be blamed on the shapes of our hands. Computers, however, are internally binary — everything is ones and zeroes, including how they represent numbers.
With most mathematical operations — addition, subtraction, and multiplication — the distinctions between base 10 and binary matter little, especially when we restrict our domain to the integers. Once we introduce division and go beyond integers, however, we often end up thinking of numbers that might not have a perfect representation in the binary formats that our computers understand. In particular, one fifth has a finite representation as a decimal, while it is has an infinitely repeating representation in binary.
Consider the following example where we add up two simple non-integers, and notice how the result deviates from our intuition for how numbers work:
void testFPMath()
{
const double one_tenth = 0.1; // 0.1000000000000000055511151
const double two_tenths = 0.2; // 0.2000000000000000111022302
const double three_tenths = 0.3; // 0.2999999999999999888977698
const double sum = one_tenth + two_tenths; // 0.300000000000000044408921
BSLS_ASSERT( sum == three_tenths ); // Fails!!!
}
This oddity is, of course, because initializing a double to a decimal literal
will pick the closest binary representation to that decimal
value, but will rarely be able to pick an exact such representation. In this case
one_tenth and two_tenths both contain floating point numbers ever so slightly
higher than the decimals they were initialized with, and so adding them
gives a value that compounds this offset to be even farther away from the
decimal 0.3. Initializing three_tenths, however, finds that the
double value just below 0.3 is closest.
Many learned the risks of this kind of math in 1983 under the tutelage of Richard Pryor (IMDB). The small errors introduced by most floating point math can compound and produce large errors and, as you can see above, highly unintuitive results. There is, however, an alternative option to binary floating point representations that fixes many of these problems — IEEE-754 decimal floating-point.
BDE provides types to encapsulate that representation for numbers — an
integral value associated with an integral power of ten — in the
component
bdldfp_decimal.
In that component we can find the type bdldfp::Decimal64 along with C++11
user-defined literal operators that lets us easily define constants that
finally match our intuition when used in simple mathematical operations:
void testDFPMath()
{
using namespace bdldfp::DecimalLiterals;
const bdldfp::Decimal64 one_tenth = 0.1_d64;
const bdldfp::Decimal64 two_tenths = 0.2_d64;
const bdldfp::Decimal64 three_tenths = 0.3_d64;
BSLS_ASSERT( one_tenth + two_tenths == three_tenths );
}
Of course, because it is still a finite representation there will be equally surprising results when we use numbers that do not have exact base-10 representations.
void testDFPMath2()
{
using namespace bdldfp::DecimalLiterals;
const bdldfp::Decimal64 one_seventh = 1_d64 / 7_d64;
// 0.1428571428571429
const bdldfp::Decimal64 three_sevenths = 3_d64 / 7_d64;
// 0.4285714285714286
const bdldfp::Decimal64 four_sevenths = 4_d64 / 7_d64;
// 0.5714285714285714
const bdldfp::Decimal64 sum = one_seventh + three_sevenths;
// 0.5714285714285715
BSLS_ASSERT( sum == four_sevenths ); // Fails!!
}
Of course, getting an inexact result when we are doing division with finite representations is less surprising. For the uses we encounter as a financial firm, however, decimal floating point is often the perfect tool to transport values exactly as users have written them and perform simple mathematical operations on those values with accuracy.
- If you’d like to know more, check out:
The documentation for bdldfp_decimal where you will find:
Details on the other types made available.
How to parse and format decimal values.
Specifics of the IEE-754 Standard that is implemented by
bdldfp::Decimal.