Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions specs/TRI-HASHES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

| Gene | Level | File | Version | SHA256 | Last Modified |
|------|-------|------|---------|--------|---------------|
| **GF16** | 1 | `specs/gf16.tri` | 1 | `23f2353746e083ad236cd348ddc4152d5976cbbc20d35d23b27c5300ef07e70a` | 2026-03-31 |
| **GF16** | 1 | `specs/gf16.tri` | 1 | `ac35405ca9cb0b7389e21ce920392a379321b2716e667d65e47a31bbceaf7dc2` | 2026-08-08 |
| **TF3** | 2 | `specs/tf3.tri` | 1 | `b482b1f829a8c856022077a26fd6bafa864eecda4c696da058083cea52a8e19c` | 2026-03-31 |
| **OPS** | 1 | `specs/ops.tri` | 1 | `750e57342e5ea8b52135c9cc36d9a70becba532566160be1dc825ffb5571ff95` | 2026-03-31 |
| **OPS** | 1 | `specs/ops.tri` | 1 | `43ccb0bda4d75de1edcdfc78e0d35a367b2902936a56aac3d344548a8f38702f` | 2026-08-08 |

## Hash Calculation

Expand Down
10 changes: 5 additions & 5 deletions specs/gf16.tri
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,13 @@ test_vectors:
raw_hex: "0000"
- name: one
f32: 1.0
raw_hex: "3c00"
raw_hex: "3e00"
- name: minus_one
f32: -1.0
raw_hex: "bc00"
raw_hex: "be00"
- name: pi
f32: 3.1415927
raw_hex: "4248"
raw_hex: "4124"
- name: max_pos
f32: 4.3e9
raw_hex: "7bff"
f32: 4.29e9
raw_hex: "7dff"
4 changes: 2 additions & 2 deletions specs/ops.tri
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ gf16:
intermediate_type: f32
steps:
- extract_sign_bit
- normalize_to_0_5_to_1_range
- normalize_to_1_to_2_range
- compute_exponent_with_bias_31
- round_mantissa_to_9_bits
- pack_into_u16
Expand All @@ -34,7 +34,7 @@ gf16:
- unpack_fields
- check_special_cases
- unbiased_exponent = exp - 31
- mantissa_scaled = mant / 512 + 0.5
- mantissa_scaled = 1 + mant / 512
- value = mantissa_scaled * 2^unbiased_exponent
- apply_sign

