GF16: основной формат
Вы узнаете
GoldenFloat на 16 битов, который семейство отмечает основным: 1 + 6 + 9, смещение 31 и его особые коды.
В GF16 16 битов: 1 знак, 6 порядка, 9 мантиссы, смещение 31. Код порядка 63, все единицы, оставлен для бесконечности и NaN, как в IEEE 754. Спека даёт имя каждому особому коду: 0x0000 и 0x8000 для двух нулей, 0x7E00 и 0xFE00 для двух бесконечностей, 0xFE01 для NaN. Мантисса из 9 битов делится на 512.
Попробуйте
Найдите SIGN_MASK, EXP_MASK и MANT_MASK в gf16.t27 и проверьте, что вместе они покрывают все 16 битов. Затем найдите код NaN и скажите, какое поле отличает его от бесконечности.

GF16 special codes, read from gf16.t27. Lesson 13 of the GoldenFloat course.
specs/numeric/gf16.t27
// SPDX-License-Identifier: Apache-2.0
; gf16.t27 — GoldenFloat16 Encode/Decode
; GF16: 16-bit floating point with 1 sign + 6 exponent + 9 mantissa
; Bit layout: [S(1) E(6) M(9)] = [15:15][14:9][8:0]
; φ² + 1/φ² = 3 | TRINITY
module triformat-gf16;
// ============================================================================
// Constants
// ============================================================================
pub const SIGN_SHIFT : u8 = 15;
pub const EXP_SHIFT : u8 = 9;
pub const MANT_SHIFT : u8 = 0;
pub const SIGN_MASK : u16 = 0x8000; // 1 << 15
pub const EXP_MASK : u16 = 0x7E00; // 0b111111 << 9
pub const MANT_MASK : u16 = 0x01FF; // 0b111111111
pub const EXP_MAX : u8 = 0x3F; // 63 (all ones in 6 bits)
pub const EXP_MIN : u8 = 0x00;
pub const BIAS : i8 = 31; // Exponent bias for GF16
pub const SPECIAL_EXP : u8 = 0x3F; // All ones = special (Inf/NaN)
pub const MANT_DIVISOR : u16 = 512; // 2^9
pub const MANT_DIVISOR_SHIFT : u8 = 9; // log2(512)
pub const PHI_BIAS : u16 = 60; // Phi-optimized rounding bias
// GF16 special values
pub const GF16_ZERO_POS : u16 = 0x0000;
pub const GF16_ZERO_NEG : u16 = 0x8000;
pub const GF16_INF_POS : u16 = 0x7E00;
pub const GF16_INF_NEG : u16 = 0xFE00;
pub const GF16_NAN : u16 = 0xFE01; // Sign + all exp + mantissa != 0
// ============================================================================
// Types
// ============================================================================
pub const GF16 = u16;
// ============================================================================
// Lookup Tables
// ============================================================================
// Powers of 2 for exponents 0-31
pub const pow2_table : [32]u16 = [32]u16{
0x3C00, 0x3D00, 0x3D80, 0x3E00, 0x3E40, 0x3E80, 0x3EC0, 0x3F00,
0x3F40, 0x3F80, 0x3FC0, 0x3FE0, 0x3FF0, 0x4000, 0x4040, 0x4080,
0x40C0, 0x4100, 0x4140, 0x4180, 0x41C0, 0x4200, 0x4240, 0x4280,
0x42C0, 0x4300, 0x4340, 0x4380, 0x43C0, 0x4400, 0x4440, 0x4480,
};
// ============================================================================
// Functions
// ============================================================================
// gf16_extract_sign(gf16: GF16) → i8
// Extract sign bit (bit 15)
// Returns: 0 for positive, -1 for negative
pub fn gf16_extract_sign(gf16: GF16) i8 {
const bit = (gf16 >> SIGN_SHIFT) & 1;
return if (bit != 0) -1 else 0;
}
// gf16_extract_exponent(gf16: GF16) → i8
// Extract exponent bits (bits 14-9)
// Returns: 0-63
pub fn gf16_extract_exponent(gf16: GF16) i8 {
return @as(i8, @intCast((gf16 >> EXP_SHIFT) & EXP_MASK));
}
// gf16_extract_mantissa(gf16: GF16) → i16
// Extract mantissa bits (bits 8-0)
// Returns: 0-511
pub fn gf16_extract_mantissa(gf16: GF16) i16 {
return @as(i16, gf16 & MANT_MASK);
}
// gf16_from_components(sign: i8, exp: i8, mant: i16) → GF16
// Assemble GF16 from sign, exponent, mantissa
pub fn gf16_from_components(sign: i8, exp: i8, mant: i16) GF16 {
const sign_bit = if (sign < 0) 1 else 0;
return (@as(GF16, @intCast(sign_bit)) << SIGN_SHIFT) |
(@as(GF16, @intCast(exp)) << EXP_SHIFT) |
@as(GF16, @intCast(mant));
}
// gf16_is_zero(gf16: GF16) → bool
// Check if GF16 is zero (positive or negative)
pub fn gf16_is_zero(gf16: GF16) bool {
return gf16 == GF16_ZERO_POS or gf16 == GF16_ZERO_NEG;
}
// gf16_is_special(gf16: GF16) → bool
// Check if GF16 is Inf or NaN (exp == 63)
pub fn gf16_is_special(gf16: GF16) bool {
return gf16_extract_exponent(gf16) == EXP_MAX;
}
// gf16_encode_f32(f32: f32) → GF16
// Encode IEEE 754 single precision to GF16
// Round-to-nearest, ties to even
// Range: 2^-31 to 2^32 (normal), subnormals flushed to zero
pub fn gf16_encode_f32(value: f32) GF16 {
// Handle zero
if (value == 0.0) {
return if (std.math.signbit(value)) GF16_ZERO_NEG else GF16_ZERO_POS;
}
// Extract sign
const sign = if (value < 0.0) -1 else 0;
const abs_value = if (value < 0.0) -value else value;
// Get f32 components
const f32_bits: u32 = @bitCast(abs_value);
var f32_exp: i8 = @intCast((f32_bits >> 23) & 0xFF) - 127;
var f32_mant: u32 = f32_bits & 0x7FFFFF;
// Convert exp from f32 bias (127) to GF16 bias (31)
// gf16_exp = f32_exp + 31 - 127 = f32_exp - 96
var gf16_exp: u8 = @as(u8, @intCast(f32_exp - 96));
// Clamp exponent
if (gf16_exp < 0) {
gf16_exp = 0; // Underflow to zero
} else if (gf16_exp > EXP_MAX) {
gf16_exp = EXP_MAX; // Overflow to Inf
}
// Extract mantissa and scale to 9 bits
// f32 mantissa is 23 bits, GF16 needs 9 bits
// Shift right by 14 bits (23 - 9 = 14)
var mant = @as(u16, @intCast(f32_mant >> 14));
// Round-to-nearest
const discarded = f32_mant & 0x3FFF;
if ((discarded & 0x2000) != 0) {
mant += 1;
if (mant > MANT_MASK) {
mant = 0;
if (gf16_exp < EXP_MAX) {
gf16_exp += 1;
}
}
}
return gf16_from_components(sign, gf16_exp, mant);
}
// gf16_decode_to_f32(gf16: GF16) → f32
// Decode GF16 to IEEE 754 single precision
pub fn gf16_decode_to_f32(gf16: GF16) f32 {
// Handle zero
if (gf16_is_zero(gf16)) {
const sign = gf16_extract_sign(gf16);
return if (sign < 0) -0.0 else 0.0;
}
// Handle special values (Inf/NaN)
if (gf16_is_special(gf16)) {
const mant = gf16_extract_mantissa(gf16);
const sign = gf16_extract_sign(gf16);
if (mant == 0) {
// Infinity
return if (sign < 0) -std.math.inf(f32) else std.math.inf(f32);
} else {
// NaN
return std.math.nan(f32);
}
}
// Normal number: value = (-1)^s * (1 + m/2^9) * 2^(e - 31)
const sign = gf16_extract_sign(gf16);
const exp = gf16_extract_exponent(gf16);
const mant = gf16_extract_mantissa(gf16);
const sign_mult = if (sign < 0) -1.0 else 1.0;
const mant_mult = 1.0 + @as(f32, @floatFromInt(mant)) / 512.0;
const exp_mult = @as(f32, @exp2(f32, @floatFromInt(exp - BIAS)));
return sign_mult * mant_mult * exp_mult;
}
// gf16_round_phi(value: f32) → GF16
// Phi-optimized rounding for GF16
// Uses golden ratio bias for rounding decisions instead of standard round-to-nearest
// Bias = (1/φ - 0.5) * scale, where 1/φ ≈ 0.618
// This improves numerical stability for sacred physics calculations
pub fn gf16_round_phi(value: f32) GF16 {
// Handle zero
if (value == 0.0) {
return if (std.math.signbit(value)) GF16_ZERO_NEG else GF16_ZERO_POS;
}
// Extract sign
const sign = if (value < 0.0) -1 else 0;
const abs_value = if (value < 0.0) -value else value;
// Get f32 components
const f32_bits: u32 = @bitCast(abs_value);
var f32_exp: i8 = @intCast((f32_bits >> 23) & 0xFF) - 127;
var f32_mant: u32 = f32_bits & 0x7FFFFF;
// Convert exp from f32 bias (127) to GF16 bias (31)
var gf16_exp: u8 = @as(u8, @intCast(f32_exp - 96));
// Clamp exponent
if (gf16_exp < 0) {
gf16_exp = 0;
} else if (gf16_exp > EXP_MAX) {
gf16_exp = EXP_MAX;
}
// Add implied 1 for normalization
const normalized_mant: u32 = f32_mant | 0x00800000;
// Scale to 9 bits with phi bias
var mant = @as(u16, @intCast((normalized_mant >> 15) + PHI_BIAS));
// Check for overflow and adjust
if (mant > MANT_MASK) {
mant = 0;
if (gf16_exp < EXP_MAX) {
gf16_exp += 1;
} else {
gf16_exp = EXP_MAX; // Overflow to Inf
}
}
return gf16_from_components(sign, gf16_exp, mant);
}
// gf16_is_inf(gf16: GF16) → bool
// Check if GF16 represents infinity
pub fn gf16_is_inf(gf16: GF16) bool {
const exp = gf16_extract_exponent(gf16);
const mant = gf16_extract_mantissa(gf16);
return (exp == EXP_MAX) and (mant == 0);
}
// gf16_is_nan(gf16: GF16) → bool
// Check if GF16 represents NaN (Not a Number)
pub fn gf16_is_nan(gf16: GF16) bool {
const exp = gf16_extract_exponent(gf16);
const mant = gf16_extract_mantissa(gf16);
return (exp == EXP_MAX) and (mant != 0);
}
// gf16_is_negative(gf16: GF16) → bool
// Check if GF16 is negative (excluding negative zero)
pub fn gf16_is_negative(gf16: GF16) bool {
const sign = gf16_extract_sign(gf16);
return (sign < 0) and !gf16_is_zero(gf16);
}
// gf16_is_positive(gf16: GF16) → bool
// Check if GF16 is positive (excluding positive zero)
pub fn gf16_is_positive(gf16: GF16) bool {
const sign = gf16_extract_sign(gf16);
return (sign >= 0) and !gf16_is_zero(gf16);
}
// gf16_negate(gf16: GF16) → GF16
// Negate a GF16 value (flip sign bit)
pub fn gf16_negate(gf16: GF16) GF16 {
return gf16 ^ SIGN_MASK;
}
// gf16_abs(gf16: GF16) → GF16
// Absolute value of GF16 (clear sign bit)
pub fn gf16_abs(gf16: GF16) GF16 {
return gf16 & ~SIGN_MASK;
}
// gf16_copy_sign(gf16: GF16, sign_source: GF16) → GF16
// Copy sign from sign_source to gf16 value
pub fn gf16_copy_sign(gf16: GF16, sign_source: GF16) GF16 {
const sign_mask = sign_source & SIGN_MASK;
const value_mask = gf16 & ~SIGN_MASK;
return value_mask | sign_mask;
}
// gf16_max(a: GF16, b: GF16) → GF16
// Return the greater of two GF16 values
pub fn gf16_max(a: GF16, b: GF16) GF16 {
if (gf16_is_nan(a)) return b;
if (gf16_is_nan(b)) return a;
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
if (a_val >= b_val) return a else return b;
}
// gf16_min(a: GF16, b: GF16) → GF16
// Return the smaller of two GF16 values
pub fn gf16_min(a: GF16, b: GF16) GF16 {
if (gf16_is_nan(a)) return b;
if (gf16_is_nan(b)) return a;
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
if (a_val <= b_val) return a else return b;
}
// gf16_add(a: GF16, b: GF16) → GF16
// Add two GF16 values (decode, add, re-encode)
// Returns NaN if either operand is NaN, Inf if overflow
pub fn gf16_add(a: GF16, b: GF16) GF16 {
if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN;
if (gf16_is_inf(a) and gf16_is_inf(b)) {
// Inf + Inf = NaN (if same sign)
// Inf + (-Inf) = NaN
const a_sign = gf16_extract_sign(a);
const b_sign = gf16_extract_sign(b);
return if (a_sign == b_sign) a else GF16_NAN;
}
if (gf16_is_inf(a)) return a;
if (gf16_is_inf(b)) return b;
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
const result = a_val + b_val;
return gf16_encode_f32(result);
}
// gf16_sub(a: GF16, b: GF16) → GF16
// Subtract two GF16 values (decode, subtract, re-encode)
// Returns NaN if either operand is NaN, Inf if overflow
pub fn gf16_sub(a: GF16, b: GF16) GF16 {
if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN;
if (gf16_is_inf(a) and gf16_is_inf(b)) {
// Inf - Inf = NaN
return GF16_NAN;
}
if (gf16_is_inf(a)) return a;
if (gf16_is_inf(b)) {
// -Inf + something = Inf (with sign flip)
return gf16_negate(b);
}
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
const result = a_val - b_val;
return gf16_encode_f32(result);
}
// gf16_mul(a: GF16, b: GF16) → GF16
// Multiply two GF16 values (decode, multiply, re-encode)
// Returns NaN if either operand is NaN
pub fn gf16_mul(a: GF16, b: GF16) GF16 {
if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN;
if (gf16_is_zero(a) or gf16_is_zero(b)) {
// 0 * x = 0, with sign handling
const a_sign = gf16_extract_sign(a);
const b_sign = gf16_extract_sign(b);
const result_sign = a_sign ^ b_sign;
return if (result_sign != 0) GF16_ZERO_NEG else GF16_ZERO_POS;
}
if (gf16_is_inf(a) or gf16_is_inf(b)) {
// Inf * non-zero = Inf, with sign handling
const a_sign = gf16_extract_sign(a);
const b_sign = gf16_extract_sign(b);
const result_sign = a_sign ^ b_sign;
return if (result_sign != 0) GF16_INF_NEG else GF16_INF_POS;
}
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
const result = a_val * b_val;
return gf16_encode_f32(result);
}
// gf16_div(a: GF16, b: GF16) → GF16
// Divide two GF16 values (decode, divide, re-encode)
// Returns NaN if division by zero or either operand is NaN
// Returns Inf if numerator is Inf and denominator is finite non-zero
pub fn gf16_div(a: GF16, b: GF16) GF16 {
if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN;
if (gf16_is_zero(b)) {
// Division by zero = Inf with sign of a
const a_sign = gf16_extract_sign(a);
return if (a_sign != 0) GF16_INF_NEG else GF16_INF_POS;
}
if (gf16_is_inf(a)) {
// Inf / finite = Inf, with sign handling
const a_sign = gf16_extract_sign(a);
const b_sign = gf16_extract_sign(b);
const result_sign = a_sign ^ b_sign;
return if (result_sign != 0) GF16_INF_NEG else GF16_INF_POS;
}
if (gf16_is_inf(b)) {
// Finite / Inf = 0, with sign handling
const a_sign = gf16_extract_sign(a);
const b_sign = gf16_extract_sign(b);
const result_sign = a_sign ^ b_sign;
return if (result_sign != 0) GF16_ZERO_NEG else GF16_ZERO_POS;
}
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
const result = a_val / b_val;
return gf16_encode_f32(result);
}
// gf16_fma(a: GF16, b: GF16, c: GF16) → GF16
// Fused multiply-add: a * b + c with single rounding
// More accurate than separate mul and add
pub fn gf16_fma(a: GF16, b: GF16, c: GF16) GF16 {
if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) return GF16_NAN;
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
const c_val = gf16_decode_to_f32(c);
// Handle special cases
if (gf16_is_zero(a) or gf16_is_zero(b)) {
return gf16_add(c, gf16_encode_f32(0.0));
}
// Compute a * b + c
const product = a_val * b_val;
const result = product + c_val;
return gf16_encode_f32(result);
}
// gf16_sqrt(a: GF16) → GF16
// Square root of GF16 value
// Returns NaN for negative values, Inf for infinity
pub fn gf16_sqrt(a: GF16) GF16 {
if (gf16_is_nan(a)) return GF16_NAN;
if (gf16_is_inf(a) and !gf16_is_negative(a)) return a;
if (gf16_is_inf(a)) return GF16_NAN; // -Inf sqrt = NaN
if (gf16_is_zero(a)) return a;
if (gf16_is_negative(a)) return GF16_NAN;
const a_val = gf16_decode_to_f32(a);
const result = @sqrt(a_val);
return gf16_encode_f32(result);
}
// gf16_square(a: GF16) → GF16
// Square of GF16 value
// Uses gf16_mul internally
pub fn gf16_square(a: GF16) GF16 {
return gf16_mul(a, a);
}
// gf16_eq(a: GF16, b: GF16) → bool
// Equality comparison for GF16
// NaN values are never equal to anything (including themselves)
pub fn gf16_eq(a: GF16, b: GF16) bool {
if (gf16_is_nan(a) or gf16_is_nan(b)) return false;
// For zero values, treat +0 and -0 as equal
if (gf16_is_zero(a) and gf16_is_zero(b)) return true;
return a == b;
}
// gf16_ne(a: GF16, b: GF16) → bool
// Not-equal comparison for GF16
// NaN values are not equal to anything (including themselves)
pub fn gf16_ne(a: GF16, b: GF16) bool {
return !gf16_eq(a, b);
}
// gf16_lt(a: GF16, b: GF16) → bool
// Less-than comparison for GF16
// Returns false if either operand is NaN
pub fn gf16_lt(a: GF16, b: GF16) bool {
if (gf16_is_nan(a) or gf16_is_nan(b)) return false;
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
return a_val < b_val;
}
// gf16_le(a: GF16, b: GF16) → bool
// Less-than-or-equal comparison for GF16
// Returns false if either operand is NaN
pub fn gf16_le(a: GF16, b: GF16) bool {
if (gf16_is_nan(a) or gf16_is_nan(b)) return false;
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
return a_val <= b_val;
}
// gf16_gt(a: GF16, b: GF16) → bool
// Greater-than comparison for GF16
// Returns false if either operand is NaN
pub fn gf16_gt(a: GF16, b: GF16) bool {
if (gf16_is_nan(a) or gf16_is_nan(b)) return false;
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
return a_val > b_val;
}
// gf16_ge(a: GF16, b: GF16) → bool
// Greater-than-or-equal comparison for GF16
// Returns false if either operand is NaN
pub fn gf16_ge(a: GF16, b: GF16) bool {
if (gf16_is_nan(a) or gf16_is_nan(b)) return false;
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
return a_val >= b_val;
}
// gf16_floor(a: GF16) → GF16
// Round down to the nearest integer (toward -inf)
// Returns NaN for NaN input, unchanged for Inf/-Inf
pub fn gf16_floor(a: GF16) GF16 {
if (gf16_is_nan(a)) return GF16_NAN;
if (gf16_is_inf(a)) return a;
if (gf16_is_zero(a)) return a;
const a_val = gf16_decode_to_f32(a);
const result = @floor(a_val);
return gf16_encode_f32(result);
}
// gf16_ceil(a: GF16) → GF16
// Round up to the nearest integer (toward +inf)
// Returns NaN for NaN input, unchanged for Inf/-Inf
pub fn gf16_ceil(a: GF16) GF16 {
if (gf16_is_nan(a)) return GF16_NAN;
if (gf16_is_inf(a)) return a;
if (gf16_is_zero(a)) return a;
const a_val = gf16_decode_to_f32(a);
const result = @ceil(a_val);
return gf16_encode_f32(result);
}
// gf16_round(a: GF16) → GF16
// Round to nearest integer, ties to even (IEEE 754 roundTiesToEven)
// Returns NaN for NaN input, unchanged for Inf/-Inf
pub fn gf16_round(a: GF16) GF16 {
if (gf16_is_nan(a)) return GF16_NAN;
if (gf16_is_inf(a)) return a;
if (gf16_is_zero(a)) return a;
const a_val = gf16_decode_to_f32(a);
const result = @round(a_val);
return gf16_encode_f32(result);
}
// gf16_trunc(a: GF16) → GF16
// Round toward zero (truncate fractional part)
// Returns NaN for NaN input, unchanged for Inf/-Inf
pub fn gf16_trunc(a: GF16) GF16 {
if (gf16_is_nan(a)) return GF16_NAN;
if (gf16_is_inf(a)) return a;
if (gf16_is_zero(a)) return a;
const a_val = gf16_decode_to_f32(a);
const result = @trunc(a_val);
return gf16_encode_f32(result);
}
// gf16_fms(a: GF16, b: GF16, c: GF16) → GF16
// Fused multiply-subtract: a * b - c with single rounding
// More accurate than separate mul and sub
// Useful for neural network backpropagation
pub fn gf16_fms(a: GF16, b: GF16, c: GF16) GF16 {
if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) return GF16_NAN;
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
const c_val = gf16_decode_to_f32(c);
// Handle special cases
if (gf16_is_zero(a) or gf16_is_zero(b)) {
return gf16_sub(gf16_encode_f32(0.0), c);
}
// Compute a * b - c
const product = a_val * b_val;
const result = product - c_val;
return gf16_encode_f32(result);
}
// gf16_hypot(a: GF16, b: GF16) → GF16
// Compute sqrt(a^2 + b^2) without overflow/underflow
// Returns NaN if either operand is NaN, Inf if both are Inf
// Useful for distance calculations, neural network normalization
pub fn gf16_hypot(a: GF16, b: GF16) GF16 {
if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN;
if (gf16_is_inf(a) or gf16_is_inf(b)) return GF16_INF_POS;
if (gf16_is_zero(a) and gf16_is_zero(b)) return GF16_ZERO_POS;
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
// Standard algorithm to avoid overflow: scale by max(|a|, |b|)
const abs_a = @abs(a_val);
const abs_b = @abs(b_val);
const max_val = @max(abs_a, abs_b);
const min_val = @min(abs_a, abs_b);
if (max_val == 0.0) return GF16_ZERO_POS;
const ratio = min_val / max_val;
const result = max_val * @sqrt(1.0 + ratio * ratio);
return gf16_encode_f32(result);
}
// gf16_fmod(a: GF16, b: GF16) → GF16
// Compute remainder of a / b (IEEE 754 style)
// Result has same sign as dividend (a)
// Returns NaN if divisor is zero or either operand is NaN
pub fn gf16_fmod(a: GF16, b: GF16) GF16 {
if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN;
if (gf16_is_zero(b)) return GF16_NAN;
if (gf16_is_inf(a)) return GF16_NAN;
if (gf16_is_inf(b)) return a;
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
// Handle zero dividend
if (a_val == 0.0) return a;
const result = @mod(a_val, b_val);
return gf16_encode_f32(result);
}
// gf16_is_finite(gf16: GF16) → bool
// Check if GF16 value is finite (not NaN, not infinity)
pub fn gf16_is_finite(gf16: GF16) bool {
return !gf16_is_nan(gf16) and !gf16_is_inf(gf16);
}
// gf16_is_normal(gf16: GF16) → bool
// Check if GF16 value is a normal (normalized) number
// Normal numbers have exponent in range [1, EXP_MAX-1] and are not zero
pub fn gf16_is_normal(gf16: GF16) bool {
if (gf16_is_zero(gf16) or gf16_is_nan(gf16) or gf16_is_inf(gf16)) {
return false;
}
const exp = gf16_extract_exponent(gf16);
// GF16: exp = 0 is subnormal, exp = 31 is inf/nan, 1-30 is normal
return exp > 0 and exp < GF16_EXP_MAX;
}
// gf16_is_subnormal(gf16: GF16) → bool
// Check if GF16 value is subnormal (denormal)
// Subnormal numbers have exponent = 0 and mantissa != 0
pub fn gf16_is_subnormal(gf16: GF16) bool {
if (gf16_is_zero(gf16) or gf16_is_nan(gf16) or gf16_is_inf(gf16)) {
return false;
}
const exp = gf16_extract_exponent(gf16);
const mant = gf16_extract_mantissa(gf16);
// Subnormal: exp = 0 and mantissa != 0
return exp == 0 and mant != 0;
}
// gf16_signbit(gf16: GF16) → bool
// Check if the sign bit is set (value is negative or negative zero)
// Returns true for negative values and negative zero
pub fn gf16_signbit(gf16: GF16) bool {
return (gf16 & GF16_SIGN_MASK) != 0;
}
// gf16_sign(gf16: GF16) → i8
// Return the sign of the GF16 value: -1 for negative, 0 for zero, +1 for positive
// Returns 0 for NaN (IEEE 754 specifies sign of NaN is undefined)
pub fn gf16_sign(gf16: GF16) i8 {
if (gf16_is_nan(gf16)) {
return 0;
}
if (gf16_is_zero(gf16)) {
return 0;
}
if (gf16_signbit(gf16)) {
return -1;
} else {
return 1;
}
}
// gf16_clamp(x: GF16, min_val: GF16, max_val: GF16) → GF16
// Clamp x to the range [min_val, max_val]
// Returns min_val if x < min_val, max_val if x > max_val, otherwise x
pub fn gf16_clamp(x: GF16, min_val: GF16, max_val: GF16) GF16 {
if (gf16_is_nan(x) or gf16_is_nan(min_val) or gf16_is_nan(max_val)) {
return GF16_NAN;
}
// Decode for comparison
const x_decoded = gf16_decode_to_f32(x);
const min_decoded = gf16_decode_to_f32(min_val);
const max_decoded = gf16_decode_to_f32(max_val);
if (x_decoded < min_decoded) {
return min_val;
} else if (x_decoded > max_decoded) {
return max_val;
} else {
return x;
}
}
// gf16_lerp(a: GF16, b: GF16, t: GF16) → GF16
// Linear interpolation: a + t * (b - a)
// Returns a when t=0, b when t=1, and interpolates for other values
pub fn gf16_lerp(a: GF16, b: GF16, t: GF16) GF16 {
if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(t)) {
return GF16_NAN;
}
// Decode to f32 for computation
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
const t_val = gf16_decode_to_f32(t);
// Compute: a + t * (b - a)
const result = a_val + t_val * (b_val - a_val);
return gf16_encode_f32(result);
}
// gf16_fnma(a: GF16, b: GF16, c: GF16) → GF16
// Fused negative multiply-add: -(a * b) + c
// More accurate than computing gf16_sub(c, gf16_mul(a, b))
pub fn gf16_fnma(a: GF16, b: GF16, c: GF16) GF16 {
if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) {
return GF16_NAN;
}
// Handle infinity cases
if (gf16_is_inf(a) or gf16_is_inf(b)) {
if (gf16_is_inf(c)) {
return GF16_NAN;
}
// -(inf * b) + c = -inf (with appropriate sign)
if (gf16_is_inf(a) or gf16_is_inf(b)) {
const sign_a = gf16_signbit(a);
const sign_b = gf16_signbit(b);
const result_sign = (sign_a != sign_b); // XOR for negative result
return if (result_sign) GF16_INF_NEG else GF16_INF_POS;
}
}
if (gf16_is_inf(c)) {
return c;
}
// Handle zero cases
if (gf16_is_zero(a) or gf16_is_zero(b)) {
return c;
}
if (gf16_is_zero(c)) {
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
const neg_product = -(a_val * b_val);
return gf16_encode_f32(neg_product);
}
// Decode to f32 for computation
const a_val = gf16_decode_to_f32(a);
const b_val = gf16_decode_to_f32(b);
const c_val = gf16_decode_to_f32(c);
const result = -(a_val * b_val) + c_val;
return gf16_encode_f32(result);
}
// gf16_exp(x: GF16) → GF16
// Compute e^x (exponential function)
// Uses Taylor series approximation for small values
// Returns Inf for very large positive inputs, 0 for very large negative inputs
pub fn gf16_exp(x: GF16) GF16 {
if (gf16_is_nan(x)) return GF16_NAN;
if (gf16_is_inf(x)) {
if (gf16_is_negative(x)) return GF16_ZERO_POS;
return GF16_INF_POS;
}
const x_val = gf16_decode_to_f32(x);
// For large positive values, return Inf
if (x_val > 88.0) { // ln(MAX_FLOAT) for f32
return GF16_INF_POS;
}
// For large negative values, return 0
if (x_val < -88.0) {
return GF16_ZERO_POS;
}
// Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + x^4/4! + ...
// Use 5 terms for reasonable accuracy with GF16 precision
var result: f32 = 1.0;
var term: f32 = 1.0;
const num_terms: u32 = 5;
for (0..num_terms) |i| {
if (i > 0) {
term *= x_val / @as(f32, @floatFromInt(i));
result += term;
}
}
return gf16_encode_f32(result);
}
// gf16_log(x: GF16) → GF16
// Compute natural logarithm ln(x)
// Returns NaN for x <= 0, Inf for very large x
pub fn gf16_log(x: GF16) GF16 {
if (gf16_is_nan(x)) return GF16_NAN;
if (gf16_is_inf(x)) {
if (gf16_is_negative(x)) return GF16_NAN;
return GF16_INF_POS;
}
if (gf16_is_zero(x) or gf16_is_negative(x)) {
return GF16_NAN;
}
const x_val = gf16_decode_to_f32(x);
// For very large values, return Inf
if (x_val > 1.0e38) {
return GF16_INF_POS;
}
// Use natural log from standard library
const result = @log(x_val);
return gf16_encode_f32(result);
}
// gf16_log2(x: GF16) → GF16
// Compute base-2 logarithm log2(x)
pub fn gf16_log2(x: GF16) GF16 {
if (gf16_is_nan(x)) return GF16_NAN;
if (gf16_is_inf(x)) {
if (gf16_is_negative(x)) return GF16_NAN;
return GF16_INF_POS;
}
if (gf16_is_zero(x) or gf16_is_negative(x)) {
return GF16_NAN;
}
const x_val = gf16_decode_to_f32(x);
const result = @log2(x_val);
return gf16_encode_f32(result);
}
// gf16_log10(x: GF16) → GF16
// Compute base-10 logarithm log10(x)
pub fn gf16_log10(x: GF16) GF16 {
if (gf16_is_nan(x)) return GF16_NAN;
if (gf16_is_inf(x)) {
if (gf16_is_negative(x)) return GF16_NAN;
return GF16_INF_POS;
}
if (gf16_is_zero(x) or gf16_is_negative(x)) {
return GF16_NAN;
}
const x_val = gf16_decode_to_f32(x);
const result = @log10(x_val);
return gf16_encode_f32(result);
}
// gf16_pow(base: GF16, exponent: GF16) → GF16
// Compute base^exponent
// Handles various special cases: 0^0 = 1, 1^x = 1, x^0 = 1, etc.
pub fn gf16_pow(base: GF16, exponent: GF16) GF16 {
if (gf16_is_nan(base) or gf16_is_nan(exponent)) return GF16_NAN;
// 0^0 = 1 (by convention)
if (gf16_is_zero(base) and gf16_is_zero(exponent)) return gf16_encode_f32(1.0);
// 0^x = 0 for x > 0
if (gf16_is_zero(base) and gf16_is_positive(exponent)) return GF16_ZERO_POS;
// 0^x = Inf for x < 0 (division by zero)
if (gf16_is_zero(base) and gf16_is_negative(exponent)) return GF16_INF_POS;
// 1^x = 1 for any finite x
const base_val = gf16_decode_to_f32(base);
if (base_val == 1.0 and !gf16_is_inf(exponent)) return gf16_encode_f32(1.0);
// x^0 = 1 for any x != 0
if (gf16_is_zero(exponent)) {
if (gf16_is_zero(base)) return GF16_NAN;
return gf16_encode_f32(1.0);
}
// x^1 = x
const exp_val = gf16_decode_to_f32(exponent);
if (exp_val == 1.0) return base;
// Use stdlib pow for general case
const result = @pow(base_val, exp_val);
return gf16_encode_f32(result);
}
// gf16_sin(x: GF16) → GF16
// Compute sine function sin(x) where x is in radians
// Uses Taylor series approximation for small values
pub fn gf16_sin(x: GF16) GF16 {
if (gf16_is_nan(x)) return GF16_NAN;
if (gf16_is_inf(x)) return GF16_NAN;
const x_val = gf16_decode_to_f32(x);
// Taylor series: sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ...
// Use 4 terms for reasonable accuracy
const x_sq = x_val * x_val;
const x_cub = x_sq * x_val;
const x_5 = x_cub * x_sq;
const x_7 = x_5 * x_sq;
const term1 = x_val;
const term2 = -x_cub / 6.0;
const term3 = x_5 / 120.0;
const term4 = -x_7 / 5040.0;
const result = term1 + term2 + term3 + term4;
return gf16_encode_f32(result);
}
// gf16_cos(x: GF16) → GF16
// Compute cosine function cos(x) where x is in radians
// Uses Taylor series approximation for small values
pub fn gf16_cos(x: GF16) GF16 {
if (gf16_is_nan(x)) return GF16_NAN;
if (gf16_is_inf(x)) return GF16_NAN;
const x_val = gf16_decode_to_f32(x);
// Taylor series: cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ...
// Use 4 terms for reasonable accuracy
const x_sq = x_val * x_val;
const x_4 = x_sq * x_sq;
const x_6 = x_4 * x_sq;
const term0 = 1.0;
const term1 = -x_sq / 2.0;
const term2 = x_4 / 24.0;
const term3 = -x_6 / 720.0;
const result = term0 + term1 + term2 + term3;
return gf16_encode_f32(result);
}
// ============================================================================
// TDD - Tests
// ============================================================================
test "gf16_roundtrip_phi" {
// Verify: encoding f32 PHI to GF16 and decoding back preserves value within tolerance
const PHI: f32 = 1.6180339887498948;
const encoded = gf16_encode_f32(PHI);
const decoded = gf16_decode_to_f32(encoded);
try std.testing.expectApproxEqAbs(PHI, decoded, 0.001);
}
test "gf16_zero_encoding" {
// Verify: zero (positive and negative) encodes to correct GF16 patterns
try std.testing.expectEqual(@as(GF16, GF16_ZERO_POS), gf16_encode_f32(0.0));
try std.testing.expectEqual(@as(GF16, GF16_ZERO_NEG), gf16_encode_f32(-0.0));
}
test "gf16_phi_roundtrip_high_precision" {
// Verify: PHI roundtrip with higher tolerance for golden ratio
const PHI: f32 = 1.6180339887498948;
const encoded = gf16_encode_f32(PHI);
const decoded = gf16_decode_to_f32(encoded);
try std.testing.expectApproxEqAbs(PHI, decoded, 0.01);
}
test "gf16_inf_encoding" {
// Verify: overflow encodes to Inf correctly
const encoded = gf16_encode_f32(1.0e38);
try std.testing.expect(gf16_is_special(encoded));
try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(encoded));
}
test "gf16_sign_extraction" {
try std.testing.expectEqual(@as(i8, -1), gf16_extract_sign(0x8000));
try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(0x3C00));
try std.testing.expectEqual(@as(i8, 1), gf16_extract_sign(0x8000));
try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(0x3C00));
}
test "gf16_exponent_extraction" {
try std.testing.expectEqual(@as(i8, 0), gf16_extract_exponent(0x3C00));
try std.testing.expectEqual(@as(i8, 1), gf16_extract_exponent(0x3D00));
}
test "gf16_mantissa_extraction" {
try std.testing.expectEqual(@as(i16, 0), gf16_extract_mantissa(0x3C00));
try std.testing.expectEqual(@as(i16, 1), gf16_extract_mantissa(0x3C01));
try std.testing.expectEqual(@as(i16, 511), gf16_extract_mantissa(0x3DFF));
}
test "gf16_zero_detection" {
try std.testing.expect(gf16_is_zero(0x0000));
try std.testing.expect(gf16_is_zero(0x8000));
try std.testing.expect(!gf16_is_zero(0x0001));
}
test "gf16_special_detection" {
try std.testing.expect(gf16_is_special(0x7E00));
try std.testing.expect(gf16_is_special(0xFE01));
try std.testing.expect(!gf16_is_special(0x3C00));
}
test "gf16_from_components" {
const result = gf16_from_components(0, 0, 0);
try std.testing.expectEqual(@as(GF16, 0x3C00), result);
}
test "gf16_nan_encoding" {
const nan_val = gf16_from_components(0, 63, 1);
const decoded = gf16_decode_to_f32(nan_val);
try std.testing.expect(std.math.isNan(decoded));
}
test "gf16_round_phi_preserves_phi" {
const PHI: f32 = 1.6180339887498948;
const encoded = gf16_round_phi(PHI);
const decoded = gf16_decode_to_f32(encoded);
try std.testing.expectApproxEqAbs(PHI, decoded, 0.005);
}
test "gf16_round_phi_zero" {
try std.testing.expectEqual(@as(GF16, GF16_ZERO_POS), gf16_round_phi(0.0));
try std.testing.expectEqual(@as(GF16, GF16_ZERO_NEG), gf16_round_phi(-0.0));
}
test "gf16_round_phi_positive" {
try std.testing.expectApproxEqAbs(1.0, gf16_decode_to_f32(gf16_round_phi(1.0)), 0.01);
try std.testing.expectApproxEqAbs(2.0, gf16_decode_to_f32(gf16_round_phi(2.0)), 0.01);
try std.testing.expectApproxEqAbs(3.0, gf16_decode_to_f32(gf16_round_phi(3.0)), 0.01);
}
test "gf16_round_phi_negative" {
try std.testing.expectApproxEqAbs(-1.0, gf16_decode_to_f32(gf16_round_phi(-1.0)), 0.01);
try std.testing.expectApproxEqAbs(-2.0, gf16_decode_to_f32(gf16_round_phi(-2.0)), 0.01);
const PHI: f32 = 1.6180339887498948;
try std.testing.expectApproxEqAbs(-PHI, gf16_decode_to_f32(gf16_round_phi(-PHI)), 0.01);
}
test "gf16_pow2_table_consistency" {
try std.testing.expectEqual(@as(u16, 0x3C00), pow2_table[0]); // 2^0 = 1.0
try std.testing.expectEqual(@as(u16, 0x3D00), pow2_table[1]); // 2^1 = 2.0
try std.testing.expectEqual(@as(u16, 0x3D80), pow2_table[2]); // 2^2 = 4.0
}
test "gf16_exp_bias_identity" {
try std.testing.expectEqual(@as(i8, 31), BIAS);
}
test "gf16_identity_encoding" {
// For GF16 representing 1.0: sign=0, exp=0, mant=0, raw value = 0x3C00
try std.testing.expectEqual(@as(GF16, 0x3C00), gf16_from_components(0, 0, 0));
}
test "gf16_special_exp_all_ones" {
try std.testing.expectEqual(@as(u8, 0x3F), EXP_MAX);
}
test "gf16_is_inf_positive" {
try std.testing.expect(gf16_is_inf(GF16_INF_POS));
try std.testing.expect(!gf16_is_inf(GF16_ZERO_POS));
try std.testing.expect(!gf16_is_inf(0x3C00));
}
test "gf16_is_inf_negative" {
try std.testing.expect(gf16_is_inf(GF16_INF_NEG));
try std.testing.expect(!gf16_is_inf(GF16_ZERO_NEG));
}
test "gf16_is_nan_detection" {
try std.testing.expect(gf16_is_nan(GF16_NAN));
try std.testing.expect(!gf16_is_nan(GF16_INF_POS));
try std.testing.expect(!gf16_is_nan(GF16_ZERO_POS));
}
test "gf16_is_negative_detection" {
try std.testing.expect(gf16_is_negative(GF16_INF_NEG));
try std.testing.expect(gf16_is_negative(gf16_encode_f32(-1.5)));
try std.testing.expect(!gf16_is_negative(GF16_ZERO_NEG)); // -0 is not considered "negative"
try std.testing.expect(!gf16_is_negative(GF16_INF_POS));
}
test "gf16_is_positive_detection" {
try std.testing.expect(gf16_is_positive(GF16_INF_POS));
try std.testing.expect(gf16_is_positive(gf16_encode_f32(1.5)));
try std.testing.expect(!gf16_is_positive(GF16_ZERO_POS)); // +0 is not considered "positive"
try std.testing.expect(!gf16_is_positive(GF16_INF_NEG));
}
test "gf16_negate_sign_flip" {
const pos_one = gf16_encode_f32(1.0);
const neg_one = gf16_negate(pos_one);
const decoded = gf16_decode_to_f32(neg_one);
try std.testing.expectApproxEqAbs(-1.0, decoded, 0.01);
}
test "gf16_negate_zero_stays_zero" {
try std.testing.expectEqual(GF16_ZERO_POS, gf16_negate(GF16_ZERO_POS));
try std.testing.expectEqual(GF16_ZERO_NEG, gf16_negate(GF16_ZERO_NEG));
}
test "gf16_negate_double_negate" {
const original = gf16_encode_f32(1.5);
const negated = gf16_negate(original);
const double_negated = gf16_negate(negated);
const orig_decoded = gf16_decode_to_f32(original);
const double_decoded = gf16_decode_to_f32(double_negated);
try std.testing.expectApproxEqAbs(orig_decoded, double_decoded, 0.001);
}
test "gf16_abs_clears_sign" {
const neg_value = gf16_encode_f32(-2.5);
const abs_value = gf16_abs(neg_value);
const decoded = gf16_decode_to_f32(abs_value);
try std.testing.expectApproxEqAbs(2.5, decoded, 0.01);
}
test "gf16_abs_positive_unchanged" {
const pos_value = gf16_encode_f32(3.5);
const abs_value = gf16_abs(pos_value);
try std.testing.expectEqual(pos_value, abs_value);
}
test "gf16_abs_zero_unchanged" {
try std.testing.expectEqual(GF16_ZERO_POS, gf16_abs(GF16_ZERO_POS));
try std.testing.expectEqual(GF16_ZERO_POS, gf16_abs(GF16_ZERO_NEG));
}
test "gf16_copy_sign_from_negative" {
const pos_value = gf16_encode_f32(2.5);
const neg_source = gf16_encode_f32(-1.0);
const result = gf16_copy_sign(pos_value, neg_source);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-2.5, decoded, 0.01);
}
test "gf16_copy_sign_from_positive" {
const neg_value = gf16_encode_f32(-2.5);
const pos_source = gf16_encode_f32(1.0);
const result = gf16_copy_sign(neg_value, pos_source);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(2.5, decoded, 0.01);
}
test "gf16_max_returns_greater" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(5.0);
const result = gf16_max(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(5.0, decoded, 0.01);
}
test "gf16_max_equal_values" {
const a = gf16_encode_f32(3.0);
const b = gf16_encode_f32(3.0);
const result = gf16_max(a, b);
try std.testing.expectEqual(a, result);
}
test "gf16_max_with_nan" {
const a = gf16_encode_f32(2.0);
const nan_val = GF16_NAN;
const result = gf16_max(a, nan_val);
try std.testing.expectEqual(a, result);
}
test "gf16_min_returns_smaller" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(5.0);
const result = gf16_min(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(2.0, decoded, 0.01);
}
test "gf16_min_equal_values" {
const a = gf16_encode_f32(3.0);
const b = gf16_encode_f32(3.0);
const result = gf16_min(a, b);
try std.testing.expectEqual(a, result);
}
test "gf16_min_with_nan" {
const a = gf16_encode_f32(2.0);
const nan_val = GF16_NAN;
const result = gf16_min(a, nan_val);
try std.testing.expectEqual(a, result);
}
// ============================================================================
// TDD - Invariants
// ============================================================================
invariant gf16_identity_encoding {
// For GF16 representing 1.0: sign=0, exp=0, mant=0, raw value = 0x3C00
@compileAssert(gf16_from_components(0, 0, 0) == 0x3C00);
}
invariant gf16_sign_mask_bit_position {
// SIGN_MASK = 0x8000 has bit 15 set (MSB)
@compileAssert(SIGN_MASK == 0x8000);
}
invariant gf16_exp_mask_range {
// EXP_MASK = 0x7E00 covers bits 14-9 (6 bits for exponent)
@compileAssert(EXP_MASK == 0x7E00);
}
invariant gf16_mant_mask_range {
// MANT_MASK = 0x01FF covers bits 8-0 (9 bits for mantissa)
@compileAssert(MANT_MASK == 0x01FF);
}
invariant gf16_exp_bias_identity {
// BIAS = 31, so unbiased exp = encoded_exp - 31
@compileAssert(BIAS == 31);
}
invariant gf16_roundtrip_symmetry {
// For all normal values x: |decode(encode(x)) - x| < epsilon
@compileAssert(true);
}
invariant gf16_zero_uniqueness {
// Both 0x0000 and 0x8000 represent zero (positive/negative)
@compileAssert(GF16_ZERO_POS == 0x0000);
@compileAssert(GF16_ZERO_NEG == 0x8000);
}
invariant gf16_special_exp_all_ones {
// EXP_MAX = 0x3F (63) all ones indicates Inf/NaN
@compileAssert(EXP_MAX == 0x3F);
}
invariant gf16_pow2_table_consistency {
// pow2_table[n] encodes 2^n for n = 0 to 31
@compileAssert(pow2_table.len == 32);
}
invariant gf16_mantissa_implicit_one {
// For normal numbers: actual mantissa = 1 + (stored_mant / 512)
@compileAssert(MANT_DIVISOR == 512);
}
invariant gf16_phi_bias_positive {
// PHI_BIAS = 60 > 0
@compileAssert(PHI_BIAS > 0);
}
invariant gf16_phi_bias_less_than_mantissa_scale {
// PHI_BIAS = 60 < 512 (MANT_DIVISOR)
@compileAssert(PHI_BIAS < MANT_DIVISOR);
}
invariant gf16_round_phi_preserves_sign {
// For all x: sign(gf16_round_phi(x)) = sign(x)
@compileAssert(true);
}
invariant gf16_inf_exp_all_ones_mant_zero {
// Infinity: exp = 63, mant = 0
@compileAssert(gf16_extract_exponent(GF16_INF_POS) == EXP_MAX);
@compileAssert(gf16_extract_mantissa(GF16_INF_POS) == 0);
}
invariant gf16_nan_exp_all_ones_mant_nonzero {
// NaN: exp = 63, mant != 0
@compileAssert(gf16_extract_exponent(GF16_NAN) == EXP_MAX);
@compileAssert(gf16_extract_mantissa(GF16_NAN) != 0);
}
invariant gf16_negate_flips_sign_bit {
// gf16_negate(x) = x ^ 0x8000
@compileAssert(gf16_negate(0x3C00) == 0xBC00);
@compileAssert(gf16_negate(0xBC00) == 0x3C00);
}
invariant gf16_negate_involutive {
// gf16_negate(gf16_negate(x)) = x
@compileAssert(true);
}
invariant gf16_abs_clears_sign_bit {
// gf16_abs(x) = x & ~0x8000
@compileAssert(gf16_abs(0xBC00) == 0x3C00);
@compileAssert(gf16_abs(0x3C00) == 0x3C00);
}
invariant gf16_abs_non_negative {
// gf16_abs(x) is always non-negative (sign bit cleared)
@compileAssert((gf16_abs(0xBC00) & SIGN_MASK) == 0);
}
invariant gf16_copy_sign_preserves_sign_source {
// sign(gf16_copy_sign(x, s)) = sign(s)
@compileAssert(true);
}
invariant gf16_copy_sign_preserves_magnitude {
// |gf16_copy_sign(x, s)| = |x|
@compileAssert(true);
}
invariant gf16_max_idempotent {
// gf16_max(x, x) = x
@compileAssert(true);
}
invariant gf16_min_idempotent {
// gf16_min(x, x) = x
@compileAssert(true);
}
invariant gf16_max_commutative {
// gf16_max(a, b) = gf16_max(b, a)
@compileAssert(true);
}
invariant gf16_min_commutative {
// gf16_min(a, b) = gf16_min(b, a)
@compileAssert(true);
}
invariant gf16_is_inf_and_is_nan_exclusive {
// A value cannot be both Inf and NaN
@compileAssert(!gf16_is_inf(GF16_NAN));
@compileAssert(!gf16_is_nan(GF16_INF_POS));
}
invariant gf16_add_zero_identity {
// gf16_add(x, 0) = gf16_add(0, x) = x (approximately, due to encoding)
@compileAssert(true);
}
invariant gf16_mul_zero_annihilates {
// gf16_mul(x, 0) = gf16_mul(0, x) = 0
@compileAssert(true);
}
invariant gf16_mul_one_identity {
// gf16_mul(x, 1) = gf16_mul(1, x) = x (approximately)
@compileAssert(true);
}
invariant gf16_negate_involutive {
// gf16_negate(gf16_negate(x)) = x
@compileAssert(true);
}
invariant gf16_add_commutative {
// gf16_add(a, b) = gf16_add(b, a)
@compileAssert(true);
}
invariant gf16_mul_commutative {
// gf16_mul(a, b) = gf16_mul(b, a)
@compileAssert(true);
}
invariant gf16_div_by_one_identity {
// gf16_div(x, 1) = x (approximately)
@compileAssert(true);
}
invariant gf16_sqrt_non_negative {
// gf16_sqrt(x) >= 0 for all x >= 0
@compileAssert(true);
}
invariant gf16_sqrt_of_square_less_than_or_equal {
// gf16_sqrt(gf16_square(x)) <= x for all x >= 0
@compileAssert(true);
}
invariant gf16_fma_distributive_approximation {
// gf16_fma(a, b, c) ≈ gf16_add(gf16_mul(a, b), c)
// Not exact due to encoding rounding
@compileAssert(true);
}
invariant gf16_square_positive {
// gf16_square(x) >= 0 for all x
@compileAssert(true);
}
invariant gf16_eq_reflexive_for_non_nan {
// For all x != NaN: gf16_eq(x, x) = true
@compileAssert(true);
}
invariant gf16_ne_irreflexive_for_non_nan {
// For all x != NaN: gf16_ne(x, x) = false
@compileAssert(true);
}
invariant gf16_lt_and_gt_mutually_exclusive {
// For all a, b: not (gf16_lt(a, b) and gf16_gt(a, b))
@compileAssert(true);
}
invariant gf16_le_and_ge_mutually_inclusive {
// For all a, b: gf16_le(a, b) or gf16_ge(a, b) (for non-NaN)
@compileAssert(true);
}
invariant gf16_lt_implies_le {
// For all a, b: gf16_lt(a, b) implies gf16_le(a, b)
@compileAssert(true);
}
invariant gf16_gt_implies_ge {
// For all a, b: gf16_gt(a, b) implies gf16_ge(a, b)
@compileAssert(true);
}
invariant gf16_eq_implies_le_and_ge {
// For all a, b: gf16_eq(a, b) implies gf16_le(a, b) and gf16_ge(a, b)
@compileAssert(true);
}
invariant gf16_ne_nan_is_true {
// gf16_ne(NaN, NaN) = true per IEEE 754
@compileAssert(true);
}
invariant gf16_lt_nan_is_false {
// gf16_lt(NaN, x) = false for all x
@compileAssert(true);
}
invariant gf16_gt_nan_is_false {
// gf16_gt(NaN, x) = false for all x
@compileAssert(true);
}
invariant gf16_floor_yields_integer {
// For all x != NaN, Inf: floor(gf16_floor(x)) = gf16_floor(x)
@compileAssert(true);
}
invariant gf16_ceil_yields_integer {
// For all x != NaN, Inf: ceil(gf16_ceil(x)) = gf16_ceil(x)
@compileAssert(true);
}
invariant gf16_round_yields_integer {
// For all x != NaN, Inf: round(gf16_round(x)) = gf16_round(x)
@compileAssert(true);
}
invariant gf16_trunc_yields_integer {
// For all x != NaN, Inf: trunc(gf16_trunc(x)) = gf16_trunc(x)
@compileAssert(true);
}
invariant gf16_floor_le_value {
// For all x: floor(x) <= x
@compileAssert(true);
}
invariant gf16_ceil_ge_value {
// For all x: ceil(x) >= x
@compileAssert(true);
}
invariant gf16_round_closest_integer {
// For all x: |round(x) - x| <= 0.5
@compileAssert(true);
}
invariant gf16_trunc_magnitude_less_or_equal {
// For all x: |trunc(x)| <= |x|
@compileAssert(true);
}
invariant gf16_trunc_positive_equals_floor {
// For all x >= 0: trunc(x) = floor(x)
@compileAssert(true);
}
invariant gf16_trunc_negative_equals_ceil {
// For all x <= 0: trunc(x) = ceil(x)
@compileAssert(true);
}
invariant gf16_fms_related_to_fma {
// gf16_fms(a, b, c) = gf16_fma(a, b, -c) (approximately, due to encoding)
@compileAssert(true);
}
invariant gf16_fms_with_zero_subtractand {
// gf16_fms(a, b, 0) = gf16_mul(a, b) (approximately)
@compileAssert(true);
}
invariant gf16_hypot_non_negative {
// For all a, b: gf16_hypot(a, b) >= 0
@compileAssert(true);
}
invariant gf16_hypot_symmetric {
// For all a, b: gf16_hypot(a, b) = gf16_hypot(b, a)
@compileAssert(true);
}
invariant gf16_hypot_pythagorean_identity {
// For all a, b: hypot(a, b)^2 = a^2 + b^2 (approximately, due to encoding)
@compileAssert(true);
}
invariant gf16_hypot_ge_max_input {
// For all a, b: gf16_hypot(a, b) >= max(|a|, |b|)
@compileAssert(true);
}
invariant gf16_hypot_zero_with_zeros {
// gf16_hypot(0, 0) = 0
@compileAssert(true);
}
invariant gf16_fmod_result_sign_matches_dividend {
// For all a, b where b != 0: sign(gf16_fmod(a, b)) = sign(a)
@compileAssert(true);
}
invariant gf16_fmod_less_than_divisor {
// For all a, b where b > 0: |gf16_fmod(a, b)| < |b|
@compileAssert(true);
}
invariant gf16_fmod_with_divisible_values {
// For all a, b where a = k*b: gf16_fmod(a, b) = 0
@compileAssert(true);
}
invariant gf16_is_finite_excludes_inf_nan {
// gf16_is_finite(x) = true implies !gf16_is_inf(x) and !gf16_is_nan(x)
@compileAssert(true);
}
invariant gf16_is_normal_implies_finite {
// gf16_is_normal(x) = true implies gf16_is_finite(x)
@compileAssert(true);
}
invariant gf16_is_subnormal_implies_finite {
// gf16_is_subnormal(x) = true implies gf16_is_finite(x)
@compileAssert(true);
}
invariant gf16_is_normal_and_subnormal_mutually_exclusive {
// gf16_is_normal(x) and gf16_is_subnormal(x) cannot both be true
@compileAssert(true);
}
invariant gf16_zero_neither_normal_nor_subnormal {
// gf16_is_zero(x) = true implies !gf16_is_normal(x) and !gf16_is_subnormal(x)
@compileAssert(true);
}
invariant gf16_classification_exhaustive {
// For all x: (is_finite and (is_normal or is_subnormal or is_zero)) or is_inf or is_nan
@compileAssert(true);
}
invariant gf16_signbit_positive_no_signbit {
// gf16_signbit(x) = false for x >= 0 (including +0 and +inf)
@compileAssert(true);
}
invariant gf16_signbit_negative_has_signbit {
// gf16_signbit(x) = true for x < 0 (including -0 and -inf)
@compileAssert(true);
}
invariant gf16_sign_positive_returns_one {
// For x > 0 and x is not NaN: gf16_sign(x) = 1
@compileAssert(true);
}
invariant gf16_sign_negative_returns_minus_one {
// For x < 0 and x is not NaN: gf16_sign(x) = -1
@compileAssert(true);
}
invariant gf16_sign_zero_returns_zero {
// For x = 0 (positive or negative): gf16_sign(x) = 0
@compileAssert(true);
}
invariant gf16_sign_nan_returns_zero {
// For NaN: gf16_sign(x) = 0 (sign of NaN is undefined)
@compileAssert(true);
}
invariant gf16_clamp_in_range_returns_value {
// For x in [min, max]: gf16_clamp(x, min, max) = x
@compileAssert(true);
}
invariant gf16_clamp_below_min_returns_min {
// For x < min: gf16_clamp(x, min, max) = min
@compileAssert(true);
}
invariant gf16_clamp_above_max_returns_max {
// For x > max: gf16_clamp(x, min, max) = max
@compileAssert(true);
}
invariant gf16_lerp_t_zero_returns_a {
// gf16_lerp(a, b, 0) = a
@compileAssert(true);
}
invariant gf16_lerp_t_one_returns_b {
// gf16_lerp(a, b, 1) = b
@compileAssert(true);
}
invariant gf16_lerp_monotonic {
// For fixed a < b: gf16_lerp(a, b, t) is monotonic in t
@compileAssert(true);
}
invariant gf16_fnma_equals_neg_mul_plus_c {
// gf16_fnma(a, b, c) = -(a*b) + c (approximately, with better precision)
@compileAssert(true);
}
invariant gf16_fnma_zero_multiplier_returns_c {
// gf16_fnma(0, b, c) = c
@compileAssert(true);
}
invariant gf16_exp_zero_returns_one {
// gf16_exp(0) = 1
@compileAssert(true);
}
invariant gf16_exp_positive_greater_than_one {
// gf16_exp(x) > 1 for x > 0
@compileAssert(true);
}
invariant gf16_exp_negative_between_zero_and_one {
// 0 < gf16_exp(x) < 1 for x < 0
@compileAssert(true);
}
invariant gf16_log_one_returns_zero {
// gf16_log(1) = 0
@compileAssert(true);
}
invariant gf16_log_zero_or_negative_nan {
// gf16_log(x) = NaN for x <= 0
@compileAssert(true);
}
invariant gf16_pow_zero_to_zero_returns_one {
// gf16_pow(0, 0) = 1 (by convention)
@compileAssert(true);
}
invariant gf16_pow_any_to_zero_returns_one {
// gf16_pow(x, 0) = 1 for x != 0
@compileAssert(true);
}
invariant gf16_pow_one_to_any_returns_one {
// gf16_pow(1, x) = 1 for finite x
@compileAssert(true);
}
invariant gf16_sin_zero_returns_zero {
// gf16_sin(0) = 0
@compileAssert(true);
}
invariant gf16_cos_zero_returns_one {
// gf16_cos(0) = 1
@compileAssert(true);
}
invariant gf16_trig_identity_approx {
// sin^2(x) + cos^2(x) ≈ 1 for reasonable x values
@compileAssert(true);
}
test "gf16_add_positive_values" {
const a = gf16_encode_f32(1.5);
const b = gf16_encode_f32(2.5);
const result = gf16_add(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(4.0, decoded, 0.1);
}
test "gf16_add_negative_values" {
const a = gf16_encode_f32(-1.5);
const b = gf16_encode_f32(-2.5);
const result = gf16_add(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-4.0, decoded, 0.1);
}
test "gf16_add_opposite_values" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(-2.0);
const result = gf16_add(a, b);
try std.testing.expect(gf16_is_zero(result));
}
test "gf16_add_with_zero" {
const a = gf16_encode_f32(3.5);
const zero = gf16_encode_f32(0.0);
const result1 = gf16_add(a, zero);
const result2 = gf16_add(zero, a);
const decoded1 = gf16_decode_to_f32(result1);
const decoded2 = gf16_decode_to_f32(result2);
try std.testing.expectApproxEqAbs(3.5, decoded1, 0.05);
try std.testing.expectApproxEqAbs(3.5, decoded2, 0.05);
}
test "gf16_sub_positive_values" {
const a = gf16_encode_f32(5.0);
const b = gf16_encode_f32(2.0);
const result = gf16_sub(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(3.0, decoded, 0.1);
}
test "gf16_sub_negative_result" {
const a = gf16_encode_f32(1.0);
const b = gf16_encode_f32(3.0);
const result = gf16_sub(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1);
}
test "gf16_sub_with_zero" {
const a = gf16_encode_f32(2.5);
const zero = gf16_encode_f32(0.0);
const result1 = gf16_sub(a, zero);
const result2 = gf16_sub(zero, a);
const decoded1 = gf16_decode_to_f32(result1);
const decoded2 = gf16_decode_to_f32(result2);
try std.testing.expectApproxEqAbs(2.5, decoded1, 0.05);
try std.testing.expectApproxEqAbs(-2.5, decoded2, 0.05);
}
test "gf16_mul_positive_values" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
const result = gf16_mul(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(6.0, decoded, 0.1);
}
test "gf16_mul_negative_positive" {
const a = gf16_encode_f32(-2.0);
const b = gf16_encode_f32(3.0);
const result = gf16_mul(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-6.0, decoded, 0.1);
}
test "gf16_mul_with_zero" {
const a = gf16_encode_f32(5.0);
const zero = gf16_encode_f32(0.0);
const result1 = gf16_mul(a, zero);
const result2 = gf16_mul(zero, a);
try std.testing.expect(gf16_is_zero(result1));
try std.testing.expect(gf16_is_zero(result2));
}
test "gf16_mul_by_one" {
const a = gf16_encode_f32(3.5);
const one = gf16_encode_f32(1.0);
const result = gf16_mul(a, one);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(3.5, decoded, 0.05);
}
test "gf16_div_positive_values" {
const a = gf16_encode_f32(6.0);
const b = gf16_encode_f32(3.0);
const result = gf16_div(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(2.0, decoded, 0.1);
}
test "gf16_div_negative_result" {
const a = gf16_encode_f32(6.0);
const b = gf16_encode_f32(-3.0);
const result = gf16_div(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1);
}
test "gf16_div_by_one" {
const a = gf16_encode_f32(2.5);
const one = gf16_encode_f32(1.0);
const result = gf16_div(a, one);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(2.5, decoded, 0.05);
}
test "gf16_div_zero_by_value" {
const zero = gf16_encode_f32(0.0);
const a = gf16_encode_f32(5.0);
const result = gf16_div(zero, a);
try std.testing.expect(gf16_is_zero(result));
}
test "gf16_div_value_by_zero" {
const a = gf16_encode_f32(5.0);
const zero = gf16_encode_f32(0.0);
const result = gf16_div(a, zero);
try std.testing.expect(gf16_is_inf(result));
}
test "gf16_div_inf_by_finite" {
const inf = GF16_INF_POS;
const a = gf16_encode_f32(5.0);
const result = gf16_div(inf, a);
try std.testing.expect(gf16_is_inf(result));
}
test "gf16_fma_basic" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
const c = gf16_encode_f32(4.0);
const result = gf16_fma(a, b, c);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(10.0, decoded, 0.2);
}
test "gf16_fma_with_zero" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
const zero = gf16_encode_f32(0.0);
const result = gf16_fma(a, b, zero);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(6.0, decoded, 0.15);
}
test "gf16_sqrt_positive" {
const a = gf16_encode_f32(4.0);
const result = gf16_sqrt(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(2.0, decoded, 0.05);
}
test "gf16_sqrt_of_one" {
const a = gf16_encode_f32(1.0);
const result = gf16_sqrt(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(1.0, decoded, 0.05);
}
test "gf16_sqrt_of_zero" {
const zero = gf16_encode_f32(0.0);
const result = gf16_sqrt(zero);
try std.testing.expect(gf16_is_zero(result));
}
test "gf16_sqrt_negative_nan" {
const neg = gf16_encode_f32(-4.0);
const result = gf16_sqrt(neg);
try std.testing.expect(gf16_is_nan(result));
}
test "gf16_square_of_two" {
const a = gf16_encode_f32(2.0);
const result = gf16_square(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(4.0, decoded, 0.1);
}
test "gf16_square_of_zero" {
const zero = gf16_encode_f32(0.0);
const result = gf16_square(zero);
try std.testing.expect(gf16_is_zero(result));
}
test "gf16_square_of_negative" {
const neg = gf16_encode_f32(-2.0);
const result = gf16_square(neg);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(4.0, decoded, 0.1);
}
test "gf16_add_commutative" {
const a = gf16_encode_f32(1.5);
const b = gf16_encode_f32(2.5);
const result1 = gf16_add(a, b);
const result2 = gf16_add(b, a);
try std.testing.expectEqual(result1, result2);
}
test "gf16_mul_commutative" {
const a = gf16_encode_f32(1.5);
const b = gf16_encode_f32(2.5);
const result1 = gf16_mul(a, b);
const result2 = gf16_mul(b, a);
try std.testing.expectEqual(result1, result2);
}
test "gf16_sqrt_square_roundtrip" {
const a = gf16_encode_f32(4.0);
const squared = gf16_square(a);
const rooted = gf16_sqrt(squared);
const decoded = gf16_decode_to_f32(rooted);
try std.testing.expectApproxEqAbs(4.0, decoded, 0.2);
}
test "gf16_eq_equal_values" {
const a = gf16_encode_f32(2.5);
const b = gf16_encode_f32(2.5);
try std.testing.expect(gf16_eq(a, b));
}
test "gf16_eq_different_values" {
const a = gf16_encode_f32(2.5);
const b = gf16_encode_f32(3.5);
try std.testing.expect(!gf16_eq(a, b));
}
test "gf16_eq_pos_zero_eq_neg_zero" {
// IEEE 754: +0.0 == -0.0 is true
try std.testing.expect(gf16_eq(GF16_ZERO_POS, GF16_ZERO_NEG));
}
test "gf16_eq_nan_not_equal_nan" {
// NaN != NaN per IEEE 754
try std.testing.expect(!gf16_eq(GF16_NAN, GF16_NAN));
}
test "gf16_eq_nan_not_equal_value" {
const value = gf16_encode_f32(1.5);
try std.testing.expect(!gf16_eq(GF16_NAN, value));
try std.testing.expect(!gf16_eq(value, GF16_NAN));
}
test "gf16_ne_different_values" {
const a = gf16_encode_f32(2.5);
const b = gf16_encode_f32(3.5);
try std.testing.expect(gf16_ne(a, b));
}
test "gf16_ne_equal_values" {
const a = gf16_encode_f32(2.5);
const b = gf16_encode_f32(2.5);
try std.testing.expect(!gf16_ne(a, b));
}
test "gf16_ne_nan_not_equal_nan" {
// NaN != NaN per IEEE 754
try std.testing.expect(gf16_ne(GF16_NAN, GF16_NAN));
}
test "gf16_ne_nan_not_equal_value" {
const value = gf16_encode_f32(1.5);
try std.testing.expect(gf16_ne(GF16_NAN, value));
try std.testing.expect(gf16_ne(value, GF16_NAN));
}
test "gf16_lt_less_than" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
try std.testing.expect(gf16_lt(a, b));
}
test "gf16_lt_equal_values" {
const a = gf16_encode_f32(2.5);
const b = gf16_encode_f32(2.5);
try std.testing.expect(!gf16_lt(a, b));
}
test "gf16_lt_greater_than" {
const a = gf16_encode_f32(3.0);
const b = gf16_encode_f32(2.0);
try std.testing.expect(!gf16_lt(a, b));
}
test "gf16_lt_negative_positive" {
const neg = gf16_encode_f32(-2.0);
const pos = gf16_encode_f32(1.0);
try std.testing.expect(gf16_lt(neg, pos));
}
test "gf16_lt_with_nan" {
const value = gf16_encode_f32(1.5);
try std.testing.expect(!gf16_lt(GF16_NAN, value));
try std.testing.expect(!gf16_lt(value, GF16_NAN));
}
test "gf16_le_less_than_or_equal" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
try std.testing.expect(gf16_le(a, b));
}
test "gf16_le_equal_values" {
const a = gf16_encode_f32(2.5);
const b = gf16_encode_f32(2.5);
try std.testing.expect(gf16_le(a, b));
}
test "gf16_le_greater_than" {
const a = gf16_encode_f32(3.0);
const b = gf16_encode_f32(2.0);
try std.testing.expect(!gf16_le(a, b));
}
test "gf16_le_with_nan" {
const value = gf16_encode_f32(1.5);
try std.testing.expect(!gf16_le(GF16_NAN, value));
try std.testing.expect(!gf16_le(value, GF16_NAN));
}
test "gf16_gt_greater_than" {
const a = gf16_encode_f32(3.0);
const b = gf16_encode_f32(2.0);
try std.testing.expect(gf16_gt(a, b));
}
test "gf16_gt_equal_values" {
const a = gf16_encode_f32(2.5);
const b = gf16_encode_f32(2.5);
try std.testing.expect(!gf16_gt(a, b));
}
test "gf16_gt_less_than" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
try std.testing.expect(!gf16_gt(a, b));
}
test "gf16_gt_with_nan" {
const value = gf16_encode_f32(1.5);
try std.testing.expect(!gf16_gt(GF16_NAN, value));
try std.testing.expect(!gf16_gt(value, GF16_NAN));
}
test "gf16_ge_greater_than_or_equal" {
const a = gf16_encode_f32(3.0);
const b = gf16_encode_f32(2.0);
try std.testing.expect(gf16_ge(a, b));
}
test "gf16_ge_equal_values" {
const a = gf16_encode_f32(2.5);
const b = gf16_encode_f32(2.5);
try std.testing.expect(gf16_ge(a, b));
}
test "gf16_ge_less_than" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
try std.testing.expect(!gf16_ge(a, b));
}
test "gf16_ge_with_nan" {
const value = gf16_encode_f32(1.5);
try std.testing.expect(!gf16_ge(GF16_NAN, value));
try std.testing.expect(!gf16_ge(value, GF16_NAN));
}
test "gf16_comparison_consistency" {
// Verify: lt, le, gt, ge, eq, ne are mutually consistent
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
// a < b implies a <= b and !a > b and !a >= b
try std.testing.expect(gf16_lt(a, b));
try std.testing.expect(gf16_le(a, b));
try std.testing.expect(!gf16_gt(a, b));
try std.testing.expect(!gf16_ge(a, b));
}
test "gf16_floor_positive_value" {
const a = gf16_encode_f32(2.7);
const result = gf16_floor(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(2.0, decoded, 0.1);
}
test "gf16_floor_negative_value" {
const a = gf16_encode_f32(-2.7);
const result = gf16_floor(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-3.0, decoded, 0.1);
}
test "gf16_floor_integer" {
const a = gf16_encode_f32(5.0);
const result = gf16_floor(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(5.0, decoded, 0.05);
}
test "gf16_floor_zero" {
const pos_zero = gf16_encode_f32(0.0);
const neg_zero = gf16_encode_f32(-0.0);
try std.testing.expect(gf16_is_zero(gf16_floor(pos_zero)));
try std.testing.expect(gf16_is_zero(gf16_floor(neg_zero)));
}
test "gf16_floor_inf_unchanged" {
try std.testing.expectEqual(GF16_INF_POS, gf16_floor(GF16_INF_POS));
try std.testing.expectEqual(GF16_INF_NEG, gf16_floor(GF16_INF_NEG));
}
test "gf16_floor_nan_returns_nan" {
try std.testing.expect(gf16_is_nan(gf16_floor(GF16_NAN)));
}
test "gf16_ceil_positive_value" {
const a = gf16_encode_f32(2.3);
const result = gf16_ceil(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(3.0, decoded, 0.1);
}
test "gf16_ceil_negative_value" {
const a = gf16_encode_f32(-2.7);
const result = gf16_ceil(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1);
}
test "gf16_ceil_integer" {
const a = gf16_encode_f32(5.0);
const result = gf16_ceil(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(5.0, decoded, 0.05);
}
test "gf16_ceil_inf_unchanged" {
try std.testing.expectEqual(GF16_INF_POS, gf16_ceil(GF16_INF_POS));
try std.testing.expectEqual(GF16_INF_NEG, gf16_ceil(GF16_INF_NEG));
}
test "gf16_ceil_nan_returns_nan" {
try std.testing.expect(gf16_is_nan(gf16_ceil(GF16_NAN)));
}
test "gf16_round_half_up" {
const a = gf16_encode_f32(2.5);
const result = gf16_round(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); // roundTiesToEven
}
test "gf16_round_positive_value" {
const a = gf16_encode_f32(2.7);
const result = gf16_round(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(3.0, decoded, 0.1);
}
test "gf16_round_negative_value" {
const a = gf16_encode_f32(-2.7);
const result = gf16_round(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-3.0, decoded, 0.1);
}
test "gf16_round_fractional_down" {
const a = gf16_encode_f32(2.3);
const result = gf16_round(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(2.0, decoded, 0.1);
}
test "gf16_round_inf_unchanged" {
try std.testing.expectEqual(GF16_INF_POS, gf16_round(GF16_INF_POS));
try std.testing.expectEqual(GF16_INF_NEG, gf16_round(GF16_INF_NEG));
}
test "gf16_round_nan_returns_nan" {
try std.testing.expect(gf16_is_nan(gf16_round(GF16_NAN)));
}
test "gf16_trunc_positive_value" {
const a = gf16_encode_f32(2.7);
const result = gf16_trunc(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(2.0, decoded, 0.1);
}
test "gf16_trunc_negative_value" {
const a = gf16_encode_f32(-2.7);
const result = gf16_trunc(a);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1);
}
test "gf16_trunc_zero" {
const pos_zero = gf16_encode_f32(0.0);
const neg_zero = gf16_encode_f32(-0.0);
try std.testing.expect(gf16_is_zero(gf16_trunc(pos_zero)));
try std.testing.expect(gf16_is_zero(gf16_trunc(neg_zero)));
}
test "gf16_trunc_inf_unchanged" {
try std.testing.expectEqual(GF16_INF_POS, gf16_trunc(GF16_INF_POS));
try std.testing.expectEqual(GF16_INF_NEG, gf16_trunc(GF16_INF_NEG));
}
test "gf16_trunc_nan_returns_nan" {
try std.testing.expect(gf16_is_nan(gf16_trunc(GF16_NAN)));
}
test "gf16_rounding_floor_vs_trunc_negative" {
// floor(-2.7) = -3.0, trunc(-2.7) = -2.0
const a = gf16_encode_f32(-2.7);
const floored = gf16_decode_to_f32(gf16_floor(a));
const truncated = gf16_decode_to_f32(gf16_trunc(a));
try std.testing.expectApproxEqAbs(-3.0, floored, 0.1);
try std.testing.expectApproxEqAbs(-2.0, truncated, 0.1);
}
test "gf16_rounding_ceil_vs_trunc_positive" {
// ceil(2.3) = 3.0, trunc(2.3) = 2.0
const a = gf16_encode_f32(2.3);
const ceiled = gf16_decode_to_f32(gf16_ceil(a));
const truncated = gf16_decode_to_f32(gf16_trunc(a));
try std.testing.expectApproxEqAbs(3.0, ceiled, 0.1);
try std.testing.expectApproxEqAbs(2.0, truncated, 0.1);
}
test "gf16_fms_basic" {
const a = gf16_encode_f32(5.0);
const b = gf16_encode_f32(3.0);
const c = gf16_encode_f32(2.0);
const result = gf16_fms(a, b, c);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(13.0, decoded, 0.2); // 5*3 - 2 = 13
}
test "gf16_fms_with_zero_c" {
const a = gf16_encode_f32(4.0);
const b = gf16_encode_f32(3.0);
const c = gf16_encode_f32(0.0);
const result = gf16_fms(a, b, c);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(12.0, decoded, 0.2); // 4*3 - 0 = 12
}
test "gf16_fms_negative_result" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
const c = gf16_encode_f32(10.0);
const result = gf16_fms(a, b, c);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-4.0, decoded, 0.2); // 2*3 - 10 = -4
}
test "gf16_fms_with_nan" {
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
const result = gf16_fms(a, b, GF16_NAN);
try std.testing.expect(gf16_is_nan(result));
}
test "gf16_fms_zero_a" {
const a = gf16_encode_f32(0.0);
const b = gf16_encode_f32(5.0);
const c = gf16_encode_f32(3.0);
const result = gf16_fms(a, b, c);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-3.0, decoded, 0.2); // 0 - 3 = -3
}
test "gf16_hypot_pythagorean_triple" {
const a = gf16_encode_f32(3.0);
const b = gf16_encode_f32(4.0);
const result = gf16_hypot(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); // sqrt(9 + 16) = 5
}
test "gf16_hypot_both_zero" {
try std.testing.expectEqual(GF16_ZERO_POS, gf16_hypot(GF16_ZERO_POS, GF16_ZERO_POS));
}
test "gf16_hypot_one_zero" {
const a = gf16_encode_f32(3.0);
const result = gf16_hypot(a, GF16_ZERO_POS);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(3.0, decoded, 0.05);
}
test "gf16_hypot_negative_inputs" {
const a = gf16_encode_f32(-3.0);
const b = gf16_encode_f32(-4.0);
const result = gf16_hypot(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); // sqrt(9 + 16) = 5
}
test "gf16_hypot_with_nan" {
const a = gf16_encode_f32(3.0);
const result = gf16_hypot(a, GF16_NAN);
try std.testing.expect(gf16_is_nan(result));
}
test "gf16_hypot_with_inf" {
const a = gf16_encode_f32(3.0);
try std.testing.expectEqual(GF16_INF_POS, gf16_hypot(a, GF16_INF_POS));
}
test "gf16_fmod_basic" {
const a = gf16_encode_f32(10.0);
const b = gf16_encode_f32(3.0);
const result = gf16_fmod(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); // 10 % 3 = 1
}
test "gf16_fmod_exact_division" {
const a = gf16_encode_f32(12.0);
const b = gf16_encode_f32(3.0);
const result = gf16_fmod(a, b);
try std.testing.expect(gf16_is_zero(result));
}
test "gf16_fmod_negative_dividend" {
const a = gf16_encode_f32(-10.0);
const b = gf16_encode_f32(3.0);
const result = gf16_fmod(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-1.0, decoded, 0.1); // -10 % 3 = -1 (sign follows dividend)
}
test "gf16_fmod_fractional" {
const a = gf16_encode_f32(5.5);
const b = gf16_encode_f32(2.0);
const result = gf16_fmod(a, b);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(1.5, decoded, 0.1); // 5.5 % 2 = 1.5
}
test "gf16_fmod_zero_divisor" {
const a = gf16_encode_f32(10.0);
const zero = gf16_encode_f32(0.0);
const result = gf16_fmod(a, zero);
try std.testing.expect(gf16_is_nan(result));
}
test "gf16_fmod_with_nan" {
const a = gf16_encode_f32(10.0);
const result = gf16_fmod(a, GF16_NAN);
try std.testing.expect(gf16_is_nan(result));
}
test "gf16_is_finite_normal_numbers" {
// Verify: normal numbers are finite
const n1 = gf16_encode_f32(1.0);
const n2 = gf16_encode_f32(-1.0);
const n3 = gf16_encode_f32(100.5);
try std.testing.expect(gf16_is_finite(n1));
try std.testing.expect(gf16_is_finite(n2));
try std.testing.expect(gf16_is_finite(n3));
}
test "gf16_is_finite_zero" {
// Verify: zero is finite
const z1 = gf16_encode_f32(0.0);
const z2 = gf16_encode_f32(-0.0);
try std.testing.expect(gf16_is_finite(z1));
try std.testing.expect(gf16_is_finite(z2));
}
test "gf16_is_finite_false_for_inf" {
// Verify: infinity is not finite
const pos_inf = GF16_INF_POS;
const neg_inf = GF16_INF_NEG;
try std.testing.expect(!gf16_is_finite(pos_inf));
try std.testing.expect(!gf16_is_finite(neg_inf));
}
test "gf16_is_finite_false_for_nan" {
// Verify: NaN is not finite
try std.testing.expect(!gf16_is_finite(GF16_NAN));
}
test "gf16_is_normal_true_for_normal" {
// Verify: normal numbers return true
const n1 = gf16_encode_f32(1.0);
const n2 = gf16_encode_f32(-2.5);
const n3 = gf16_encode_f32(100.0);
try std.testing.expect(gf16_is_normal(n1));
try std.testing.expect(gf16_is_normal(n2));
try std.testing.expect(gf16_is_normal(n3));
}
test "gf16_is_normal_false_for_zero" {
// Verify: zero is not normal
const z1 = gf16_encode_f32(0.0);
const z2 = gf16_encode_f32(-0.0);
try std.testing.expect(!gf16_is_normal(z1));
try std.testing.expect(!gf16_is_normal(z2));
}
test "gf16_is_normal_false_for_inf" {
// Verify: infinity is not normal
try std.testing.expect(!gf16_is_normal(GF16_INF_POS));
try std.testing.expect(!gf16_is_normal(GF16_INF_NEG));
}
test "gf16_is_normal_false_for_nan" {
// Verify: NaN is not normal
try std.testing.expect(!gf16_is_normal(GF16_NAN));
}
test "gf16_is_subnormal_true_for_subnormal" {
// Verify: subnormal (denormal) numbers return true
// Smallest subnormal in GF16: exp=0, mant=1 (approximately 2^-14 * 2^-9 = 2^-23)
// We'll check a value that decodes to subnormal
const sub = gf16_encode_f32(0.000001);
const decoded = gf16_decode_to_f32(sub);
// If the value rounds to subnormal, is_subnormal should be true
// This test depends on GF16 subnormal threshold (~6.1e-5)
const is_sub = gf16_is_subnormal(sub);
_ = decoded;
_ = is_sub;
// We just verify the function doesn't crash for now
try std.testing.expect(true);
}
test "gf16_is_subnormal_false_for_normal" {
// Verify: normal numbers are not subnormal
const n1 = gf16_encode_f32(1.0);
const n2 = gf16_encode_f32(100.0);
try std.testing.expect(!gf16_is_subnormal(n1));
try std.testing.expect(!gf16_is_subnormal(n2));
}
test "gf16_is_subnormal_false_for_zero" {
// Verify: zero is not subnormal (zero is a special case)
const z1 = gf16_encode_f32(0.0);
const z2 = gf16_encode_f32(-0.0);
try std.testing.expect(!gf16_is_subnormal(z1));
try std.testing.expect(!gf16_is_subnormal(z2));
}
test "gf16_is_subnormal_false_for_special" {
// Verify: NaN and infinity are not subnormal
try std.testing.expect(!gf16_is_subnormal(GF16_NAN));
try std.testing.expect(!gf16_is_subnormal(GF16_INF_POS));
try std.testing.expect(!gf16_is_subnormal(GF16_INF_NEG));
}
test "gf16_classification_complete_coverage" {
// Verify: all GF16 values can be classified
// For any value, exactly one of these should be true:
// - is_finite and (is_normal or is_subnormal or is_zero)
// OR is_inf
// OR is_nan
const test_values = [_]f32{
0.0, -0.0, 1.0, -1.0, 100.0, -100.0,
0.0001, -0.0001,
};
for (test_values) |val| {
const gf = gf16_encode_f32(val);
const is_fin = gf16_is_finite(gf);
const is_inf = gf16_is_inf(gf);
const is_nan = gf16_is_nan(gf);
// Exactly one of finite, inf, nan should be true
const count = @as(u8, @intFromBool(is_fin)) +
@as(u8, @intFromBool(is_inf)) +
@as(u8, @intFromBool(is_nan));
try std.testing.expectEqual(@as(u8, 1), count);
}
}
test "gf16_signbit_positive" {
// Verify: positive values have signbit = false
const val = gf16_encode_f32(1.5);
try std.testing.expect(!gf16_signbit(val));
}
test "gf16_signbit_negative" {
// Verify: negative values have signbit = true
const val = gf16_encode_f32(-1.5);
try std.testing.expect(gf16_signbit(val));
}
test "gf16_signbit_positive_zero" {
// Verify: positive zero has signbit = false
const zero_pos = GF16_ZERO_POS;
try std.testing.expect(!gf16_signbit(zero_pos));
}
test "gf16_signbit_negative_zero" {
// Verify: negative zero has signbit = true
const zero_neg = GF16_ZERO_NEG;
try std.testing.expect(gf16_signbit(zero_neg));
}
test "gf16_signbit_infinity" {
// Verify: signbit is set for negative infinity, not for positive
try std.testing.expect(!gf16_signbit(GF16_INF_POS));
try std.testing.expect(gf16_signbit(GF16_INF_NEG));
}
test "gf16_signbit_nan" {
// Verify: NaN can have signbit set or not (we check both cases)
// Most NaN implementations propagate signbit
const nan_with_sign = GF16_NAN | 0x8000;
try std.testing.expect(gf16_signbit(nan_with_sign));
}
test "gf16_sign_positive" {
// Verify: positive values return +1
const v1 = gf16_encode_f32(1.0);
const v2 = gf16_encode_f32(100.5);
try std.testing.expectEqual(@as(i8, 1), gf16_sign(v1));
try std.testing.expectEqual(@as(i8, 1), gf16_sign(v2));
}
test "gf16_sign_negative" {
// Verify: negative values return -1
const v1 = gf16_encode_f32(-1.0);
const v2 = gf16_encode_f32(-100.5);
try std.testing.expectEqual(@as(i8, -1), gf16_sign(v1));
try std.testing.expectEqual(@as(i8, -1), gf16_sign(v2));
}
test "gf16_sign_zero" {
// Verify: zero (positive or negative) returns 0
try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_ZERO_POS));
try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_ZERO_NEG));
}
test "gf16_sign_nan" {
// Verify: NaN returns 0 (IEEE 754 specifies sign of NaN is undefined)
try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_NAN));
}
test "gf16_sign_infinity" {
// Verify: positive infinity returns +1, negative returns -1
try std.testing.expectEqual(@as(i8, 1), gf16_sign(GF16_INF_POS));
try std.testing.expectEqual(@as(i8, -1), gf16_sign(GF16_INF_NEG));
}
test "gf16_sign_matches_signbit" {
// Verify: gf16_sign and gf16_signbit are consistent for non-zero values
const pos_val = gf16_encode_f32(5.5);
const neg_val = gf16_encode_f32(-5.5);
try std.testing.expect(!gf16_signbit(pos_val) and gf16_sign(pos_val) > 0);
try std.testing.expect(gf16_signbit(neg_val) and gf16_sign(neg_val) < 0);
}
test "gf16_clamp_in_range" {
// Verify: value within range is unchanged
const x = gf16_encode_f32(5.0);
const min_val = gf16_encode_f32(0.0);
const max_val = gf16_encode_f32(10.0);
const result = gf16_clamp(x, min_val, max_val);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(5.0, decoded, 0.1);
}
test "gf16_clamp_below_min" {
// Verify: value below min returns min
const x = gf16_encode_f32(-5.0);
const min_val = gf16_encode_f32(0.0);
const max_val = gf16_encode_f32(10.0);
const result = gf16_clamp(x, min_val, max_val);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(0.0, decoded, 0.1);
}
test "gf16_clamp_above_max" {
// Verify: value above max returns max
const x = gf16_encode_f32(15.0);
const min_val = gf16_encode_f32(0.0);
const max_val = gf16_encode_f32(10.0);
const result = gf16_clamp(x, min_val, max_val);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(10.0, decoded, 0.1);
}
test "gf16_clamp_with_nan" {
// Verify: NaN propagates
const x = GF16_NAN;
const min_val = gf16_encode_f32(0.0);
const max_val = gf16_encode_f32(10.0);
const result = gf16_clamp(x, min_val, max_val);
try std.testing.expect(gf16_is_nan(result));
}
test "gf16_lerp_t_zero" {
// Verify: lerp with t=0 returns a
const a = gf16_encode_f32(10.0);
const b = gf16_encode_f32(20.0);
const t = gf16_encode_f32(0.0);
const result = gf16_lerp(a, b, t);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(10.0, decoded, 0.1);
}
test "gf16_lerp_t_one" {
// Verify: lerp with t=1 returns b
const a = gf16_encode_f32(10.0);
const b = gf16_encode_f32(20.0);
const t = gf16_encode_f32(1.0);
const result = gf16_lerp(a, b, t);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(20.0, decoded, 0.1);
}
test "gf16_lerp_t_half" {
// Verify: lerp with t=0.5 returns midpoint
const a = gf16_encode_f32(0.0);
const b = gf16_encode_f32(10.0);
const t = gf16_encode_f32(0.5);
const result = gf16_lerp(a, b, t);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(5.0, decoded, 0.1);
}
test "gf16_lerp_with_nan" {
// Verify: NaN propagates
const a = GF16_NAN;
const b = gf16_encode_f32(20.0);
const t = gf16_encode_f32(0.5);
const result = gf16_lerp(a, b, t);
try std.testing.expect(gf16_is_nan(result));
}
test "gf16_fnma_basic" {
// Verify: fnma(a, b, c) = -(a*b) + c
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
const c = gf16_encode_f32(10.0);
const result = gf16_fnma(a, b, c);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-(2.0 * 3.0) + 10.0, decoded, 0.1); // = 4.0
}
test "gf16_fnma_zero_multiplier" {
// Verify: fnma with zero multiplier returns c
const a = gf16_encode_f32(0.0);
const b = gf16_encode_f32(3.0);
const c = gf16_encode_f32(10.0);
const result = gf16_fnma(a, b, c);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(10.0, decoded, 0.1);
}
test "gf16_fnma_zero_addend" {
// Verify: fnma with c=0 returns -(a*b)
const a = gf16_encode_f32(2.0);
const b = gf16_encode_f32(3.0);
const c = gf16_encode_f32(0.0);
const result = gf16_fnma(a, b, c);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(-(2.0 * 3.0), decoded, 0.1); // = -6.0
}
test "gf16_fnma_with_nan" {
// Verify: NaN propagates
const a = GF16_NAN;
const b = gf16_encode_f32(3.0);
const c = gf16_encode_f32(10.0);
const result = gf16_fnma(a, b, c);
try std.testing.expect(gf16_is_nan(result));
}
test "gf16_exp_zero" {
// Verify: e^0 = 1
const x = gf16_encode_f32(0.0);
const result = gf16_exp(x);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(1.0, decoded, 0.1);
}
test "gf16_exp_one" {
// Verify: e^1 ≈ 2.718
const x = gf16_encode_f32(1.0);
const result = gf16_exp(x);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(2.718, decoded, 0.1);
}
test "gf16_exp_negative" {
// Verify: e^-1 ≈ 0.368
const x = gf16_encode_f32(-1.0);
const result = gf16_exp(x);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(0.368, decoded, 0.05);
}
test "gf16_exp_large_positive" {
// Verify: e^88 is very large (returns Inf)
const x = gf16_encode_f32(88.0);
const result = gf16_exp(x);
try std.testing.expect(gf16_is_inf(result));
}
test "gf16_log_one" {
// Verify: ln(1) = 0
const x = gf16_encode_f32(1.0);
const result = gf16_log(x);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(0.0, decoded, 0.05);
}
test "gf16_log_e" {
// Verify: ln(e) ≈ 1
const e = gf16_encode_f32(2.71828);
const result = gf16_log(e);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(1.0, decoded, 0.1);
}
test "gf16_log_zero_or_negative" {
// Verify: ln(0) or ln(x<0) = NaN
const zero = gf16_encode_f32(0.0);
const neg = gf16_encode_f32(-1.0);
try std.testing.expect(gf16_is_nan(gf16_log(zero)));
try std.testing.expect(gf16_is_nan(gf16_log(neg)));
}
test "gf16_log2_eight" {
// Verify: log2(8) = 3
const x = gf16_encode_f32(8.0);
const result = gf16_log2(x);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(3.0, decoded, 0.1);
}
test "gf16_log10_ten" {
// Verify: log10(10) = 1
const x = gf16_encode_f32(10.0);
const result = gf16_log10(x);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(1.0, decoded, 0.1);
}
test "gf16_pow_two_cubed" {
// Verify: 2^3 = 8
const base = gf16_encode_f32(2.0);
const exp = gf16_encode_f32(3.0);
const result = gf16_pow(base, exp);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(8.0, decoded, 0.1);
}
test "gf16_pow_zero_to_zero" {
// Verify: 0^0 = 1 (by convention)
const base = gf16_encode_f32(0.0);
const exp = gf16_encode_f32(0.0);
const result = gf16_pow(base, exp);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(1.0, decoded, 0.01);
}
test "gf16_pow_any_to_zero" {
// Verify: x^0 = 1 for x != 0
const base = gf16_encode_f32(5.5);
const exp = gf16_encode_f32(0.0);
const result = gf16_pow(base, exp);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(1.0, decoded, 0.01);
}
test "gf16_pow_zero_to_positive" {
// Verify: 0^x = 0 for x > 0
const base = gf16_encode_f32(0.0);
const exp = gf16_encode_f32(2.0);
const result = gf16_pow(base, exp);
try std.testing.expect(gf16_is_zero(result));
}
test "gf16_pow_one_to_any" {
// Verify: 1^x = 1
const base = gf16_encode_f32(1.0);
const exp = gf16_encode_f32(5.0);
const result = gf16_pow(base, exp);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(1.0, decoded, 0.01);
}
test "gf16_sin_zero" {
// Verify: sin(0) = 0
const x = gf16_encode_f32(0.0);
const result = gf16_sin(x);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(0.0, decoded, 0.05);
}
test "gf16_sin_small_angle" {
// Verify: sin(π/6) ≈ 0.5
const pi_six = gf16_encode_f32(3.14159 / 6.0);
const result = gf16_sin(pi_six);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(0.5, decoded, 0.05);
}
test "gf16_cos_zero" {
// Verify: cos(0) = 1
const x = gf16_encode_f32(0.0);
const result = gf16_cos(x);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(1.0, decoded, 0.05);
}
test "gf16_cos_small_angle" {
// Verify: cos(π/6) ≈ 0.866
const pi_six = gf16_encode_f32(3.14159 / 6.0);
const result = gf16_cos(pi_six);
const decoded = gf16_decode_to_f32(result);
try std.testing.expectApproxEqAbs(0.866, decoded, 0.05);
}
test "gf16_trig_identity" {
// Verify: sin^2(x) + cos^2(x) ≈ 1 for small x
const x = gf16_encode_f32(0.5);
const sin_val = gf16_decode_to_f32(gf16_sin(x));
const cos_val = gf16_decode_to_f32(gf16_cos(x));
const sum = sin_val * sin_val + cos_val * cos_val;
try std.testing.expectApproxEqAbs(1.0, sum, 0.1);
}
// ============================================================================
// TDD - Benchmarks
// ============================================================================
bench "gf16_encode_throughput" {
// Measure: gf16_encode_f32 calls per second
// Target: > 10M encodes/sec on typical hardware
@setEvalBranchQuota(10000);
var result: GF16 = 0;
for (0..1000) |_| {
result = gf16_encode_f32(1.5);
}
_ = result;
}
bench "gf16_decode_throughput" {
// Measure: gf16_decode_to_f32 calls per second
// Target: > 10M decodes/sec on typical hardware
@setEvalBranchQuota(10000);
var result: f32 = 0;
for (0..1000) |_| {
result = gf16_decode_to_f32(0x3C00);
}
_ = result;
}
bench "gf16_roundtrip_latency" {
// Measure: encode + decode latency in nanoseconds
// Target: < 100ns for typical values
@setEvalBranchQuota(10000);
var result: f32 = 0;
const value: f32 = 1.5;
for (0..1000) |_| {
result = gf16_decode_to_f32(gf16_encode_f32(value));
}
_ = result;
}
bench "gf16_round_phi_latency" {
// Measure: nanoseconds to gf16_round_phi(1.0)
// Target: < 200ns
@setEvalBranchQuota(10000);
var result: GF16 = 0;
for (0..1000) |_| {
result = gf16_round_phi(1.0);
}
_ = result;
}
bench "gf16_extract_sign_latency" {
// Measure: nanoseconds to extract sign
// Target: < 20ns
@setEvalBranchQuota(10000);
var result: i8 = 0;
for (0..1000) |_| {
result = gf16_extract_sign(0x8000);
}
_ = result;
}
bench "gf16_extract_exponent_latency" {
// Measure: nanoseconds to extract exponent
// Target: < 20ns
@setEvalBranchQuota(10000);
var result: i8 = 0;
for (0..1000) |_| {
result = gf16_extract_exponent(0x3C00);
}
_ = result;
}
bench "gf16_is_inf_latency" {
// Measure: nanoseconds to check if infinity
// Target: < 20ns
@setEvalBranchQuota(10000);
var result: bool = false;
for (0..1000) |_| {
result = gf16_is_inf(0x7E00);
}
_ = result;
}
bench "gf16_is_nan_latency" {
// Measure: nanoseconds to check if NaN
// Target: < 20ns
@setEvalBranchQuota(10000);
var result: bool = false;
for (0..1000) |_| {
result = gf16_is_nan(0xFE01);
}
_ = result;
}
bench "gf16_negate_latency" {
// Measure: nanoseconds to negate
// Target: < 10ns (single XOR operation)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
for (0..1000) |_| {
result = gf16_negate(0x3C00);
}
_ = result;
}
bench "gf16_abs_latency" {
// Measure: nanoseconds to compute absolute value
// Target: < 10ns (single AND operation)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
for (0..1000) |_| {
result = gf16_abs(0xBC00);
}
_ = result;
}
bench "gf16_max_latency" {
// Measure: nanoseconds to compute max of two values
// Target: < 100ns (includes decode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3C00;
const b: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_max(a, b);
}
_ = result;
}
bench "gf16_min_latency" {
// Measure: nanoseconds to compute min of two values
// Target: < 100ns (includes decode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3C00;
const b: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_min(a, b);
}
_ = result;
}
bench "gf16_add_latency" {
// Measure: nanoseconds to add two values
// Target: < 200ns (includes decode + add + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D00;
const b: GF16 = 0x3C00;
for (0..1000) |_| {
result = gf16_add(a, b);
}
_ = result;
}
bench "gf16_sub_latency" {
// Measure: nanoseconds to subtract two values
// Target: < 200ns (includes decode + sub + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D00;
const b: GF16 = 0x3C00;
for (0..1000) |_| {
result = gf16_sub(a, b);
}
_ = result;
}
bench "gf16_mul_latency" {
// Measure: nanoseconds to multiply two values
// Target: < 200ns (includes decode + mul + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D00;
const b: GF16 = 0x3D80;
for (0..1000) |_| {
result = gf16_mul(a, b);
}
_ = result;
}
bench "gf16_div_latency" {
// Measure: nanoseconds to divide two values
// Target: < 300ns (includes decode + div + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D00;
const b: GF16 = 0x3C80;
for (0..1000) |_| {
result = gf16_div(a, b);
}
_ = result;
}
bench "gf16_sqrt_latency" {
// Measure: nanoseconds to compute square root
// Target: < 300ns (includes decode + sqrt + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_sqrt(a);
}
_ = result;
}
bench "gf16_fma_latency" {
// Measure: nanoseconds for fused multiply-add
// Target: < 300ns (fused operation, more accurate than separate)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D00;
const b: GF16 = 0x3C80;
const c: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_fma(a, b, c);
}
_ = result;
}
bench "gf16_square_latency" {
// Measure: nanoseconds to square a value
// Target: < 200ns (uses mul internally)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_square(a);
}
_ = result;
}
bench "gf16_eq_latency" {
// Measure: nanoseconds to compare equality
// Target: < 30ns (simple comparison with NaN check)
@setEvalBranchQuota(10000);
var result: bool = false;
const a: GF16 = 0x3C00;
const b: GF16 = 0x3C00;
for (0..1000) |_| {
result = gf16_eq(a, b);
}
_ = result;
}
bench "gf16_ne_latency" {
// Measure: nanoseconds to compare not-equal
// Target: < 30ns (negation of eq)
@setEvalBranchQuota(10000);
var result: bool = false;
const a: GF16 = 0x3C00;
const b: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_ne(a, b);
}
_ = result;
}
bench "gf16_lt_latency" {
// Measure: nanoseconds to compare less-than
// Target: < 50ns (includes decode)
@setEvalBranchQuota(10000);
var result: bool = false;
const a: GF16 = 0x3C00;
const b: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_lt(a, b);
}
_ = result;
}
bench "gf16_le_latency" {
// Measure: nanoseconds to compare less-than-or-equal
// Target: < 50ns (includes decode)
@setEvalBranchQuota(10000);
var result: bool = false;
const a: GF16 = 0x3C00;
const b: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_le(a, b);
}
_ = result;
}
bench "gf16_gt_latency" {
// Measure: nanoseconds to compare greater-than
// Target: < 50ns (includes decode)
@setEvalBranchQuota(10000);
var result: bool = false;
const a: GF16 = 0x3D00;
const b: GF16 = 0x3C00;
for (0..1000) |_| {
result = gf16_gt(a, b);
}
_ = result;
}
bench "gf16_ge_latency" {
// Measure: nanoseconds to compare greater-than-or-equal
// Target: < 50ns (includes decode)
@setEvalBranchQuota(10000);
var result: bool = false;
const a: GF16 = 0x3D00;
const b: GF16 = 0x3C00;
for (0..1000) |_| {
result = gf16_ge(a, b);
}
_ = result;
}
bench "gf16_floor_latency" {
// Measure: nanoseconds to compute floor
// Target: < 200ns (includes decode + floor + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D40;
for (0..1000) |_| {
result = gf16_floor(a);
}
_ = result;
}
bench "gf16_ceil_latency" {
// Measure: nanoseconds to compute ceil
// Target: < 200ns (includes decode + ceil + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D40;
for (0..1000) |_| {
result = gf16_ceil(a);
}
_ = result;
}
bench "gf16_round_latency" {
// Measure: nanoseconds to compute round
// Target: < 200ns (includes decode + round + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D40;
for (0..1000) |_| {
result = gf16_round(a);
}
_ = result;
}
bench "gf16_trunc_latency" {
// Measure: nanoseconds to compute trunc
// Target: < 200ns (includes decode + trunc + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D40;
for (0..1000) |_| {
result = gf16_trunc(a);
}
_ = result;
}
bench "gf16_fms_latency" {
// Measure: nanoseconds for fused multiply-subtract
// Target: < 300ns (fused operation)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D00;
const b: GF16 = 0x3C80;
const c: GF16 = 0x3C00;
for (0..1000) |_| {
result = gf16_fms(a, b, c);
}
_ = result;
}
bench "gf16_hypot_latency" {
// Measure: nanoseconds to compute hypotenuse
// Target: < 400ns (includes sqrt)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D80;
const b: GF16 = 0x3E00;
for (0..1000) |_| {
result = gf16_hypot(a, b);
}
_ = result;
}
bench "gf16_fmod_latency" {
// Measure: nanoseconds to compute modulo
// Target: < 300ns (includes decode + mod + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3F00;
const b: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_fmod(a, b);
}
_ = result;
}
bench "gf16_is_finite_latency" {
// Measure: nanoseconds to check if value is finite
// Target: < 30ns (simple bit checks)
@setEvalBranchQuota(10000);
var result: bool = false;
const val: GF16 = 0x3C00;
for (0..1000) |_| {
result = gf16_is_finite(val);
}
_ = result;
}
bench "gf16_is_normal_latency" {
// Measure: nanoseconds to check if value is normal
// Target: < 40ns (extraction + range check)
@setEvalBranchQuota(10000);
var result: bool = false;
const val: GF16 = 0x3C00;
for (0..1000) |_| {
result = gf16_is_normal(val);
}
_ = result;
}
bench "gf16_is_subnormal_latency" {
// Measure: nanoseconds to check if value is subnormal
// Target: < 40ns (extraction + mantissa check)
@setEvalBranchQuota(10000);
var result: bool = false;
const val: GF16 = 0x0001;
for (0..1000) |_| {
result = gf16_is_subnormal(val);
}
_ = result;
}
bench "gf16_signbit_latency" {
// Measure: nanoseconds to check sign bit
// Target: < 5ns (single bit test)
@setEvalBranchQuota(10000);
var result: bool = false;
const val: GF16 = 0x8000;
for (0..1000) |_| {
result = gf16_signbit(val);
}
_ = result;
}
bench "gf16_sign_latency" {
// Measure: nanoseconds to get sign value
// Target: < 30ns (includes zero/nan/inf checks)
@setEvalBranchQuota(10000);
var result: i8 = 0;
const val: GF16 = 0xBC00;
for (0..1000) |_| {
result = gf16_sign(val);
}
_ = result;
}
bench "gf16_clamp_latency" {
// Measure: nanoseconds to clamp value to range
// Target: < 200ns (includes decode + compare + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const x: GF16 = 0x3F00;
const min_val: GF16 = 0x3C00;
const max_val: GF16 = 0x4800;
for (0..1000) |_| {
result = gf16_clamp(x, min_val, max_val);
}
_ = result;
}
bench "gf16_lerp_latency" {
// Measure: nanoseconds to compute linear interpolation
// Target: < 300ns (includes decode + computation + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3C00;
const b: GF16 = 0x4800;
const t: GF16 = 0x3C00;
for (0..1000) |_| {
result = gf16_lerp(a, b, t);
}
_ = result;
}
bench "gf16_fnma_latency" {
// Measure: nanoseconds for fused negative multiply-add
// Target: < 300ns (fused operation)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const a: GF16 = 0x3D00;
const b: GF16 = 0x3C80;
const c: GF16 = 0x3C00;
for (0..1000) |_| {
result = gf16_fnma(a, b, c);
}
_ = result;
}
bench "gf16_exp_latency" {
// Measure: nanoseconds to compute exponential
// Target: < 500ns (Taylor series)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const x: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_exp(x);
}
_ = result;
}
bench "gf16_log_latency" {
// Measure: nanoseconds to compute natural log
// Target: < 300ns (includes decode + log + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const x: GF16 = 0x3E00;
for (0..1000) |_| {
result = gf16_log(x);
}
_ = result;
}
bench "gf16_pow_latency" {
// Measure: nanoseconds to compute power
// Target: < 400ns (includes decode + pow + encode)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const base: GF16 = 0x3D00;
const exp: GF16 = 0x3D80;
for (0..1000) |_| {
result = gf16_pow(base, exp);
}
_ = result;
}
bench "gf16_sin_latency" {
// Measure: nanoseconds to compute sine
// Target: < 500ns (Taylor series)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const x: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_sin(x);
}
_ = result;
}
bench "gf16_cos_latency" {
// Measure: nanoseconds to compute cosine
// Target: < 500ns (Taylor series)
@setEvalBranchQuota(10000);
var result: GF16 = 0;
const x: GF16 = 0x3D00;
for (0..1000) |_| {
result = gf16_cos(x);
}
_ = result;
}
// =====================================================================
// Phase C3 (epic #181) -- GF16 1-6-9 split under FP8 low-precision (appended)
// L6 NOTE: append-only; FORMAT-SPEC-001.json and existing gf16 consts UNCHANGED.
// =====================================================================
pub const FP8_EFFECTIVE_MANT_BITS : u8 = 4;
// GF16 canonical split (registered in FORMAT-SPEC-001 -- do not change).
pub const GF16_CANONICAL_EXP_BITS : u8 = 6;
pub const GF16_CANONICAL_MANT_BITS : u8 = 9;
// Alternate split A: 1-7-8 (ablation only -- NOT registered).
pub const ALT_A_EXP_BITS : u8 = 7;
pub const ALT_A_MANT_BITS : u8 = 8;
// Alternate split B: 1-8-7 (ablation only -- NOT registered).
pub const ALT_B_EXP_BITS : u8 = 8;
pub const ALT_B_MANT_BITS : u8 = 7;
// phi_dist of the canonical 1-6-9 split (from FORMAT-SPEC-001.json v1.1).
// [Registered fact -- not a conjecture; do not modify.]
pub const GF16_PHI_DIST_REGISTERED : f64 = 0.0486326415435630;
// phi_dist of alternate splits (computed, not registered).
// phi_dist = |exp_bits/mant_bits - phi^-1|
// phi^-1 ~ 0.6180339887498949
pub const ALT_A_PHI_DIST : f64 = 0.2569660112501051; // |7/8 - phi^-1| = |0.875 - 0.618034|
pub const ALT_B_PHI_DIST : f64 = 0.5248231541072479; // |8/7 - phi^-1| = |1.142857 - 0.618034|
// FP8 effective ULP at the 4-bit mantissa level (1/2^4 = 0.0625).
pub const FP8_ULP : f64 = 0.0625;
// ============================================================================
// C3 Tests (L4 TESTABILITY)
// ============================================================================
test "gf16_lp_ssot_split_constants" {
// Verify that the canonical GF16 split constants are unchanged (L6 guard).
// These must match FORMAT-SPEC-001.json v1.1 and the existing gf16.t27 constants.
try std.testing.expect(EXP_SHIFT == 9); // EXP_SHIFT = 9 from gf16.t27
try std.testing.expect(SIGN_SHIFT == 15); // SIGN_SHIFT = 15
try std.testing.expect(EXP_MASK == 0x7E00); // 6 exp bits
try std.testing.expect(MANT_MASK == 0x01FF); // 9 mant bits
try std.testing.expect(BIAS == 31); // bias = 31
// Canonical bit counts match registered values.
try std.testing.expect(GF16_CANONICAL_EXP_BITS == 6);
try std.testing.expect(GF16_CANONICAL_MANT_BITS == 9);
}
test "gf16_lp_phi_dist_registered" {
// The phi_dist of the canonical 1-6-9 split is the FORMAT-SPEC-001 value.
// phi_dist = |exp_bits/mant_bits - phi^-1| = |6/9 - phi^-1|
// = |0.6667 - 0.6180| = 0.04863...
// This matches the registered value to 4 significant figures.
const ratio_canonical : f64 = 6.0 / 9.0; // ~ 0.6667
const phi_dist_canonical = if (ratio_canonical > PHI_INV)
ratio_canonical - PHI_INV
else
PHI_INV - ratio_canonical;
const d = phi_dist_canonical - GF16_PHI_DIST_REGISTERED;
const d_abs = if (d < 0.0) -d else d;
try std.testing.expect(d_abs < 1e-4);
}
test "gf16_lp_alternate_split_phi_dist_larger" {
// The canonical 1-6-9 has smaller phi_dist than both alternates.
// This is the phi-motivation for the 1-6-9 split [Open conjecture: this
// structural closeness to phi^-1 is hypothesised to benefit training,
// but that hypothesis is what C3 tests].
const ratio_canonical : f64 = 6.0 / 9.0;
const ratio_alt_a : f64 = 7.0 / 8.0; // 0.875
const ratio_alt_b : f64 = 8.0 / 7.0; // ~1.143
fn phi_dist(r: f64) f64 {
const d = r - PHI_INV;
return if (d < 0.0) -d else d;
}
const pd_canonical = phi_dist(ratio_canonical);
const pd_alt_a = phi_dist(ratio_alt_a);
const pd_alt_b = phi_dist(ratio_alt_b);
// Canonical 1-6-9 has minimum phi_dist (registered structural fact).
try std.testing.expect(pd_canonical < pd_alt_a);
try std.testing.expect(pd_canonical < pd_alt_b);
}
test "gf16_lp_fp8_effective_precision" {
// Under FP8 (4-bit effective mantissa), test round-trip error for GF16
// vs a simulated 1-7-8 alternate split on a representative value set.
//
// Method: encode a value to GF16, truncate the mantissa to FP8_EFFECTIVE_MANT_BITS,
// decode, compare error. Then simulate the 1-7-8 split (8 mant bits ->
// truncate to 4 bits), compare.
//
// If both errors are within 1 FP8 ULP of each other, the 9-bit mantissa
// provides no measurable advantage in the FP8 regime.
//
// Test values: 1.0, phi, 0.5, 2.0 (representative small set).
const test_vals : [4]f32 = [1.0, 1.618034, 0.5, 2.0];
for (test_vals) |v| {
// Encode to GF16 (full precision, 9-bit mantissa).
const gf16_encoded = gf16_encode_f32(v);
// Extract mantissa bits.
const mant_full = gf16_encoded & MANT_MASK;
// Truncate to FP8 effective depth (keep top FP8_EFFECTIVE_MANT_BITS bits).
// FP8_EFFECTIVE_MANT_BITS = 4; MANT_MASK = 9 bits; shift = 9 - 4 = 5.
const trunc_shift : u4 = GF16_CANONICAL_MANT_BITS - FP8_EFFECTIVE_MANT_BITS;
const mant_truncated = (mant_full >> trunc_shift) << trunc_shift;
// Reconstruct GF16 with truncated mantissa.
const gf16_truncated = (gf16_encoded & ~MANT_MASK) | mant_truncated;
// Decode truncated GF16.
const decoded_trunc = gf16_decode_to_f32(gf16_truncated);
// Compute error.
const err_val = decoded_trunc - v;
const err_abs = if (err_val < 0.0) -err_val else err_val;
// Error must be bounded by 2 FP8 ULPs (loose bound -- tight bound is 1 ULP).
// A tighter bound would require comparing against the 1-7-8 split directly.
try std.testing.expect(err_abs < @as(f32, @floatCast(FP8_ULP * 2.0)));
}
}
test "gf16_lp_fp8_canonical_vs_alt_error_comparison" {
// Compare GF16 1-6-9 vs simulated 1-7-8 truncated error on phi.
// 1-7-8 simulated: encode with gf16_encode_f32 (same hardware),
// then truncate 8 mant bits to 4 bits (shift = 8 - 4 = 4).
// The alt split has 1 fewer mantissa bit at full precision but
// the same FP8 truncation floor.
//
// Prediction [Open conjecture]: at FP8 floor, both splits suffer
// similar truncation error, making the 9-bit depth vacuous in
// this context. If verified, this FALSIFIES the mantissa-depth
// motivation for 1-6-9 under FP8 training.
const v : f32 = 1.6180339; // phi to f32 precision
// Canonical GF16 1-6-9 path.
const gf16_enc = gf16_encode_f32(v);
const mant_9 = gf16_enc & MANT_MASK;
const shift_canonical : u4 = 9 - FP8_EFFECTIVE_MANT_BITS; // 5
const mant_9_trunc = (mant_9 >> shift_canonical) << shift_canonical;
const gf16_trunc = (gf16_enc & ~MANT_MASK) | mant_9_trunc;
const decoded_canonical = gf16_decode_to_f32(gf16_trunc);
const err_canonical = if (decoded_canonical > v) decoded_canonical - v else v - decoded_canonical;
// Alternate 1-7-8 path simulation: same encoding hardware; treat the
// lower 8 bits as the mantissa (hypothetical), truncate to 4 bits.
const mant_8 = gf16_enc & 0x00FF; // lower 8 bits (hypothetical 1-7-8 mant)
const shift_alt : u4 = 8 - FP8_EFFECTIVE_MANT_BITS; // 4
const mant_8_trunc = (mant_8 >> shift_alt) << shift_alt;
const gf16_alt = (gf16_enc & 0xFF00) | mant_8_trunc;
const decoded_alt = gf16_decode_to_f32(gf16_alt);
const err_alt = if (decoded_alt > v) decoded_alt - v else v - decoded_alt;
// Check: neither split dominates by more than 1 FP8 ULP.
const diff = if (err_canonical > err_alt) err_canonical - err_alt else err_alt - err_canonical;
// NOTE: if diff > FP8_ULP, one split is measurably better in this context.
// The test records the comparison; it does NOT assert which is better.
// The parent agent must check the actual run result to evaluate the conjecture.
const within_one_ulp = diff < @as(f32, @floatCast(FP8_ULP));
// The result of this test is informational: pass in either direction.
// (A strict > assertion here would pre-judge the conjecture.)
_ = within_one_ulp;
// Structural sanity: errors are finite and non-negative.
try std.testing.expect(err_canonical >= 0.0);
try std.testing.expect(err_alt >= 0.0);
}
// ============================================================================
// C3 Invariants (L4 TESTABILITY)
// ============================================================================
invariant "gf16_lp_ssot_split_unchanged" {
// L6 guard: the canonical split constants must not be altered.
// EXP_SHIFT, EXP_MASK, MANT_MASK, BIAS are the SSOT (FORMAT-SPEC-001).
@compileAssert(EXP_SHIFT == 9);
@compileAssert(EXP_MASK == 0x7E00);
@compileAssert(MANT_MASK == 0x01FF);
@compileAssert(BIAS == 31);
@compileAssert(GF16_CANONICAL_EXP_BITS == 6);
@compileAssert(GF16_CANONICAL_MANT_BITS == 9);
}
invariant "gf16_lp_phi_dist_registered_positive" {
// The registered phi_dist of GF16 is positive (it is not exactly phi^-1).
@compileAssert(GF16_PHI_DIST_REGISTERED > 0.0);
}
invariant "gf16_lp_fp8_depth_less_than_canonical" {
// FP8 effective mantissa depth is strictly less than GF16 canonical depth.
@compileAssert(FP8_EFFECTIVE_MANT_BITS < GF16_CANONICAL_MANT_BITS);
}
invariant "gf16_lp_alt_splits_not_registered" {
// Alternate splits (1-7-8, 1-8-7) are ablation artifacts only.
// They are not in FORMAT-SPEC-001 and must not be confused with the
// canonical format. The ablation is additive and read-only w.r.t. L6.
@compileAssert(ALT_A_EXP_BITS != GF16_CANONICAL_EXP_BITS);
@compileAssert(ALT_A_MANT_BITS != GF16_CANONICAL_MANT_BITS);
}
invariant "gf16_lp_canonical_minimises_phi_dist" {
// [Registered structural fact from FORMAT-SPEC-001]
// phi_dist(1-6-9) < phi_dist(1-7-8); the canonical split is closer to phi^-1.
// This is a STRUCTURAL fact, not a training quality claim.
@compileAssert(GF16_PHI_DIST_REGISTERED < ALT_A_PHI_DIST);
}
// ============================================================================
// C3 Bench (L4 TESTABILITY)
// ============================================================================
bench "bench_gf16_lp_encode_fp8_projection" {
// Latency of: encode f32 to GF16, truncate mantissa to 4 bits (FP8 floor),
// decode back. This is the inner loop cost of FP8-projected GF16 operations.
// Target: < 2x overhead vs plain gf16_encode_f32 + gf16_decode_to_f32.
@setEvalBranchQuota(10000);
const v : f32 = 1.5;
const fp8_shift : u4 = GF16_CANONICAL_MANT_BITS - FP8_EFFECTIVE_MANT_BITS;
var result : f32 = 0.0;
for (0..1000) |_| {
const enc = gf16_encode_f32(v);
const mant = enc & MANT_MASK;
const mant_trunc = (mant >> fp8_shift) << fp8_shift;
const enc_trunc = (enc & ~MANT_MASK) | mant_trunc;
result = gf16_decode_to_f32(enc_trunc);
}
_ = result;
}
bench "bench_gf16_lp_roundtrip_baseline" {
// Plain GF16 encode + decode (no FP8 truncation) for latency comparison.
// The ratio bench_gf16_lp_encode_fp8_projection / bench_gf16_lp_roundtrip_baseline
// must be < 2.0 (the MAX_LATENCY_RATIO from C2 convention).
@setEvalBranchQuota(10000);
const v : f32 = 1.5;
var result : f32 = 0.0;
for (0..1000) |_| {
const enc = gf16_encode_f32(v);
result = gf16_decode_to_f32(enc);
}
_ = result;
}
Все уроки
Модуль 1 · Правило и его числа
Одно правило делит каждую ширину, отношение, к которому оно стремится, и числа Люка за тройкой 3.
Модуль 2 · Почему phi, почему три
Почему деление идёт по phi, почему основание три и как спека проверяет, что GF16 хранит phi.
Модуль 3 · Малые ступени: от GF4 до GF8
GF4, GF6 и GF8 — меньше всего битов, и округление до целых битов стоит здесь дороже всего.
Модуль 4 · От десяти до четырнадцати битов
GF10, GF12 и GF14 и то, как расстояние до 1 / phi меняется с ростом слова.
Модуль 5 · GF16 в работе
Основной 16-битный формат, скалярное произведение из двух слагаемых в GF-T16, затем GF20 и GF24.
Модуль 6 · От GF32 до GF64
GF32 рядом с IEEE single, GF48 без пары в IEEE, GF64 рядом с IEEE double.
Модуль 7 · От GF96 до GF256
GF96, GF128 и GF256, где спеки держат раскладку инвариантами.
Модуль 8 · Самые широкие ступени, затем триты
GF512 и GF1024, две самые широкие ступени, затем GF-T8, где порядок уходит в триты.
Модуль 9 · Ещё триты, затем декодирование
GF-T16 и GF-T32, затем почему фиксированные поля декодируются параллельно, а posit — нет.