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}