fixed_point  rev.2
Binary Fixed-Point Arithmetic Library in C++
User Manual

Table of Contents

Introduction

The fixed_point library provides a header-only C++11 API for approximating real numbers using binary fixed-point arithmetic. It forms the reference implementation for a standard library proposal presented in paper, P0037 and is developed as part of study groups, SG14 and SG6. Installation instructions are provided on the project page.

Features

Header, sg14/fixed_point, defines class template, fixed_point, with:

Header, sg14/num_traits, contains additions (such as set_digits) that support widening of arithmetic types in order to deal with precision loss.

Auxiliary modules:

Examples

The following examples can be found in the test suite.

Declaration

The fixed_point type extends the behavior of integer types in a natural way. It represents a real number using an integer type, scaled by a power of two.

#include <sg14/fixed_point>
#include <iostream>
using namespace sg14;
using namespace std;
void declaration_example()
{
// x is represented by an int and scaled down by 1 bit
auto x = fixed_point<int, -1>{3.5};
// another way to specify a fixed-point type is with make_fixed or make_ufixed
auto y = make_fixed<30, 1>{3.5}; // (s30:1)
static_assert(is_same<decltype(x), decltype(y)>::value, ""); // assumes that int is 32-bit
// under the hood, x stores a whole number
cout << x.data() << endl; // "7"
// but it multiplies that whole number by 2^-1 to produce a real number
cout << x << endl; // "3.5"
// like an int, x has limited precision
x /= 2;
cout << x << endl; // "1.5"
}

Arithmetic Operators

Specializations of fixed_point behave a lot like native C/C++ numeric types. Operators are designed to behave in an way which is both predictable and efficient.

void basic_arithmetic_example()
{
// define a constant signed value with 3 integer and 28 fractional bits (s3:28)
auto pi = fixed_point<int32_t, -28>{3.1415926535};
// expressions involving integers return fixed_point results
auto tau = pi*2;
static_assert(is_same<decltype(tau), fixed_point<int32_t, -28>>::value, "");
// "6.28319"
cout << tau << endl;
// expressions involving floating-point values return floating-point results
auto degrees = tau*(180/3.1415926534);
static_assert(is_same<decltype(degrees), double>::value, "");
// "360"
cout << degrees << '\n';
}

Arithmetic Functions

But one size does not fit all. Different applications of the same operation might call for different trade-offs between storage, precision, safety and speed. Named functions - such as multiply - provide fine-tuned control over arithmetic results.

#include <iomanip>
void advanced_arithmetic_example()
{
// this variable uses all of its capacity
auto x = fixed_point<uint8_t, -4>{15.9375};
// 15.9375 * 15.9375 = 254.00390625 ... overflow!
cout << fixed_point<uint8_t, -4>{x*x} << endl; // "14" instead!
// fixed-point multiplication operator widens result
auto xx = x*x;
// x*x is promoted to fixed_point<int, -8>
static_assert(is_same<decltype(xx), fixed_point<int, -8>>::value, "");
cout << setprecision(12) << xx << endl; // "254.00390625" - correct
// you can avoid the pitfalls of integer promotion using the multiply function
auto named_xx = multiply(x, x);
// multiply result is same as underlying representation's operation
static_assert(is_same<decltype(named_xx), fixed_point<uint16_t, -8>>::value, "");
cout << named_xx << endl; // "254.00390625" - also correct but prone to overflow
}

Extensible

Because one size does not fit all, fixed_point is designed to make it easy to tailor new arithmetic types. The elastic_fixed_point type illustrates this. As each calculation requires more digits, so the results of elastic_fixed_point operations allocate more storage.

#include <sg14/auxiliary/elastic_integer.h>
void elastic_example1()
{
// Consider an integer type which keeps count of the bits that it uses.
auto a = elastic_integer<6, int8_t>{ 63 };
// Results of its operations widen as required.
auto aa = a*a;
static_assert(is_same<decltype(aa), elastic_integer<12, int8_t >> ::value, "");
// Obviously, this type no longer fits in a byte.
static_assert(sizeof(aa)==2, "");
// Addition requires smaller results.
auto a2 = a+a;
static_assert(is_same<decltype(a2), elastic_integer<7, int8_t >> ::value, "");
}
// Such a type can be used to specialize fixed_point.
template<int IntegerDigits, int FractionalDigits, class Narrowest>
using elastic_fixed_point = fixed_point<elastic_integer<IntegerDigits+FractionalDigits, Narrowest>, -FractionalDigits>;
void elastic_example2()
{
// Now arithmetic operations are more efficient and less error-prone.
auto b = elastic_fixed_point<4, 28, unsigned>{15.9375};
auto bb = b*b;
cout << bb << endl; // "254.00390625"
static_assert(is_same<decltype(bb), elastic_fixed_point<8, 56, unsigned>>::value, "");
}