triblespace_core/value/schemas/
f256.rs1use crate::id::ExclusiveId;
2use crate::id::Id;
3use crate::id_hex;
4use crate::macros::entity;
5use crate::metadata;
6use crate::metadata::{ConstDescribe, ConstId};
7use crate::repo::BlobStore;
8use crate::trible::Fragment;
9use crate::value::schemas::hash::Blake3;
10use crate::value::TryFromValue;
11use crate::value::ToValue;
12use crate::value::TryToValue;
13use crate::value::Value;
14use crate::value::ValueSchema;
15use std::convert::Infallible;
16use std::fmt;
17
18use f256::f256;
19use serde_json::Number as JsonNumber;
20
21pub struct F256LE;
23
24impl ConstId for F256LE {
25 const ID: Id = id_hex!("D9A419D3CAA0D8E05D8DAB950F5E80F2");
26}
27
28pub struct F256BE;
30
31impl ConstId for F256BE {
32 const ID: Id = id_hex!("A629176D4656928D96B155038F9F2220");
33}
34
35pub type F256 = F256LE;
37
38impl ConstDescribe for F256LE {
39 fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
40 where
41 B: BlobStore<Blake3>,
42 {
43 let id = Self::ID;
44 let description = blobs.put(
45 "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.",
46 )?;
47 let tribles = entity! {
48 ExclusiveId::force_ref(&id) @
49 metadata::name: blobs.put("f256le")?,
50 metadata::description: description,
51 metadata::tag: metadata::KIND_VALUE_SCHEMA,
52 };
53
54 #[cfg(feature = "wasm")]
55 let tribles = {
56 let mut tribles = tribles;
57 tribles += entity! { ExclusiveId::force_ref(&id) @
58 metadata::value_formatter: blobs.put(wasm_formatter::F256_LE_WASM)?,
59 };
60 tribles
61 };
62 Ok(tribles)
63 }
64}
65impl ValueSchema for F256LE {
66 type ValidationError = Infallible;
67}
68impl ConstDescribe for F256BE {
69 fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
70 where
71 B: BlobStore<Blake3>,
72 {
73 let id = Self::ID;
74 let description = blobs.put(
75 "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.",
76 )?;
77 let tribles = entity! {
78 ExclusiveId::force_ref(&id) @
79 metadata::name: blobs.put("f256be")?,
80 metadata::description: description,
81 metadata::tag: metadata::KIND_VALUE_SCHEMA,
82 };
83
84 #[cfg(feature = "wasm")]
85 let tribles = {
86 let mut tribles = tribles;
87 tribles += entity! { ExclusiveId::force_ref(&id) @
88 metadata::value_formatter: blobs.put(wasm_formatter::F256_BE_WASM)?,
89 };
90 tribles
91 };
92 Ok(tribles)
93 }
94}
95impl ValueSchema for F256BE {
96 type ValidationError = Infallible;
97}
98
99#[cfg(feature = "wasm")]
100mod wasm_formatter {
101 use core::fmt::Write;
102
103 use triblespace_core_macros::value_formatter;
104
105 #[value_formatter(const_wasm = F256_LE_WASM)]
106 pub(crate) fn f256_le(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
107 let mut buf = [0u8; 16];
108 buf.copy_from_slice(&raw[0..16]);
109 let lo = u128::from_le_bytes(buf);
110 buf.copy_from_slice(&raw[16..32]);
111 let hi = u128::from_le_bytes(buf);
112
113 const EXP_BITS: u32 = 19;
114 const HI_FRACTION_BITS: u32 = 108;
115 const EXP_MAX: u32 = (1u32 << EXP_BITS) - 1;
116 const EXP_BIAS: i32 = (EXP_MAX >> 1) as i32;
117
118 const HI_SIGN_MASK: u128 = 1u128 << 127;
119 const HI_EXP_MASK: u128 = (EXP_MAX as u128) << HI_FRACTION_BITS;
120 const HI_FRACTION_MASK: u128 = (1u128 << HI_FRACTION_BITS) - 1;
121
122 let sign = (hi & HI_SIGN_MASK) != 0;
123 let exp = ((hi & HI_EXP_MASK) >> HI_FRACTION_BITS) as u32;
124
125 let frac_hi = hi & HI_FRACTION_MASK;
126 let frac_lo = lo;
127 let fraction_is_zero = frac_hi == 0 && frac_lo == 0;
128
129 if exp == EXP_MAX {
130 let text = if fraction_is_zero {
131 if sign {
132 "-inf"
133 } else {
134 "inf"
135 }
136 } else {
137 "nan"
138 };
139 out.write_str(text).map_err(|_| 1u32)?;
140 return Ok(());
141 }
142
143 if exp == 0 && fraction_is_zero {
144 let text = if sign { "-0" } else { "0" };
145 out.write_str(text).map_err(|_| 1u32)?;
146 return Ok(());
147 }
148
149 const HEX: &[u8; 16] = b"0123456789ABCDEF";
150
151 if sign {
152 out.write_char('-').map_err(|_| 1u32)?;
153 }
154
155 let exp2 = if exp == 0 {
156 1 - EXP_BIAS
157 } else {
158 exp as i32 - EXP_BIAS
159 };
160 if exp == 0 {
161 out.write_str("0x0").map_err(|_| 1u32)?;
162 } else {
163 out.write_str("0x1").map_err(|_| 1u32)?;
164 }
165
166 let mut digits = [0u8; 59];
167 for i in 0..27 {
168 let shift = (26 - i) * 4;
169 let nibble = ((frac_hi >> shift) & 0xF) as usize;
170 digits[i] = HEX[nibble];
171 }
172 for i in 0..32 {
173 let shift = (31 - i) * 4;
174 let nibble = ((frac_lo >> shift) & 0xF) as usize;
175 digits[27 + i] = HEX[nibble];
176 }
177
178 let mut end = digits.len();
179 while end > 0 && digits[end - 1] == b'0' {
180 end -= 1;
181 }
182 if end > 0 {
183 out.write_char('.').map_err(|_| 1u32)?;
184 for &b in &digits[0..end] {
185 out.write_char(b as char).map_err(|_| 1u32)?;
186 }
187 }
188
189 write!(out, "p{exp2:+}").map_err(|_| 1u32)?;
190 Ok(())
191 }
192
193 #[value_formatter(const_wasm = F256_BE_WASM)]
194 pub(crate) fn f256_be(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
195 let mut buf = [0u8; 16];
196 buf.copy_from_slice(&raw[0..16]);
197 let hi = u128::from_be_bytes(buf);
198 buf.copy_from_slice(&raw[16..32]);
199 let lo = u128::from_be_bytes(buf);
200
201 const EXP_BITS: u32 = 19;
202 const HI_FRACTION_BITS: u32 = 108;
203 const EXP_MAX: u32 = (1u32 << EXP_BITS) - 1;
204 const EXP_BIAS: i32 = (EXP_MAX >> 1) as i32;
205
206 const HI_SIGN_MASK: u128 = 1u128 << 127;
207 const HI_EXP_MASK: u128 = (EXP_MAX as u128) << HI_FRACTION_BITS;
208 const HI_FRACTION_MASK: u128 = (1u128 << HI_FRACTION_BITS) - 1;
209
210 let sign = (hi & HI_SIGN_MASK) != 0;
211 let exp = ((hi & HI_EXP_MASK) >> HI_FRACTION_BITS) as u32;
212
213 let frac_hi = hi & HI_FRACTION_MASK;
214 let frac_lo = lo;
215 let fraction_is_zero = frac_hi == 0 && frac_lo == 0;
216
217 if exp == EXP_MAX {
218 let text = if fraction_is_zero {
219 if sign {
220 "-inf"
221 } else {
222 "inf"
223 }
224 } else {
225 "nan"
226 };
227 out.write_str(text).map_err(|_| 1u32)?;
228 return Ok(());
229 }
230
231 if exp == 0 && fraction_is_zero {
232 let text = if sign { "-0" } else { "0" };
233 out.write_str(text).map_err(|_| 1u32)?;
234 return Ok(());
235 }
236
237 const HEX: &[u8; 16] = b"0123456789ABCDEF";
238
239 if sign {
240 out.write_char('-').map_err(|_| 1u32)?;
241 }
242
243 let exp2 = if exp == 0 {
244 1 - EXP_BIAS
245 } else {
246 exp as i32 - EXP_BIAS
247 };
248 if exp == 0 {
249 out.write_str("0x0").map_err(|_| 1u32)?;
250 } else {
251 out.write_str("0x1").map_err(|_| 1u32)?;
252 }
253
254 let mut digits = [0u8; 59];
255 for i in 0..27 {
256 let shift = (26 - i) * 4;
257 let nibble = ((frac_hi >> shift) & 0xF) as usize;
258 digits[i] = HEX[nibble];
259 }
260 for i in 0..32 {
261 let shift = (31 - i) * 4;
262 let nibble = ((frac_lo >> shift) & 0xF) as usize;
263 digits[27 + i] = HEX[nibble];
264 }
265
266 let mut end = digits.len();
267 while end > 0 && digits[end - 1] == b'0' {
268 end -= 1;
269 }
270 if end > 0 {
271 out.write_char('.').map_err(|_| 1u32)?;
272 for &b in &digits[0..end] {
273 out.write_char(b as char).map_err(|_| 1u32)?;
274 }
275 }
276
277 write!(out, "p{exp2:+}").map_err(|_| 1u32)?;
278 Ok(())
279 }
280}
281
282impl TryFromValue<'_, F256BE> for f256 {
283 type Error = Infallible;
284 fn try_from_value(v: &Value<F256BE>) -> Result<Self, Infallible> {
285 Ok(f256::from_be_bytes(v.raw))
286 }
287}
288
289impl ToValue<F256BE> for f256 {
290 fn to_value(self) -> Value<F256BE> {
291 Value::new(self.to_be_bytes())
292 }
293}
294
295impl TryFromValue<'_, F256LE> for f256 {
296 type Error = Infallible;
297 fn try_from_value(v: &Value<F256LE>) -> Result<Self, Infallible> {
298 Ok(f256::from_le_bytes(v.raw))
299 }
300}
301
302impl ToValue<F256LE> for f256 {
303 fn to_value(self) -> Value<F256LE> {
304 Value::new(self.to_le_bytes())
305 }
306}
307
308#[derive(Debug, Clone, PartialEq)]
310pub enum JsonNumberToF256Error {
311 Unrepresentable,
313}
314
315impl fmt::Display for JsonNumberToF256Error {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 match self {
318 JsonNumberToF256Error::Unrepresentable => {
319 write!(f, "number is too large to represent as f256")
320 }
321 }
322 }
323}
324
325impl std::error::Error for JsonNumberToF256Error {}
326
327impl TryToValue<F256> for JsonNumber {
328 type Error = JsonNumberToF256Error;
329
330 fn try_to_value(self) -> Result<Value<F256>, Self::Error> {
331 (&self).try_to_value()
332 }
333}
334
335impl TryToValue<F256> for &JsonNumber {
336 type Error = JsonNumberToF256Error;
337
338 fn try_to_value(self) -> Result<Value<F256>, Self::Error> {
339 if let Some(value) = self.as_u128() {
340 return Ok(f256::from(value).to_value());
341 }
342 if let Some(value) = self.as_i128() {
343 return Ok(f256::from(value).to_value());
344 }
345 if let Some(value) = self.as_f64() {
346 return Ok(f256::from(value).to_value());
347 }
348 Err(JsonNumberToF256Error::Unrepresentable)
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use crate::value::{ToValue, TryToValue};
356 use ::f256::f256;
357 use proptest::prelude::*;
358
359 fn arb_f256_non_nan() -> impl Strategy<Value = f256> {
361 any::<f64>()
362 .prop_filter("not NaN", |v| !v.is_nan())
363 .prop_map(f256::from)
364 }
365
366 proptest! {
367 #[test]
368 fn f256le_roundtrip(input in arb_f256_non_nan()) {
369 let value: Value<F256LE> = input.to_value();
370 let output: f256 = value.from_value();
371 prop_assert_eq!(input, output);
372 }
373
374 #[test]
375 fn f256be_roundtrip(input in arb_f256_non_nan()) {
376 let value: Value<F256BE> = input.to_value();
377 let output: f256 = value.from_value();
378 prop_assert_eq!(input, output);
379 }
380
381 #[test]
382 fn f256le_validates(input in arb_f256_non_nan()) {
383 let value: Value<F256LE> = input.to_value();
384 prop_assert!(F256LE::validate(value).is_ok());
385 }
386
387 #[test]
388 fn f256be_validates(input in arb_f256_non_nan()) {
389 let value: Value<F256BE> = input.to_value();
390 prop_assert!(F256BE::validate(value).is_ok());
391 }
392
393 #[test]
394 fn f256_le_and_be_differ(input in arb_f256_non_nan().prop_filter("non-zero", |v| *v != f256::ZERO)) {
395 let le_val: Value<F256LE> = input.to_value();
396 let be_val: Value<F256BE> = input.to_value();
397 prop_assert_ne!(le_val.raw, be_val.raw);
398 }
399
400 #[test]
401 fn json_number_u128_roundtrip(input: u64) {
402 let s = input.to_string();
403 let num: JsonNumber = serde_json::from_str(&s).unwrap();
404 let value: Value<F256> = num.try_to_value().expect("valid number");
405 let output: f256 = value.from_value();
406 prop_assert_eq!(output, f256::from(input as u128));
407 }
408
409 #[test]
410 fn json_number_negative_roundtrip(input in any::<i64>().prop_filter("negative", |v| *v < 0)) {
411 let s = input.to_string();
412 let num: JsonNumber = serde_json::from_str(&s).unwrap();
413 let value: Value<F256> = num.try_to_value().expect("valid number");
414 let output: f256 = value.from_value();
415 prop_assert_eq!(output, f256::from(input as i128));
416 }
417
418 #[test]
419 fn json_number_f64_roundtrip(input in any::<f64>().prop_filter("finite", |v| v.is_finite())) {
420 let s = ryu::Buffer::new().format(input).to_string();
421 let num: JsonNumber = serde_json::from_str(&s).unwrap();
422 let expected = f256::from(num.as_f64().unwrap());
424 let value: Value<F256> = (&num).try_to_value().expect("valid number");
425 let output: f256 = value.from_value();
426 prop_assert_eq!(output, expected);
430 }
431
432 #[test]
433 fn json_number_ref_roundtrip(input: u64) {
434 let s = input.to_string();
435 let num: JsonNumber = serde_json::from_str(&s).unwrap();
436 let value: Value<F256> = (&num).try_to_value().expect("valid ref number");
437 let output: f256 = value.from_value();
438 prop_assert_eq!(output, f256::from(input as u128));
439 }
440 }
441
442 #[test]
444 fn f256_le_roundtrip_nan() {
445 let input = f256::NAN;
446 let value: Value<F256LE> = input.to_value();
447 let output: f256 = value.from_value();
448 assert!(output.is_nan());
449 }
450}