Expand Down
2 changes: 1 addition & 1 deletion src/c/gf16.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ extern "C" {
* [8:0] Mantissa (9 bits, fractional part)
*
* **Value Formula:**
* value = (-1)^sign × (0.5 + mant/512) × 2^(exp - 31)
* value = (-1)^sign × (1 + mant/512) × 2^(exp - 31)
*
* **Special Values:**
* - exp=0, mant=0: Zero (signed by sign bit)
Expand Down
5 changes: 5 additions & 0 deletions src/formats/gf_binary.zig
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ test "GF ladder: exact-bit golden vectors (encoding regression guard)" {
try E(@as(GF12.Repr, 0x3C0), GF12.fromF32(1.5).bits_());
try E(@as(GF12.Repr, 0x400), GF12.fromF32(2.0).bits_());
try E(@as(GF12.Repr, 0xC20), GF12.fromF32(-2.5).bits_());
// gf16 [1:6:9] b31 — the primary production rung (shared with golden_float16.GF16)
try E(@as(GF16.Repr, 0x3E00), GF16.fromF32(1.0).bits_());
try E(@as(GF16.Repr, 0x3F00), GF16.fromF32(1.5).bits_());
try E(@as(GF16.Repr, 0x4000), GF16.fromF32(2.0).bits_());
try E(@as(GF16.Repr, 0xC080), GF16.fromF32(-2.5).bits_());
// gf20 [1:7:12] b63
try E(@as(GF20.Repr, 0x3F000), GF20.fromF32(1.0).bits_());
try E(@as(GF20.Repr, 0x3F800), GF20.fromF32(1.5).bits_());
Expand Down
82 changes: 39 additions & 43 deletions src/formats/golden_float16.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
//!

const std = @import("std");
const gf_binary = @import("gf_binary.zig");

// ═════════════════════════════════════════════════════════════════════════
// TRINITY CONSTANTS
Expand Down Expand Up @@ -84,53 +85,19 @@ pub const GF16 = packed struct(u16) {
/// comptime calculation (0.049 for GF16)
// pub const phi_distance: comptime_float = @import("std").math.fabs(6.0 / 9.0 - 1.0 / PHI);

/// Create GF16 from f32 with φ-optimized encoding
/// Create GF16 from f32.
///
/// Delegates to the single normative codec `gf_binary.GF16` (the φ²-sized
/// binary rung factory) so GF16 has exactly ONE encoding across the repo:
/// the standard `(1 + M/512)·2^(E−31)` significand with the full 9-bit
/// mantissa (FORMAT-SPEC-001, specs/gf16.tri). e.g. 1.0 → 0x3E00.
pub fn fromF32(v: f32) GF16 {
if (v == 0.0) return .{ .mant = 0, .exp = 0, .sign = 0 };

if (std.math.isNan(v)) {
return .{ .mant = 1, .exp = 0x3F, .sign = 0 };
}

if (std.math.isInf(v)) {
return .{ .mant = 0, .exp = 0x3F, .sign = @intFromBool(v < 0) };
}

const sign_bit: u1 = @intFromBool(v < 0);
const abs_v = @abs(v);

// Find exponent (normalize to [0.5, 2))
var exp: i8 = 0;
var mant_f = abs_v;

while (mant_f >= 1.0 and exp < 31) : (exp += 1) mant_f /= 2.0;
while (mant_f < 0.5 and exp > -32) : (exp -= 1) mant_f *= 2.0;

const exp_i8: i8 = exp;
const exp_bias: i8 = 31;
const exp_u6: u6 = @intCast(exp_bias + exp_i8);
const mant_u9: u9 = @intFromFloat((mant_f - 0.5) * 512.0);

return .{
.mant = @min(mant_u9, 511),
.exp = exp_u6,
.sign = sign_bit,
};
return @bitCast(gf_binary.GF16.fromF32(v).bits_());
}

/// Convert GF16 to f32
/// Convert GF16 to f32 (via the same normative `gf_binary.GF16` codec).
pub fn toF32(self: GF16) f32 {
if (self.exp == 0 and self.mant == 0) {
return if (self.sign == 1) -0.0 else 0.0;
}
if (self.exp == 0x3F) {
return if (self.sign == 1) -std.math.inf(f32) else std.math.inf(f32);
}

const exp_unbiased = @as(i32, self.exp) - 31;
const mant_f = 0.5 + @as(f32, @floatFromInt(self.mant)) / 512.0;
const value = mant_f * std.math.pow(f32, 2.0, @floatFromInt(exp_unbiased));
return if (self.sign == 1) -value else value;
return gf_binary.GF16.fromBits(@bitCast(self)).toF32();
}

/// GF16 addition (via f32 for precision)
Expand Down Expand Up @@ -377,6 +344,35 @@ test "GF16 roundtrip negative" {
}
}

test "GF16 exact-bit encoding is the standard (1 + M/512) form" {
// Pin the wire format: 1.0 -> 0x3E00 (E=31, mantissa 0), NOT the old
// waste-a-bit 0x4000. Full 9-bit mantissa is now reachable. See specs/gf16.tri
// and testdata/gf_conformance.csv (gf16 rows).
const E = std.testing.expectEqual;
try E(@as(u16, 0x3E00), @as(u16, @bitCast(GF16.fromF32(1.0))));
try E(@as(u16, 0x3F00), @as(u16, @bitCast(GF16.fromF32(1.5))));
try E(@as(u16, 0x4000), @as(u16, @bitCast(GF16.fromF32(2.0))));
try E(@as(u16, 0x4100), @as(u16, @bitCast(GF16.fromF32(3.0))));
try E(@as(u16, 0x3C00), @as(u16, @bitCast(GF16.fromF32(0.5))));
try E(@as(u16, 0xBE00), @as(u16, @bitCast(GF16.fromF32(-1.0))));
try E(@as(u16, 0xC080), @as(u16, @bitCast(GF16.fromF32(-2.5))));
}

test "GF16 is bit-identical to the normative gf_binary.GF16 codec" {
// One implementation: golden_float16.GF16 delegates to gf_binary.GF16, so
// every value must encode to the exact same raw u16. Guards against re-drift.
const vals = [_]f32{ 0.0, 1.0, -1.0, 0.5, 1.5, 2.0, 3.0, -2.5, 3.14159, 100.0, 0.001, 12345.0, 1e30, -1e30 };
for (vals) |v| {
const a: u16 = @bitCast(GF16.fromF32(v));
const b: u16 = gf_binary.GF16.fromF32(v).bits_();
try std.testing.expectEqual(b, a);
}
// NaN encodes consistently too (exp all-ones, mantissa != 0).
const na: u16 = @bitCast(GF16.fromF32(std.math.nan(f32)));
const nb: u16 = gf_binary.GF16.fromF32(std.math.nan(f32)).bits_();
try std.testing.expectEqual(nb, na);
}

test "GF16 arithmetic" {
const a = GF16.fromF32(1.5);
const b = GF16.fromF32(2.5);
Expand Down
14 changes: 14 additions & 0 deletions testdata/gf_conformance.csv
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ gf12,0.0,0x0
gf12,-0.0,0x800
gf12,1e+30,0x780
gf12,1e-30,0x0
gf16,1.0,0x3e00
gf16,1.5,0x3f00
gf16,2.0,0x4000
gf16,3.0,0x4100
gf16,0.5,0x3c00
gf16,-1.0,0xbe00
gf16,-2.5,0xc080
gf16,inf,0x7e00
gf16,-inf,0xfe00
gf16,nan,0x7e01
gf16,0.0,0x0
gf16,-0.0,0x8000
gf16,1e+30,0x7e00
gf16,1e-30,0x0
gf20,1.0,0x3f000
gf20,1.5,0x3f800
gf20,2.0,0x40000
Expand Down
Loading