Getting Started

This page goes from git clone to a running program in under five minutes. For background on why fixed-point math works the way it does, start with the Fixed-Point Primer and come back.

Install

FR_Math is a handful of C and C++ files. There is no package manager integration and no install step. Either:

There are no external dependencies beyond <stdint.h>, which every C99-or-newer toolchain ships.

git clone https://github.com/deftio/fr_math.git
cd fr_math
./scripts/build.sh

build.sh wipes build/, rebuilds the library, examples, and tests, and runs the full test suite. On success the output shows 42 tests passing across six test binaries.

A first program

The example below converts a decimal constant to fixed-point, multiplies it, and prints the result — all in a handful of lines. It uses a desktop printf for output; the next section shows the embedded equivalent.

Save this as hello_fr.c:

#include <stdio.h>
#include "FR_defs.h"
#include "FR_math.h"

int main(void)
{
    /* We want to represent 3.14159 in a signed 32-bit integer
       with 16 fractional bits (s15.16 format). */
    const int radix = 16;

    /* I2FR(integer, radix) converts a whole-number int into its
       fixed-point bit pattern at the requested radix. */
    s32 pi_int  = I2FR(3, radix);                   /* = 3 << 16 */
    s32 pi_frac = (s32)(0.14159 * (1 << radix));   /* build-time float */

    s32 pi_fixed = pi_int + pi_frac;
    printf("pi as s15.16 = 0x%08x = %d.%06d\n",
           pi_fixed,
           FR2I(pi_fixed, radix),                /* integer part */
           FR2I((pi_fixed & ((1 << radix) - 1)) * 1000000, radix));

    /* Multiply pi by 2 at the same radix using FR_FixMuls. */
    s32 two_pi = FR_FixMuls(pi_fixed, I2FR(2, radix));
    printf("2*pi ~= %d.%06d\n",
           FR2I(two_pi, radix),
           FR2I((two_pi & ((1 << radix) - 1)) * 1000000, radix));

    return 0;
}

Compile and run:

cc -Isrc hello_fr.c src/FR_math.c -lm -o hello_fr
./hello_fr

Expected output:

pi as s15.16 = 0x0003243f = 3.141586
2*pi ~= 6.283172

The last decimal digit wobbles because we only kept 16 bits of fractional precision — that’s roughly 5 significant decimal digits, which matches what we see. This is the core trade of fixed-point: the precision is chosen at build time, and the application lives with it.

The same program for embedded targets

The example above uses printf and a build-time float literal to get started quickly on a desktop. On an embedded target neither is usually available — there is no <stdio.h> and often no floating-point unit. FR_Math ships three callback-based print functions that replace printf for numeric output:

FunctionOutput
FR_printNumF(f, n, radix, pad, prec) Fixed-point value as decimal with a dot, e.g. "3.14158"
FR_printNumD(f, n, pad) Plain signed integer, e.g. "-42"
FR_printNumH(f, n, showPrefix) Hexadecimal, e.g. "0x0003243f"

Each function takes a int (*f)(char) callback and calls it once per output character. Point that callback at a UART transmit register, an LCD driver, a ring buffer — whatever the platform provides.

Save this as hello_embedded.c:

/* hello_embedded.c — FR_Math on bare metal, no printf, no float. */
#include "FR_defs.h"
#include "FR_math.h"

/* ---- Platform glue ------------------------------------------------
 * Replace uart_putc() with your board's serial-TX function.
 * The FR_printNum family calls f(char) once per character.
 */
static int uart_putc(char c)
{
    /* e.g. STM32:  HAL_UART_Transmit(&huart1, (uint8_t *)&c, 1, 10); */
    /* e.g. AVR:    while (!(UCSR0A & (1 << UDRE0))); UDR0 = c;       */
    (void)c;
    return 0;
}

/* Helper: emit a C string through the same callback. */
static void emit(const char *s) { while (*s) uart_putc(*s++); }

