triblespace_core/inline/encodings/
f256.rs1use crate::inline::Encodes;
2use crate::id::ExclusiveId;
3use crate::id::Id;
4use crate::id_hex;
5use crate::macros::entity;
6use crate::metadata;
7use crate::metadata::MetaDescribe;
8use crate::trible::Fragment;
9use crate::inline::IntoInline;
10use crate::inline::TryFromInline;
11use crate::inline::TryToInline;
12use crate::inline::Inline;
13use crate::inline::InlineEncoding;
14use std::convert::Infallible;
15use std::fmt;
16
17use f256::f256;
18use serde_json::Number as JsonNumber;
19
20pub struct F256LE;
22
23pub struct F256BE;
25
26pub type F256 = F256LE;
28
29impl MetaDescribe for F256LE {
30 fn describe() -> Fragment {
31 let id: Id = id_hex!("D9A419D3CAA0D8E05D8DAB950F5E80F2");
32 #[allow(unused_mut)]
33 let mut tribles = entity! {
34 ExclusiveId::force_ref(&id) @
35 metadata::name: "f256le",
36 metadata::description: "High-precision f256 float stored in little-endian byte order. The format preserves far more precision than f64 and can round-trip large JSON numbers.\n\nUse when precision or exact decimal import matters more than storage or compute cost. Choose the big-endian variant if you need lexicographic ordering or network byte order.\n\nF256 values are heavier to parse and compare than f64. If you only need standard double precision, prefer F64 for faster operations.",
37 metadata::tag: metadata::KIND_INLINE_ENCODING,
38 };
39
40 #[cfg(feature = "wasm")]
41 {
42 tribles += entity! { ExclusiveId::force_ref(&id) @
43 metadata::value_formatter: wasm_formatter::F256_LE_WASM,
44 };
45 }
46 tribles
47 }
48}
49impl InlineEncoding for F256LE {
50 type ValidationError = Infallible;
51 type Encoding = Self;
52}
53impl MetaDescribe for F256BE {
54 fn describe() -> Fragment {
55 let id: Id = id_hex!("A629176D4656928D96B155038F9F2220");
56 #[allow(unused_mut)]
57 let mut tribles = entity! {
58 ExclusiveId::force_ref(&id) @
59 metadata::name: "f256be",
60 metadata::description: "High-precision f256 float stored in big-endian byte order. This variant is convenient for bytewise ordering or wire formats that expect network order.\n\nUse for high-precision metrics or lossless JSON import when ordering matters across systems. For everyday numeric values, F64 is smaller and faster.\n\nAs with all floats, rounding can still occur at the chosen precision. If you need exact fractions, use R256 instead.",
61 metadata::tag: metadata::KIND_INLINE_ENCODING,
62 };
63
64 #[cfg(feature = "wasm")]
65 {
66 tribles += entity! { ExclusiveId::force_ref(&id) @
67 metadata::value_formatter: wasm_formatter::F256_BE_WASM,
68 };
69 }
70 tribles
71 }
72}
73impl InlineEncoding for F256BE {
74 type ValidationError = Infallible;
75 type Encoding = Self;
76}
77
78#[cfg(feature = "wasm")]
79mod wasm_formatter {
80 use core::fmt::Write;
81
82 use triblespace_core_macros::value_formatter;
83
84 #[value_formatter(const_wasm = F256_LE_WASM)]
85 pub(crate) fn f256_le(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
86 let mut buf = [0u8; 16];
87 buf.copy_from_slice(&raw[0..16]);
88 let lo = u128::from_le_bytes(buf);
89 buf.copy_from_slice(&raw[16..32]);
90 let hi = u128::from_le_bytes(buf);
91
92 const EXP_BITS: u32 = 19;
93 const HI_FRACTION_BITS: u32 = 108;
94 const EXP_MAX: u32 = (1u32 << EXP_BITS) - 1;
95 const EXP_BIAS: i32 = (EXP_MAX >> 1) as i32;
96
97 const HI_SIGN_MASK: u128 = 1u128 << 127;
98 const HI_EXP_MASK: u128 = (EXP_MAX as u128) << HI_FRACTION_BITS;
99 const HI_FRACTION_MASK: u128 = (1u128 << HI_FRACTION_BITS) - 1;
100
101 let sign = (hi & HI_SIGN_MASK) != 0;
102 let exp = ((hi & HI_EXP_MASK) >> HI_FRACTION_BITS) as u32;
103
104 let frac_hi = hi & HI_FRACTION_MASK;
105 let frac_lo = lo;
106 let fraction_is_zero = frac_hi == 0 && frac_lo == 0;
107
108 if exp == EXP_MAX {
109 let text = if fraction_is_zero {
110 if sign {
111 "-inf"
112 } else {
113 "inf"
114 }
115 } else {
116 "nan"
117 };
118 out.write_str(text).map_err(|_| 1u32)?;
119 return Ok(());
120 }
121
122 if exp == 0 && fraction_is_zero {
123 let text = if sign { "-0" } else { "0" };
124 out.write_str(text).map_err(|_| 1u32)?;
125 return Ok(());
126 }
127
128 const HEX: &[u8; 16] = b"0123456789ABCDEF";
129
130 if sign {
131 out.write_char('-').map_err(|_| 1u32)?;
132 }
133
134 let exp2 = if exp == 0 {
135 1 - EXP_BIAS
136 } else {
137 exp as i32 - EXP_BIAS
138 };
139 if exp == 0 {
140 out.write_str("0x0").map_err(|_| 1u32)?;
141 } else {
142 out.write_str("0x1").map_err(|_| 1u32)?;
143 }
144
145 let mut digits = [0u8; 59];
146 for i in 0..27 {
147 let shift = (26 - i) * 4;
148 let nibble = ((frac_hi >> shift) & 0xF) as usize;
149 digits[i] = HEX[nibble];
150 }
151 for i in 0..32 {
152 let shift = (31 - i) * 4;
153 let nibble = ((frac_lo >> shift) & 0xF) as usize;
154 digits[27 + i] = HEX[nibble];
155 }
156
157 let mut end = digits.len();
158 while end > 0 && digits[end - 1] == b'0' {
159 end -= 1;
160 }
161 if end > 0 {
162 out.write_char('.').map_err(|_| 1u32)?;
163 for &b in &digits[0..end] {
164 out.write_char(b as char).map_err(|_| 1u32)?;
165 }
166 }
167
168 write!(out, "p{exp2:+}").map_err(|_| 1u32)?;
169 Ok(())
170 }
171
172 #[value_formatter(const_wasm = F256_BE_WASM)]
173 pub(crate) fn f256_be(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
174 let mut buf = [0u8; 16];
175 buf.copy_from_slice(&raw[0..16]);
176 let hi = u128::from_be_bytes(buf);
177 buf.copy_from_slice(&raw[16..32]);
178 let lo = u128::from_be_bytes(buf);
179
180 const EXP_BITS: u32 = 19;
181 const HI_FRACTION_BITS: u32 = 108;
182 const EXP_MAX: u32 = (1u32 << EXP_BITS) - 1;
183 const EXP_BIAS: i32 = (EXP_MAX >> 1) as i32;
184
185 const HI_SIGN_MASK: u128 = 1u128 << 127;
186 const HI_EXP_MASK: u128 = (EXP_MAX as u128) << HI_FRACTION_BITS;
187 const HI_FRACTION_MASK: u128 = (1u128 << HI_FRACTION_BITS) - 1;
188
189 let sign = (hi & HI_SIGN_MASK) != 0;
190 let exp = ((hi & HI_EXP_MASK) >> HI_FRACTION_BITS) as u32;
191
192 let frac_hi = hi & HI_FRACTION_MASK;
193 let frac_lo = lo;
194 let fraction_is_zero = frac_hi == 0 && frac_lo == 0;
195
196 if exp == EXP_MAX {
197 let text = if fraction_is_zero {
198 if sign {
199 "-inf"
200 } else {
201 "inf"
202 }
203 } else {
204 "nan"
205 };
206 out.write_str(text).map_err(|_| 1u32)?;
207 return Ok(());
208 }
209
210 if exp == 0 && fraction_is_zero {
211 let text = if sign { "-0" } else { "0" };
212 out.write_str(text).map_err(|_| 1u32)?;
213 return Ok(());
214 }
215
216 const HEX: &[u8; 16] = b"0123456789ABCDEF";
217
218 if sign {
219 out.write_char('-').map_err(|_| 1u32)?;
220 }
221
222 let exp2 = if exp == 0 {
223 1 - EXP_BIAS
224 } else {
225 exp as i32 - EXP_BIAS
226 };
227 if exp == 0 {
228 out.write_str("0x0").map_err(|_| 1u32)?;
229 } else {
230 out.write_str("0x1").map_err(|_| 1u32)?;
231 }
232
233 let mut digits = [0u8; 59];
234 for i in 0..27 {
235 let shift = (26 - i) * 4;
236 let nibble = ((frac_hi >> shift) & 0xF) as usize;
237 digits[i] = HEX[nibble];
238 }
239 for i in 0..32 {
240 let shift = (31 - i) * 4;
241 let nibble = ((frac_lo >> shift) & 0xF) as usize;
242 digits[27 + i] = HEX[nibble];
243 }
244
245 let mut end = digits.len();
246 while end > 0 && digits[end - 1] == b'0' {
247 end -= 1;
248 }
249 if end > 0 {
250 out.write_char('.').map_err(|_| 1u32)?;
251 for &b in &digits[0..end] {
252 out.write_char(b as char).map_err(|_| 1u32)?;
253 }
254 }
255
256 write!(out, "p{exp2:+}").map_err(|_| 1u32)?;
257 Ok(())
258 }
259}
260
261impl TryFromInline<'_, F256BE> for f256 {
262 type Error = Infallible;
263 fn try_from_inline(v: &Inline<F256BE>) -> Result<Self, Infallible> {
264 Ok(f256::from_be_bytes(v.raw))
265 }
266}
267
268impl Encodes<f256> for F256BE
269{
270 type Output = Inline<F256BE>;
271 fn encode(source: f256) -> Inline<F256BE> {
272 Inline::new(source.to_be_bytes())
273 }
274}
275
276impl TryFromInline<'_, F256LE> for f256 {
277 type Error = Infallible;
278 fn try_from_inline(v: &Inline<F256LE>) -> Result<Self, Infallible> {
279 Ok(f256::from_le_bytes(v.raw))
280 }
281}
282
283impl Encodes<f256> for F256LE
284{
285 type Output = Inline<F256LE>;
286 fn encode(source: f256) -> Inline<F256LE> {
287 Inline::new(source.to_le_bytes())
288 }
289}
290
291#[derive(Debug, Clone, PartialEq)]
293pub enum JsonNumberToF256Error {
294 Unrepresentable,
296}
297
298impl fmt::Display for JsonNumberToF256Error {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 match self {
301 JsonNumberToF256Error::Unrepresentable => {
302 write!(f, "number is too large to represent as f256")
303 }
304 }
305 }
306}
307
308impl std::error::Error for JsonNumberToF256Error {}
309
310impl TryToInline<F256> for JsonNumber {
311 type Error = JsonNumberToF256Error;
312
313 fn try_to_inline(self) -> Result<Inline<F256>, Self::Error> {
314 (&self).try_to_inline()
315 }
316}
317
318impl TryToInline<F256> for &JsonNumber {
319 type Error = JsonNumberToF256Error;
320
321 fn try_to_inline(self) -> Result<Inline<F256>, Self::Error> {
322 if let Some(value) = self.as_u128() {
323 return Ok(f256::from(value).to_inline());
324 }
325 if let Some(value) = self.as_i128() {
326 return Ok(f256::from(value).to_inline());
327 }
328 if let Some(value) = self.as_f64() {
329 return Ok(f256::from(value).to_inline());
330 }
331 Err(JsonNumberToF256Error::Unrepresentable)
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use crate::inline::{IntoInline, TryToInline};
339 use ::f256::f256;
340 use proptest::prelude::*;
341
342 fn arb_f256_non_nan() -> impl Strategy<Value = f256> {
344 any::<f64>()
345 .prop_filter("not NaN", |v| !v.is_nan())
346 .prop_map(f256::from)
347 }
348
349 proptest! {
350 #[test]
351 fn f256le_roundtrip(input in arb_f256_non_nan()) {
352 let value: Inline<F256LE> = input.to_inline();
353 let output: f256 = value.from_inline();
354 prop_assert_eq!(input, output);
355 }
356
357 #[test]
358 fn f256be_roundtrip(input in arb_f256_non_nan()) {
359 let value: Inline<F256BE> = input.to_inline();
360 let output: f256 = value.from_inline();
361 prop_assert_eq!(input, output);
362 }
363
364 #[test]
365 fn f256le_validates(input in arb_f256_non_nan()) {
366 let value: Inline<F256LE> = input.to_inline();
367 prop_assert!(F256LE::validate(value).is_ok());
368 }
369
370 #[test]
371 fn f256be_validates(input in arb_f256_non_nan()) {
372 let value: Inline<F256BE> = input.to_inline();
373 prop_assert!(F256BE::validate(value).is_ok());
374 }
375
376 #[test]
377 fn f256_le_and_be_differ(input in arb_f256_non_nan().prop_filter("non-zero", |v| *v != f256::ZERO)) {
378 let le_val: Inline<F256LE> = input.to_inline();
379 let be_val: Inline<F256BE> = input.to_inline();
380 prop_assert_ne!(le_val.raw, be_val.raw);
381 }
382
383 #[test]
384 fn json_number_u128_roundtrip(input: u64) {
385 let s = input.to_string();
386 let num: JsonNumber = serde_json::from_str(&s).unwrap();
387 let value: Inline<F256> = num.try_to_inline().expect("valid number");
388 let output: f256 = value.from_inline();
389 prop_assert_eq!(output, f256::from(input as u128));
390 }
391
392 #[test]
393 fn json_number_negative_roundtrip(input in any::<i64>().prop_filter("negative", |v| *v < 0)) {
394 let s = input.to_string();
395 let num: JsonNumber = serde_json::from_str(&s).unwrap();
396 let value: Inline<F256> = num.try_to_inline().expect("valid number");
397 let output: f256 = value.from_inline();
398 prop_assert_eq!(output, f256::from(input as i128));
399 }
400
401 #[test]
402 fn json_number_f64_roundtrip(input in any::<f64>().prop_filter("finite", |v| v.is_finite())) {
403 let s = ryu::Buffer::new().format(input).to_string();
404 let num: JsonNumber = serde_json::from_str(&s).unwrap();
405 let expected = f256::from(num.as_f64().unwrap());
407 let value: Inline<F256> = (&num).try_to_inline().expect("valid number");
408 let output: f256 = value.from_inline();
409 prop_assert_eq!(output, expected);
413 }
414
415 #[test]
416 fn json_number_ref_roundtrip(input: u64) {
417 let s = input.to_string();
418 let num: JsonNumber = serde_json::from_str(&s).unwrap();
419 let value: Inline<F256> = (&num).try_to_inline().expect("valid ref number");
420 let output: f256 = value.from_inline();
421 prop_assert_eq!(output, f256::from(input as u128));
422 }
423 }
424
425 #[test]
427 fn f256_le_roundtrip_nan() {
428 let input = f256::NAN;
429 let value: Inline<F256LE> = input.to_inline();
430 let output: f256 = value.from_inline();
431 assert!(output.is_nan());
432 }
433}