Skip to main content

triblespace_core/value/schemas/
f256.rs

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