Skip to main content

triblespace_core/value/schemas/
f256.rs

1use crate::id::ExclusiveId;
2use crate::id::Id;
3use crate::id_hex;
4use crate::macros::entity;
5use crate::metadata;
6use crate::metadata::ConstMetadata;
7use crate::repo::BlobStore;
8use crate::trible::TribleSet;
9use crate::value::schemas::hash::Blake3;
10use crate::value::FromValue;
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
21#[cfg(feature = "wasm")]
22use crate::blob::schemas::wasmcode::WasmCode;
23/// A value schema for a 256-bit floating point number in little-endian byte order.
24pub struct F256LE;
25
26/// A value schema for a 256-bit floating point number in big-endian byte order.
27pub struct F256BE;
28
29/// A type alias for the little-endian version of the 256-bit floating point number.
30pub type F256 = F256LE;
31
32impl ConstMetadata for F256LE {
33    fn id() -> Id {
34        id_hex!("D9A419D3CAA0D8E05D8DAB950F5E80F2")
35    }
36
37    fn describe<B>(blobs: &mut B) -> Result<TribleSet, B::PutError>
38    where
39        B: BlobStore<Blake3>,
40    {
41        let id = Self::id();
42        let description = blobs.put(
43            "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.",
44        )?;
45        let tribles = entity! {
46            ExclusiveId::force_ref(&id) @
47                metadata::name: blobs.put("f256le".to_string())?,
48                metadata::description: description,
49                metadata::tag: metadata::KIND_VALUE_SCHEMA,
50        };
51
52        #[cfg(feature = "wasm")]
53        let tribles = {
54            let mut tribles = tribles;
55            tribles += entity! { ExclusiveId::force_ref(&id) @
56                metadata::value_formatter: blobs.put(wasm_formatter::F256_LE_WASM)?,
57            };
58            tribles
59        };
60        Ok(tribles)
61    }
62}
63impl ValueSchema for F256LE {
64    type ValidationError = Infallible;
65}
66impl ConstMetadata for F256BE {
67    fn id() -> Id {
68        id_hex!("A629176D4656928D96B155038F9F2220")
69    }
70
71    fn describe<B>(blobs: &mut B) -> Result<TribleSet, B::PutError>
72    where
73        B: BlobStore<Blake3>,
74    {
75        let id = Self::id();
76        let description = blobs.put(
77            "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.",
78        )?;
79        let tribles = entity! {
80            ExclusiveId::force_ref(&id) @
81                metadata::name: blobs.put("f256be".to_string())?,
82                metadata::description: description,
83                metadata::tag: metadata::KIND_VALUE_SCHEMA,
84        };
85
86        #[cfg(feature = "wasm")]
87        let tribles = {
88            let mut tribles = tribles;
89            tribles += entity! { ExclusiveId::force_ref(&id) @
90                metadata::value_formatter: blobs.put(wasm_formatter::F256_BE_WASM)?,
91            };
92            tribles
93        };
94        Ok(tribles)
95    }
96}
97impl ValueSchema for F256BE {
98    type ValidationError = Infallible;
99}
100
101#[cfg(feature = "wasm")]
102mod wasm_formatter {
103    use core::fmt::Write;
104
105    use triblespace_core_macros::value_formatter;
106
107    #[value_formatter(const_wasm = F256_LE_WASM)]
108    pub(crate) fn f256_le(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
109        let mut buf = [0u8; 16];
110        buf.copy_from_slice(&raw[0..16]);
111        let lo = u128::from_le_bytes(buf);
112        buf.copy_from_slice(&raw[16..32]);
113        let hi = u128::from_le_bytes(buf);
114
115        const EXP_BITS: u32 = 19;
116        const HI_FRACTION_BITS: u32 = 108;
117        const EXP_MAX: u32 = (1u32 << EXP_BITS) - 1;
118        const EXP_BIAS: i32 = (EXP_MAX >> 1) as i32;
119
120        const HI_SIGN_MASK: u128 = 1u128 << 127;
121        const HI_EXP_MASK: u128 = (EXP_MAX as u128) << HI_FRACTION_BITS;
122        const HI_FRACTION_MASK: u128 = (1u128 << HI_FRACTION_BITS) - 1;
123
124        let sign = (hi & HI_SIGN_MASK) != 0;
125        let exp = ((hi & HI_EXP_MASK) >> HI_FRACTION_BITS) as u32;
126
127        let frac_hi = hi & HI_FRACTION_MASK;
128        let frac_lo = lo;
129        let fraction_is_zero = frac_hi == 0 && frac_lo == 0;
130
131        if exp == EXP_MAX {
132            let text = if fraction_is_zero {
133                if sign {
134                    "-inf"
135                } else {
136                    "inf"
137                }
138            } else {
139                "nan"
140            };
141            out.write_str(text).map_err(|_| 1u32)?;
142            return Ok(());
143        }
144
145        if exp == 0 && fraction_is_zero {
146            let text = if sign { "-0" } else { "0" };
147            out.write_str(text).map_err(|_| 1u32)?;
148            return Ok(());
149        }
150
151        const HEX: &[u8; 16] = b"0123456789ABCDEF";
152
153        if sign {
154            out.write_char('-').map_err(|_| 1u32)?;
155        }
156
157        let exp2 = if exp == 0 {
158            1 - EXP_BIAS
159        } else {
160            exp as i32 - EXP_BIAS
161        };
162        if exp == 0 {
163            out.write_str("0x0").map_err(|_| 1u32)?;
164        } else {
165            out.write_str("0x1").map_err(|_| 1u32)?;
166        }
167
168        let mut digits = [0u8; 59];
169        for i in 0..27 {
170            let shift = (26 - i) * 4;
171            let nibble = ((frac_hi >> shift) & 0xF) as usize;
172            digits[i] = HEX[nibble];
173        }
174        for i in 0..32 {
175            let shift = (31 - i) * 4;
176            let nibble = ((frac_lo >> shift) & 0xF) as usize;
177            digits[27 + i] = HEX[nibble];
178        }
179
180        let mut end = digits.len();
181        while end > 0 && digits[end - 1] == b'0' {
182            end -= 1;
183        }
184        if end > 0 {
185            out.write_char('.').map_err(|_| 1u32)?;
186            for &b in &digits[0..end] {
187                out.write_char(b as char).map_err(|_| 1u32)?;
188            }
189        }
190
191        write!(out, "p{exp2:+}").map_err(|_| 1u32)?;
192        Ok(())
193    }
194
195    #[value_formatter(const_wasm = F256_BE_WASM)]
196    pub(crate) fn f256_be(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
197        let mut buf = [0u8; 16];
198        buf.copy_from_slice(&raw[0..16]);
199        let hi = u128::from_be_bytes(buf);
200        buf.copy_from_slice(&raw[16..32]);
201        let lo = u128::from_be_bytes(buf);
202
203        const EXP_BITS: u32 = 19;
204        const HI_FRACTION_BITS: u32 = 108;
205        const EXP_MAX: u32 = (1u32 << EXP_BITS) - 1;
206        const EXP_BIAS: i32 = (EXP_MAX >> 1) as i32;
207
208        const HI_SIGN_MASK: u128 = 1u128 << 127;
209        const HI_EXP_MASK: u128 = (EXP_MAX as u128) << HI_FRACTION_BITS;
210        const HI_FRACTION_MASK: u128 = (1u128 << HI_FRACTION_BITS) - 1;
211
212        let sign = (hi & HI_SIGN_MASK) != 0;
213        let exp = ((hi & HI_EXP_MASK) >> HI_FRACTION_BITS) as u32;
214
215        let frac_hi = hi & HI_FRACTION_MASK;
216        let frac_lo = lo;
217        let fraction_is_zero = frac_hi == 0 && frac_lo == 0;
218
219        if exp == EXP_MAX {
220            let text = if fraction_is_zero {
221                if sign {
222                    "-inf"
223                } else {
224                    "inf"
225                }
226            } else {
227                "nan"
228            };
229            out.write_str(text).map_err(|_| 1u32)?;
230            return Ok(());
231        }
232
233        if exp == 0 && fraction_is_zero {
234            let text = if sign { "-0" } else { "0" };
235            out.write_str(text).map_err(|_| 1u32)?;
236            return Ok(());
237        }
238
239        const HEX: &[u8; 16] = b"0123456789ABCDEF";
240
241        if sign {
242            out.write_char('-').map_err(|_| 1u32)?;
243        }
244
245        let exp2 = if exp == 0 {
246            1 - EXP_BIAS
247        } else {
248            exp as i32 - EXP_BIAS
249        };
250        if exp == 0 {
251            out.write_str("0x0").map_err(|_| 1u32)?;
252        } else {
253            out.write_str("0x1").map_err(|_| 1u32)?;
254        }
255
256        let mut digits = [0u8; 59];
257        for i in 0..27 {
258            let shift = (26 - i) * 4;
259            let nibble = ((frac_hi >> shift) & 0xF) as usize;
260            digits[i] = HEX[nibble];
261        }
262        for i in 0..32 {
263            let shift = (31 - i) * 4;
264            let nibble = ((frac_lo >> shift) & 0xF) as usize;
265            digits[27 + i] = HEX[nibble];
266        }
267
268        let mut end = digits.len();
269        while end > 0 && digits[end - 1] == b'0' {
270            end -= 1;
271        }
272        if end > 0 {
273            out.write_char('.').map_err(|_| 1u32)?;
274            for &b in &digits[0..end] {
275                out.write_char(b as char).map_err(|_| 1u32)?;
276            }
277        }
278
279        write!(out, "p{exp2:+}").map_err(|_| 1u32)?;
280        Ok(())
281    }
282}
283
284impl FromValue<'_, F256BE> for f256 {
285    fn from_value(v: &Value<F256BE>) -> Self {
286        f256::from_be_bytes(v.raw)
287    }
288}
289
290impl ToValue<F256BE> for f256 {
291    fn to_value(self) -> Value<F256BE> {
292        Value::new(self.to_be_bytes())
293    }
294}
295
296impl FromValue<'_, F256LE> for f256 {
297    fn from_value(v: &Value<F256LE>) -> Self {
298        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/// Errors encountered when converting JSON numbers into [`F256`] values.
309#[derive(Debug, Clone, PartialEq)]
310pub enum JsonNumberToF256Error {
311    /// The numeric value could not be represented as an `f256`.
312    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}