Skip to main content

triblespace_core/value/schemas/
f64.rs

1use 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::ToValue;
11use crate::value::TryFromValue;
12use crate::value::TryToValue;
13use crate::value::Value;
14use crate::value::ValueSchema;
15use serde_json::Number as JsonNumber;
16use std::convert::Infallible;
17use std::fmt;
18
19/// A value schema for an IEEE-754 double in little-endian byte order.
20pub struct F64;
21
22impl ConstId for F64 {
23    const ID: Id = id_hex!("C80A60F4A6F2FBA5A8DB2531A923EC70");
24}
25
26impl ConstDescribe for F64 {
27    fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
28    where
29        B: BlobStore<Blake3>,
30    {
31        let id = Self::ID;
32        let description = blobs.put(
33            "IEEE-754 double stored in the first 8 bytes (little-endian); remaining bytes are zero. This matches the standard host representation while preserving the 32-byte value width.\n\nUse for typical metrics, measurements, and calculations where floating-point rounding is acceptable. Choose F256 for higher precision or lossless JSON number import, and R256 for exact rational values.\n\nNaN and infinity can be represented; decide whether your application accepts them. If you need deterministic ordering or exact comparisons, prefer integer or rational schemas.",
34        )?;
35        let tribles = entity! {
36            ExclusiveId::force_ref(&id) @
37                metadata::name: blobs.put("f64")?,
38                metadata::description: description,
39                metadata::tag: metadata::KIND_VALUE_SCHEMA,
40        };
41
42        #[cfg(feature = "wasm")]
43        let tribles = {
44            let mut tribles = tribles;
45            tribles += entity! { ExclusiveId::force_ref(&id) @
46                metadata::value_formatter: blobs.put(wasm_formatter::F64_WASM)?,
47            };
48            tribles
49        };
50        Ok(tribles)
51    }
52}
53
54#[cfg(feature = "wasm")]
55mod wasm_formatter {
56    use core::fmt::Write;
57
58    use triblespace_core_macros::value_formatter;
59
60    #[value_formatter(const_wasm = F64_WASM)]
61    pub(crate) fn float64(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
62        let mut bytes = [0u8; 8];
63        bytes.copy_from_slice(&raw[..8]);
64        let value = f64::from_le_bytes(bytes);
65        write!(out, "{value}").map_err(|_| 1u32)?;
66        Ok(())
67    }
68}
69
70impl ValueSchema for F64 {
71    type ValidationError = Infallible;
72}
73
74impl TryFromValue<'_, F64> for f64 {
75    type Error = Infallible;
76    fn try_from_value(v: &Value<F64>) -> Result<Self, Infallible> {
77        let mut bytes = [0u8; 8];
78        bytes.copy_from_slice(&v.raw[..8]);
79        Ok(f64::from_le_bytes(bytes))
80    }
81}
82
83impl ToValue<F64> for f64 {
84    fn to_value(self) -> Value<F64> {
85        let mut raw = [0u8; 32];
86        raw[..8].copy_from_slice(&self.to_le_bytes());
87        Value::new(raw)
88    }
89}
90
91/// Errors encountered when converting JSON numbers into [`F64`] values.
92#[derive(Debug, Clone, PartialEq)]
93pub enum JsonNumberToF64Error {
94    /// The numeric value could not be represented as an `f64` (non-finite or out of range).
95    Unrepresentable,
96}
97
98impl fmt::Display for JsonNumberToF64Error {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match self {
101            JsonNumberToF64Error::Unrepresentable => {
102                write!(f, "number is too large to represent as f64")
103            }
104        }
105    }
106}
107
108impl std::error::Error for JsonNumberToF64Error {}
109
110impl TryToValue<F64> for JsonNumber {
111    type Error = JsonNumberToF64Error;
112
113    fn try_to_value(self) -> Result<Value<F64>, Self::Error> {
114        (&self).try_to_value()
115    }
116}
117
118impl TryToValue<F64> for &JsonNumber {
119    type Error = JsonNumberToF64Error;
120
121    fn try_to_value(self) -> Result<Value<F64>, Self::Error> {
122        if let Some(value) = self.as_f64().filter(|v| v.is_finite()) {
123            return Ok(value.to_value());
124        }
125        Err(JsonNumberToF64Error::Unrepresentable)
126    }
127}