← Back to AILP Home

Fixed Point — fix256

Overview

fix256 is a Q128.128 deterministic fixed-point type: 128-bit integer part + 128-bit fractional part = 256 bits total. It provides bit-exact identical results across all platforms (x86-64, ARM64, RISC-V, CUDA) by avoiding floating-point drift. This is critical for systems like robotic control and consciousness substrates where non-determinism can lead to cascading failures.

Specifications

Property Value
Total size 256 bits (32 bytes)
Integer part 128 bits (signed, two's complement)
Fractional part 128 bits
Precision 2^-128 ≈ 2.9×10^-39
Range ±2^127 ≈ ±1.7×10^38
Storage 4 × 64-bit limbs
MSB/Sign limb[3] bit 63
Deterministic Yes — zero drift, bit-exact across platforms
ERR Sentinel 0x8000000000000000 in limb[3], zeros elsewhere

Internal Representation

fix256 is stored internally as a 256-bit two's complement integer, representing the value multiplied by 2^128. The 4 64-bit limbs are ordered little-endian: - limb[0]: Fractional bits 0-63 - limb[1]: Fractional bits 64-127 - limb[2]: Integer bits 0-63 - limb[3]: Integer bits 64-127 (includes sign bit)

Declaration

You can declare fix256 variables using literal syntax or via explicit conversion functions:

// Using literal suffix
fix256:pi = 3.1415926535fix256;
fix256:negative = -10.5fix256;

// From integers
fix256:a = fix256_from_int(10);
fix256:b = fix256_from_int(-42);

// From floats (note: conversion from float imports float non-determinism at the boundary)
fix256:c = fix256_from_float(3.0);

Arithmetic Operations

All arithmetic operations are fully deterministic and handle overflow, underflow, and errors safely.

fix256:sum = a + b;
fix256:diff = a - b;
fix256:prod = a * b;
fix256:quot = a / b;
fix256:rem = a % b;

Comparison Operators

fix256 supports standard signed comparison operations. Note that comparisons involving the ERR sentinel follow sticky error semantics (see Error Handling below).

if (a == b) { /* Equality */ }
if (a != b) { /* Inequality */ }
if (a < b)  { /* Less than */ }
if (a <= b) { /* Less than or equal */ }
if (a > b)  { /* Greater than */ }
if (a >= b) { /* Greater than or equal */ }

Bitwise Operations

Bitwise operations operate on the underlying 256-bit representation.

// Shifts operate on the entire 256-bit value
fix256:shifted_left = a << 1;   // Equivalent to multiplying by 2
fix256:shifted_right = a >> 1;  // Arithmetic shift right (preserves sign)

Math Intrinsics

fix256:f = fix256_floor(a);    // Round towards negative infinity
fix256:t = fix256_trunc(a);    // Truncate fractional part (round towards zero)

Error Handling & The ERR Sentinel

fix256 employs a sticky ERR sentinel to indicate invalid operations without crashing the program (Undefined Behavior).

What is ERR? The ERR sentinel is the minimum signed 256-bit value: limb[3] = 0x8000000000000000, with all other limbs set to 0. It cannot be produced by valid arithmetic.

Triggers for ERR: - Division by zero (a / 0ERR) - Arithmetic overflow (fix256_MAX() + 1ERR) - Arithmetic underflow (subtracting beyond minimum valid value → ERR)

Sticky Propagation: If any operand is ERR, the result of the operation is ERR. This ensures errors are not silently absorbed.

fix256:bad = a / 0;          // bad is ERR
fix256:worse = bad + 10;     // worse is ERR

Checking for ERR: Since ERR represents an invalid state, it follows TBB identity rules: - ERR == ERR evaluates to false - ERR != ERR evaluates to true - ERR < x evaluates to true for any valid x (as it is the minimum representable value)

To safely check if a value is valid, use the ok() function:

if (ok(bad) == 0) {
    // bad is ERR
}

Constants

fix256:max_val = fix256_MAX();      // Largest positive value
fix256:err_val = fix256_ERR();      // The ERR sentinel
fix256:eps     = fix256_EPSILON();  // Smallest positive non-zero value (2^-128)

Type Conversions

Converting back to primitive types strips the fractional component:

// Truncates fractional part
int64:val = fix256_to_int(sum);

// Using @cast
int32:val32 = @cast<int32>(sum);

Dimensional Analysis Variants

fix256 supports dimensional type annotations for physics/engineering, allowing the compiler to enforce unit correctness at compile-time.

fix256<Joules>:energy = 100.0fix256;
fix256<Meters>:distance = 50.0fix256;

For full details on dimensional types and operations, see the Dimensional Analysis User Guide.

Performance

While fix256 provides exact determinism and massive precision, it comes with a performance overhead compared to hardware floating-point operations: - CPU: Operations are emulated using 64-bit integer limbs. Addition/subtraction are fast (a few instructions), but multiplication and division require multi-limb algorithms (Knuth's Algorithm D). - GPU: Full PTX implementations are provided for CUDA, enabling massively parallel execution.

If absolute determinism is not required and performance is the sole bottleneck, consider IEEE 754 floating point types (flt32, flt64).

Why Not Floats?

Real-World Example

// Deterministic physics update loop
func:update_physics = void(fix256:dt) {
    fix256:velocity = 10.5fix256;
    fix256:position = 0.0fix256;

    for (int32:i = 0; i < 1000; i++) {
        fix256:delta = (velocity * dt) ? fix256_ERR();
        if (ok(delta) == 0) {
            !!! ERR_PHYSICS_CORRUPTION;
        }
        position = (position + delta) ? fix256_ERR();
    }
}

Related