GF64: a double
You will learn
The 64-bit GoldenFloat, 1 + 24 + 39, beside IEEE double, and how its encoder clamps the exponent.
GF64 has 64 bits: 1 sign, 24 exponent, 39 mantissa, bias 8388607. IEEE double spends 11 bits on the exponent and 52 on the mantissa; GF64 trades mantissa for a far wider range. Its spec has an encoder and a decoder: the mantissa divides by 2^39, and the exponent is clamped between 0 and 2^24 - 1.
Try it
Find EXP_BIAS, EXP_MAX and MANT_DIV in gf64.t27. Then open encode and find where the exponent is clamped.

GF64: 64 bits as the spec lays them out, read from gf64.t27. Lesson 18 of the GoldenFloat course.
specs/numeric/gf64.t27
// SPDX-License-Identifier: Apache-2.0
// t27/specs/numeric/gf64.t27
// GoldenFloat64 — 64-bit phi-structured floating point
// NUMERIC-STANDARD-001 — canonical double-precision member of the GoldenFloat family
module GF64 {
// Import base format family + phi-ratio design rule
use numeric::goldenfloat_family;
use numeric::phi_ratio;
// 1. Format Definition
// GF64 bit layout: [S(1) | E(24) | M(39)]
// S: 1 bit (sign)
// E: 24 bits (exponent)
// M: 39 bits (mantissa)
// Split chosen by the family rule E/M -> 1/phi: 24/39 = 0.6154 (closest
// split to 1/phi on the whole ladder; phi_distance = 0.00265).
const BITS : u8 = 64;
const SIGN_BITS : u8 = 1;
const EXP_BITS : u8 = 24;
const MANT_BITS : u8 = 39;
// Bias for exponent: 2^(24-1) - 1 = 8388607
const EXP_BIAS : u32 = 8388607;
// phi-rounding bias (empirical per format, H_E; see docs/NUMERIC_FORMATS_SSOT.md).
// For GF64 this equals EXP_MAX - BIAS = 16777215 - 8388607 = 8388608 = 2^23.
const PHI_BIAS : u32 = 8388608;
// phi-ratio: exp/mant = 24/39 ~ 0.6154 (phi_distance = 0.00265 — best on the ladder)
const PHI_DISTANCE : f64 = 0.0026493553478736;
// Derived masks
const EXP_MAX : u32 = 16777215; // 2^24 - 1
const MANT_DIV : f64 = 549755813888.0; // 2^39
const MANT_MASK : u64 = 549755813887; // 2^39 - 1
const EXP_FIELD_MASK : u64 = 16777215; // (1<<24) - 1
// 2. GoldenFloat64 Type
struct GF64 {
raw : u64, // 64-bit raw value: [sign:1][exp:24][mant:39]
}
// 3. Encoding / Decoding
fn encode(value: f64) -> GF64 {
if (value == 0.0) {
return GF64{ raw = 0 };
}
const sign = if (value < 0.0) { 1u64 } else { 0u64 };
const abs_val = if (value < 0.0) { -value } else { value };
const exp_unbiased = floor_log2(abs_val) as i32;
const exp_biased = (exp_unbiased + EXP_BIAS as i32) as u32;
const exp_clamped = clamp_u32(exp_biased, 0, EXP_MAX);
const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS);
return GF64{
raw = (sign << 63) |
((exp_clamped as u64) << MANT_BITS) |
(mant as u64)
};
}
fn decode(gf: GF64) -> f64 {
const sign = (gf.raw >> 63) as u8;
const exp_biased = ((gf.raw >> MANT_BITS) & EXP_FIELD_MASK) as u32;
const mant = (gf.raw & MANT_MASK) as u64;
if (exp_biased == 0 && mant == 0) {
return 0.0;
}
const exp_unbiased = if (exp_biased == 0) {
-(EXP_BIAS as i32) + 1
} else {
(exp_biased as i32) - EXP_BIAS as i32
};
const mant_normalized = if (exp_biased == 0) {
(mant as f64) / MANT_DIV
} else {
1.0 + (mant as f64) / MANT_DIV
};
const value = mant_normalized * pow2(exp_unbiased);
if (sign != 0) { return -value; }
return value;
}
// 4. Format Properties
fn max_value() -> f64 {
const mant_max = 1.0 + (MANT_MASK as f64) / MANT_DIV;
const exp_max = (EXP_MAX as i32) - (EXP_BIAS as i32);
return mant_max * pow2(exp_max);
}
fn min_positive() -> f64 {
const mant_min = 1.0 / MANT_DIV;
const exp_min = -(EXP_BIAS as i32) + 1;
return mant_min * pow2(exp_min);
}
fn epsilon() -> f64 {
return 1.0 / MANT_DIV;
}
// Memory: 64 bits = 8 bytes (same as IEEE binary64)
const MEMORY_RATIO_VS_FP64 : f64 = 1.0;
// 5. Validation against the family descriptor
fn validate_format() -> bool {
const fmt = goldenfloat_family::get_format_by_name("GF64");
return (fmt != null) &&
(fmt.?.bits == BITS) &&
(fmt.?.exp_bits == EXP_BITS) &&
(fmt.?.mant_bits == MANT_BITS);
}
// 6. Use Cases
// GF64 is the double-precision member of the family:
// - 24-bit exponent (vs IEEE binary64's 11-bit) -> far wider dynamic range
// - 39-bit mantissa (vs IEEE's 52-bit) -> coarser precision, traded for range
// - Same 8-byte footprint as IEEE binary64, but phi-optimal E:M split
// - Target: scientific / double-precision workloads on phi-structured silicon
// Comparison with IEEE binary64:
// IEEE: 1 + 11 + 52, exp/mant = 0.212 (phi_distance = 0.406)
// GF64: 1 + 24 + 39, exp/mant = 0.615 (phi_distance = 0.00265)
// 7. Helpers (family-standard; integer-backed)
fn floor_log2(x: f64) -> i32 {
if (x <= 0.0) { return -2147483648; }
let exp : i32 = 0;
while (x >= 2.0) { x = x / 2.0; exp = exp + 1; }
while (x < 1.0) { x = x * 2.0; exp = exp - 1; }
return exp;
}
fn extract_mantissa(value: f64, exp: i32, mant_bits: u8) -> u64 {
const normalized = value / pow2(exp);
const frac = normalized - 1.0;
const max_mant = (1u64 << mant_bits) - 1;
return (frac * (max_mant as f64 + 1.0)) as u64;
}
fn clamp_u32(x: u32, min: u32, max: u32) -> u32 {
if (x < min) { return min; }
if (x > max) { return max; }
return x;
}
fn pow2(e: i32) -> f64 {
let result = 1.0;
let n = if (e < 0) { -e } else { e };
let acc = 2.0;
let k = n;
while (k > 0) {
if (k % 2 == 1) { result = result * acc; }
acc = acc * acc; k = k / 2;
}
if (e < 0) { return 1.0 / result; }
return result;
}
// TDD-Inside-Spec: tests + invariants
test gf64_decode_zero
given gf = GF64{ raw = 0 }
when value = decode(gf)
then value == 0.0
test gf64_encode_zero_roundtrip
given original = 0.0
and encoded = encode(original)
and decoded = decode(encoded)
then decoded == original
test gf64_bits_sum_correct
given total = SIGN_BITS + EXP_BITS + MANT_BITS
then total == BITS
test gf64_bias_formula
given computed = (1u32 << (EXP_BITS - 1)) - 1
then computed == EXP_BIAS
test gf64_exp_max_formula
given computed = (1u32 << EXP_BITS) - 1
then computed == EXP_MAX
test gf64_phi_bias_equals_exp_max_minus_bias
// GF64 is the one format where PHI_BIAS coincides with EXP_MAX - BIAS
given computed = EXP_MAX - EXP_BIAS
then computed == PHI_BIAS
test gf64_phi_distance_best_on_ladder
given phi_dist = PHI_DISTANCE
then phi_dist < 0.003
test gf64_memory_ratio_equals_one
given ratio = MEMORY_RATIO_VS_FP64
then ratio == 1.0
test gf64_validate_format_success
given valid = validate_format()
then valid == true
invariant gf64_bits_constant
assert BITS == 64
invariant gf64_sign_bits_is_one
assert SIGN_BITS == 1
invariant gf64_exp_bits_is_twentyfour
assert EXP_BITS == 24
invariant gf64_mant_bits_is_thirtynine
assert MANT_BITS == 39
invariant gf64_bias_is_canonical
assert EXP_BIAS == 8388607
invariant gf64_phi_bias_is_canonical
assert PHI_BIAS == 8388608
invariant gf64_phi_distance_best_on_ladder
assert PHI_DISTANCE < 0.003
invariant gf64_exp_wider_than_ieee
assert EXP_BITS > 11 // IEEE binary64 has 11-bit exponent
invariant gf64_mant_narrower_than_ieee
assert MANT_BITS < 52 // IEEE binary64 has 52-bit mantissa
invariant gf64_max_ge_min_positive
assert max_value() >= min_positive()
}
All lessons
Module 1 · The rule and its numbers
One rule splits every width, the ratio it aims at, and the Lucas numbers behind the 3.
Module 2 · Why phi, why three
Why the split is phi, why base three, and how a spec checks GF16 keeps phi.
Module 3 · The small rungs: GF4 to GF8
GF4, GF6 and GF8, the fewest bits, where rounding to whole bits costs the most.
Module 4 · Ten to fourteen bits
GF10, GF12 and GF14, and how the distance from 1 / phi moves as the word grows.
Module 5 · GF16 at work
The primary 16-bit format, a two-term dot product in GF-T16, then GF20 and GF24.
Module 6 · GF32 to GF64
GF32 beside IEEE single, GF48 with no IEEE twin, GF64 beside IEEE double.
Module 7 · GF96 to GF256
GF96, GF128 and GF256, where the specs hold the layout with invariants.
Module 8 · The widest rungs, then trits
GF512 and GF1024, the two widest rungs, then GF-T8, where the exponent moves to trits.
Module 9 · More trits, then the decode
GF-T16 and GF-T32, then why fixed fields decode in parallel and a posit does not.