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}