/fp

Fixed Point Arithmetic C++14 Library

Primary LanguageC++ISC LicenseISC

C++14 Fixed Point Library

This is a C++14 header-only fixed-point arithmetic library.

It's purpose is to wrap another type and provide fixed point arithmetic support on top of it.

It's designed to be able to wrap on top of all the built-in arithmetic types (integers and floating point types) as well as user defined arithmetic types.

Examples of other arithmetic types which are tested and preliminary adapters are provided:

  • boost rational
  • boost multiprecision cpp_int and cpp_bin_float
  • David Stone's bounded_integer

The following is a sample of what can be achieved by employing this library:

using namespace fp;

auto x = make_fp<4, int >(3.25);
auto y = make_fp<8, char>(0.75);
auto z = x * y;                  // now z is of type fp_t<int,12>
std::cout << double(z);          // prints 2.4375

// using David Stone's bounded_integer
using namespace bounded::literal;
using namespace fp::constants; // import int_ shorthand for std::integral_constant

// create a bounded fixed point integer with range [1, 100]
// with initial value 30, then perform a virtual right shift
// by 3.
auto x = fp_t<bounded::integer<1, 100>, 0>{  30_bi } >> int_<3>;
// now x holds 3.75 and has range [0.125, 12.5]

//ditto, range [2, 300], initial value 150, virtual right shift by 4
auto y = fp_t<bounded::integer<2, 300>, 0>{ 150_bi } >> int_<4>;
// now y holds 9.375 and has range [0.125, 18.75]

auto z = x + y;

std::cout << double(z); // prints 13.125
// below it shows that type information of the underlying type is not lost
std::cout << double(std::numeric_limits<decltype(z)>::min()); // prints 0.25
std::cout << double(std::numeric_limits<decltype(z)>::max()); // prints 31.25

It needs at least clang 3.4 to compile. Unit tests are included, and these can be built using CMake.

Example building and running tests on unix system:

cmake -DCMAKE_CXX_COMPILER=clang++ <path to source>
make
ctest

How to use

This is a single-header include only library, building is only required for tests. Just #include <fp/fp.hpp> and everything is inside namespace fp. The main type is fp::fp_t and there is also a fp::make_fp helper which does construction with type deduction. The namespace fp::constants contains shorthands int_ and uint_ which can be used for virtual shifting.

Fixed Point Library Description

It implements a template class type fp_t<T, E> where T is an underlying arithmetic type and E is an integer representing the binary point position. From now on these will be referred as the underlying type and the exponent, for T and E respectively.

It supports casting between integral and floating point types and the whole set of arithmetic, bitwise and relational operators. All operations are constexpr themselves, although they are only usable as such if the underlying type also implements them as constexpr.

A virtual shift may be performed by using a std::integral_constant as a shift amount with the usual shift operators << and >>. Convenience aliases int_ and uint_ are provided under namespace fp::constants

All operations preserve the resulting type of the underlying type's operation.

For example, if you have an instance a with type fp_t<A, EXP_A>, and an instance b with type fp_t<B, EXP_B>, and you multiply them together, the type of the expression a * b will be fp_t<decltype(A{} * B{}), EXP_A + EXP_B>. In the specific case that A is char, B is int and EXP_A and EXP_B are 2 and 3, the resulting type will be fp_t<int, 5>, since char{} * int{} has type int, according to normal C++ conversion rules.

Similar thing happens with unary operations, the expression +fp_t<A, EXP_A>{} has type fp_t<decltype(+A{}), EXP_A>.

The result of a virtual shift has underlying type unchanged, and just the exponent is modified. The expression fp_t<A, EXP_A>{} >> int_<SA> has type fp_t<A, EXP_A + SA>.

All the unary operations preserve the exponent, and in the case of binary operations, the exponent of the result depends on the operation being performed.

For addition, subtraction, modulus and the bitwise operators, the result has exponent equal to the greatest of the exponents of the operands, and the operation is carried out as if the operand with the smallest exponent was cast to that same exponent. For example, the result of the expression fp_t<int, 4>(1) + fp_t<int, 8>(2) has type fp_t<int, 8>

For multiplication and division, the exponent of the result is equal to the sum and subtraction of the exponents of the operands respectively. This implies the expression fp_t<int, 4>{} * fp_t<int, 8>{} has type fp_t<int, 12>, and with division instead, the resulting type would be fp_t<int, -4>

When using the relational operators between fp_t numbers with different exponents, the operand with the greatest exponent is converted so it has the same exponent as the other one, and they are then compared. This avoids a more expensive operation, and means that only least significant bits are discarded.

Requirements on the underlying type

At a minimum, the underlying type must be default constructible, copy constructible and support left and right shift by a std::integral_constant. The other operators are optional, and if they are not supported, the composed type will not support them. For example, if there is no modulus operator implemented for T, then fp_t<T> will not support modulus either.

License

ISC

Copyright (c) 2018, Matheus Izvekov mizvekov@gmail.com

All rights reserved.