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::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;
23pub struct F256LE;
25
26pub struct F256BE;
28
29pub 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#[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}