int main(void)
{
    const int radix = 16;

    /* ---- Compile-time constant: FR_NUM(integer, frac, digits, radix) ----
     *
     * FR_NUM builds a fixed-point value from its decimal parts:
     *   arg 1 (i): integer part          — here 3
     *   arg 2 (f): fractional digits     — here 14159  (the digits after ".")
     *   arg 3 (d): how many digits in f  — here 5      (1-4-1-5-9 = 5 digits)
     *   arg 4 (r): radix (frac bits)     — here 16
     *
     * Result: (3 << 16) + (14159 << 16) / 100000 = 205887 = 0x0003243F
     *
     * Because every argument is a literal, the compiler folds the entire
     * expression to a single constant — zero runtime cost, no float.
     * Use FR_NUM for any value you know at compile time.              */
    s32 pi     = FR_NUM(3, 14159, 5, radix);   /* 3.14159  s15.16  */
    s32 two    = I2FR(2, radix);                /* 2.0      s15.16  */
    s32 two_pi = FR_FixMuls(pi, two);            /* 2 * pi   s15.16  */

    /* ---- Runtime string parser: FR_numstr(string, radix) ----
     *
     * FR_numstr is the runtime counterpart of FR_NUM.  It parses a
     * null-terminated decimal string (like "3.14159") and returns the
     * same fixed-point value that FR_NUM would produce.
     *
     * Use it when the number is not known at compile time — for
     * example, reading from a serial port, a config file, or user
     * input on an LCD menu:
     *
     *   s32 gain = FR_numstr(uart_line_buf, 16);   // e.g. "0.05"
     *   s32 freq = FR_numstr(cfg_lookup("freq"), 8);
     *
     * It handles signs ("-3.5"), leading whitespace ("  3.14"),
     * leading-zero fractions ("0.05"), and up to 9 decimal digits.
     * No malloc, no strtod, no libm — safe for bare-metal use.
     *
     * FR_numstr is the inverse of FR_printNumF: one converts a
     * fixed-point number to a string, the other converts it back.    */

    /* FR_printNumF(putc, value, radix, min_width, frac_digits)
     * Prints a string like "3.14158" through the callback.           */
    FR_printNumF(uart_putc, pi,     radix, 0, 5);
    emit(" * 2 = ");
    FR_printNumF(uart_putc, two_pi, radix, 0, 5);
    emit("\r\n");

    /* Trig: cos(45 deg) printed as fixed-point and hex. */
    s32 cos45 = FR_CosI(45);                         /* s15.16        */
    emit("cos(45) = ");
    FR_printNumF(uart_putc, cos45, 16, 0, 5);         /* decimal       */
    emit(" (");
    FR_printNumH(uart_putc, cos45, 1);                /* hex           */
    emit(")\r\n");

    /* Plain integer output with FR_printNumD. */
    emit("raw = ");
    FR_printNumH(uart_putc, (int)pi, 1);
    emit(" radix=");
    FR_printNumD(uart_putc, radix, 0);
    emit("\r\n");

    while (1) {}   /* bare-metal main never returns */
}

Compile for a Cortex-M4 (or any bare-metal C99 target):

arm-none-eabi-gcc -Isrc -mcpu=cortex-m4 -mthumb -Os \
    hello_embedded.c src/FR_math.c --specs=nosys.specs -o hello_embedded.elf

Expected serial output (32-bit int platform):

3.14158 * 2 = 6.28317
cos(45) = 0.70709 (0x00005a82)
raw = 0x0003243f radix=16

Key differences from the desktop version:

static char buf[64];
static int  pos = 0;

static int buf_putc(char c) { buf[pos++] = c; return 0; }

Then pass buf_putc in place of uart_putc. After the call, buf[0..pos-1] holds the formatted string.

Using the 2D transform module

#include <stdio.h>
#include "FR_math.h"
#include "FR_math_2D.h"

int main(void)
{
    FR_Matrix2D_CPT M;
    M.ID();                 /* identity at the default radix */
    M.setrotate(45);        /* rotate 45 degrees */
    M.XlateI(100, 50);

    s32 px = I2FR(10, 16), py = I2FR(0, 16);
    s32 rx, ry;
    M.XFormPtI(px, py, &rx, &ry);  /* result ~(107, 57) */

    printf("x=%d y=%d\n",
           FR2I(rx, 16),
           FR2I(ry, 16));
    return 0;
}

See Examples for audio, wave, envelope, and log/exp walkthroughs.

Running the test suite

make test           # build + run every test suite
make coverage       # coverage report (requires gcov)

As of v2.0.1, FR_Math ships with 42 passing tests and 99% line coverage across the library sources.

Next steps