triblespace_core/value/schemas/
f256.rs1use 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;
24pub struct F256LE;
26
27pub struct F256BE;
29
30pub 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#[derive(Debug, Clone, PartialEq)]
311pub enum JsonNumberToF256Error {
312 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}