A small, fixed-point math library relying on no external libraries, and designed to work with various compilers.
Just include the core header and start coding!
#include <DMRC/FMA/Core>
It does not depend on external libraries such as STL. The only external code used is Google Test, for unit-test purposes.
The goal of FMA is to provide an elementary set of math operations for fixed-point math, and a number of container types with behavior that suits your use case.
FMA uses Q notation as defined by Texas Instruments. Learn more about Q notation here
Traditional Q notation includes both numerical and fractional bits (), but it's mroe common to omit the number of numerical bits. That's what FMA does (i.e. ). For example, DMRC::FMA::Simple<10>
AKA sQ10_t
is a fixed-point type with 10 fractional bits, since it is . Most containers default to the signed 32-bit int
type to store raw data, meaning is generally shorthand for . In FMA, that's exactly what it is (on platforms where an int
is 32 bits).
-
DMRC::FMA::Simple
- A simple fixed-point type that uses a consistent container size on any given platform, and no frills. Basic operations maintain$Q$ value.
DMRC::FMA::Flexible
- A container with user-configured containers, anything from auint8_t
to anint64_t
.DMRC::FMA::Adaptable
- A container with user-configured containers, entirely templatized math operations, and providing implementations of those operations for NEON Intrinsics and AVX512 container typesDMRC::FMA::Static
- A container providing compile-time values and calculations
FMA inlines nearly its entire library, so most of the structures and function calls compile away back down the raw multiplying and bit-shifting. That way, you get the compile-time safety checks of explicit types, and convenience features offered in FMA, without the overhead. FMA can be used in algorithm code—and is designed to be.
FMA is designed to be dropped into code bases that use raw fixed-point math. To prevent unintended behavior, no primitives can be implicitly converted to any FMA type, or vice versa, and no FMA types implicitly convert to other FMA types. This may not be the most convenient, but it prevents math bugs, even when integrating FMA into algorithm code.
Container types in FMA implement operator overloads to act just like their underlying type, but without the risk of mis-using it, or losing its Q-value due to bad symbol naming. In addition, they have several convenience features, such as:
- Rounding, floor, and ceiling
- Promotion/demotion (function calls, static casts, and bit-shifting styles)
- Conversion to/from any primitive numerical type
- Type traits to access container size, data size,
$m$ and$n$ values, etc.
Section headers link to the source code. See Unit Tests for many more examples of elementary usage.
#include <iostream>
#include <DMRC/FMA/Core>
#include <DMRC/FMA/Typedefs>
using namespace std;
using namespace DMRC::FMA::Typedefs;
int main(int argc, char** argv) {
// Two ways to initialize. Implicit assignment from primitives is not allowed.
sQ15_t x(12);
sQ15_t y = static_cast<sQ15_t>(3);
// Basic arithmetic
sQ15_t z1 = x + y;
sQ15_t z2 = x - y;
sQ15_t z3 = x * y; // Multiplication maintains Q value
sQ15_t z4 = x / y; // Division maintains Q value
cout << "x + y = " << z1.as<double>() << endl;
cout << "x - y = " << z2.as<double>() << endl;
cout << "x * y = " << z3.as<double>() << endl;
cout << "x / y = " << z4.as<double>() << endl;
// Two basic conversion techniques: casting and bit shifting
sQ12_t a = static_cast<sQ12_t>(x);
sQ12_t b = y >> fpshift(3); // operator>> is demotion: 15-3=12
sQ17_t c = (a + b) << fpshift(5); // operator<< is promotion: 12+5=17
cout << "a + b = " << c.as<double>() << endl;
}
Produces the following output:
x + y = 15
x - y = 9
x * y = 36
x / y = 4
a + b = 15
Install recommended extensions, and configure/build using your toolchain of choice.
rm -rf ./build
mkdir -p ./build
cmake -B ./build
cmake --build ./build -- -j8
After building...
cd build
ctest