code wiki / (root) / nx_fp32_q14.nx

nx_fp32_q14.nx source

↩ module page · 112 lines · 4657 B

1// nx_fp32_q14.nx -- IEEE 754 binary32 bit pattern -> Q14 fixed-point 2// i64, all-integer. Reads STL coords + any other binary format that 3// stores 32-bit floats in millimetres-scale magnitudes. 4// 5// Why integer-only conversion: 6// Per cardinal feedback-bits-up-exceed-never-match, slicing math 7// should be deterministic. Float ops drift per-platform; integer 8// shift+add ops give bit-exact results on every backend nxc2 9// targets (x86_64, riscv64, arm64, ...). We pay the float price 10// exactly once at parse-time and then operate in Q14 fixed-point 11// for the entire slicer pipeline. 12// 13// Why Q14: 14// Q14 = 1 unit = 1/16384. Matches nx_mesh's existing coord 15// convention (defined in nx_mesh.nx) so STL-loaded meshes compose 16// directly with nx_voxel_mesh / nx_render_pass / our slicer. 17// 18// Range note: 19// At Q14, max representable mm value before i64 overflow is ~5.6e14 20// mm = 5.6e11 m. Any STL coord with magnitude > 2^40 mm = ~1e9 mm 21// is clamped to the saturation sentinel. Real-world 3D-print STL 22// coords are < 1000mm typically; Christus is 305mm. No risk. 23// 24// IEEE 754 binary32 layout (little-endian byte order in STL files): 25// bit 31 : sign 26// bits 30..23 : exponent (8 bits, biased by 127) 27// bits 22..0 : mantissa (23 bits, implicit leading 1) 28// 29// value = (-1)^sign * (1 + mantissa/2^23) * 2^(exp - 127) 30// 31// For Q14 scaling we want: q14_value = value * 2^14 32// = (-1)^sign 33// * (2^23 + mantissa) 34// * 2^(exp - 127 - 23 + 14) 35// = (-1)^sign 36// * (2^23 + mantissa) 37// * 2^(exp - 136) 38// 39// If exp - 136 >= 0: shift full_mantissa left by (exp - 136) 40// If exp - 136 < 0: shift full_mantissa right by (136 - exp) 41// 42// Special cases: 43// exp == 0 : subnormal or ±0. Subnormals at Q14 scale are 44// effectively 0 (smallest subnormal is ~1.4e-45; 45// that's 2.3e-41 in Q14 units, rounds to 0). 46// exp == 255 : ±Inf or NaN. Return NX_FP32_Q14_INVALID sentinel; 47// callers MUST check. Slicer treats this as a 48// parse error and refuses the mesh. 49// 50// Rounding: P0.2a uses truncation for the right-shift case. Nearest- 51// even rounding is queued for the slicer-time tolerance tuning if 52// P5 print evidence shows accuracy regressions. Cardinal 25 (build 53// intelligence) -- the right place for rounding policy is the slicer, 54// not this parser. 55// 56// license_tier: ORIGINAL 57 58import "nx_syscalls.nx" 59 60const NX_FP32_Q14_INVALID: i64 = 0x7fffffffffffffff // Inf/NaN sentinel 61 62// Convert a binary32 bit pattern (held in the low 32 bits of bits32) 63// to a Q14 fixed-point i64 representation. 64func nx_fp32_bits_to_q14(bits32: i64) -> i64 { 65 let sign: i64 = (bits32 >> 31) & 1 66 let exp: i64 = (bits32 >> 23) & 0xff 67 let mantissa: i64 = bits32 & 0x7fffff 68 69 if exp == 0 { return 0 } // subnormal or ±0 70 if exp == 0xff { return NX_FP32_Q14_INVALID } // Inf or NaN 71 72 // Full 24-bit mantissa with implicit leading 1. 73 let full_mantissa: i64 = 0x800000 | mantissa 74 75 var mag: i64 = 0 76 if exp >= 136 { 77 let lshift: i64 = exp - 136 78 // full_mantissa is 24 bits; left-shift > 39 overflows i64. 79 // Real-world print coords never reach that; saturate. 80 if lshift > 39 { return NX_FP32_Q14_INVALID } 81 mag = full_mantissa << lshift 82 } 83 if exp < 136 { 84 let rshift: i64 = 136 - exp 85 if rshift >= 24 { return 0 } // underflows to 0 86 mag = full_mantissa >> rshift 87 } 88 89 if sign == 1 { return 0 - mag } 90 return mag 91} 92 93// Bulk version: convert n consecutive binary32 bit patterns starting 94// at byte offset `off` in `buf` (little-endian) into Q14 i64 written 95// to `out`. Stops + returns the count converted on first invalid. 96// Callers can check `n_written < n_requested` to detect Inf/NaN. 97func nx_fp32_bytes_to_q14(buf: *u8, off: i64, n: i64, out: *i64) -> i64 { 98 var i: i64 = 0 99 while i < n { 100 let bo: i64 = off + i * 4 101 let b0: i64 = (buf[bo + 0] as i64) & 0xff 102 let b1: i64 = (buf[bo + 1] as i64) & 0xff 103 let b2: i64 = (buf[bo + 2] as i64) & 0xff 104 let b3: i64 = (buf[bo + 3] as i64) & 0xff 105 let bits: i64 = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) 106 let q14: i64 = nx_fp32_bits_to_q14(bits) 107 if q14 == NX_FP32_Q14_INVALID { return i } 108 out[i] = q14 109 i = i + 1 110 } 111 return n 112}