Skip to main content

triblespace_core/value/schemas/
f64.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 serde_json::Number as JsonNumber;
17use std::convert::Infallible;
18use std::fmt;
19
20#[cfg(feature = "wasm")]
21use crate::blob::schemas::wasmcode::WasmCode;
22/// A value schema for an IEEE-754 double in little-endian byte order.
23pub struct F64;
24
25impl ConstMetadata for F64 {
26    fn id() -> Id {
27        id_hex!("C80A60F4A6F2FBA5A8DB2531A923EC70")
28    }
29
30    fn describe<B>(blobs: &mut B) -> Result<TribleSet, B::PutError>
31    where
32        B: BlobStore<Blake3>,
33    {
34        let id = Self::id();
35        let description = blobs.put::<LongString, _>(
36            "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.",
37        )?;
38        let tribles = entity! {
39            ExclusiveId::force_ref(&id) @
40                metadata::shortname: "f64",
41                metadata::description: description,
42                metadata::tag: metadata::KIND_VALUE_SCHEMA,
43        };
44
45        #[cfg(feature = "wasm")]
46        let tribles = {
47            let mut tribles = tribles;
48            tribles += entity! { ExclusiveId::force_ref(&id) @
49                metadata::value_formatter: blobs.put::<WasmCode, _>(wasm_formatter::F64_WASM)?,
50            };
51            tribles
52        };
53        Ok(tribles)
54    }
55}
56
57#[cfg(feature = "wasm")]
58mod wasm_formatter {
59    use core::fmt::Write;
60
61    use triblespace_core_macros::value_formatter;
62
63    #[value_formatter(const_wasm = F64_WASM)]
64    pub(crate) fn float64(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
65        let mut bytes = [0u8; 8];
66        bytes.copy_from_slice(&raw[..8]);
67        let value = f64::from_le_bytes(bytes);
68        write!(out, "{value}").map_err(|_| 1u32)?;
69        Ok(())
70    }
71}
72
73impl ValueSchema for F64 {
74    type ValidationError = Infallible;
75}
76
77impl FromValue<'_, F64> for f64 {
78    fn from_value(v: &Value<F64>) -> Self {
79        let mut bytes = [0u8; 8];
80        bytes.copy_from_slice(&v.raw[..8]);
81        f64::from_le_bytes(bytes)
82    }
83}
84
85impl ToValue<F64> for f64 {
86    fn to_value(self) -> Value<F64> {
87        let mut raw = [0u8; 32];
88        raw[..8].copy_from_slice(&self.to_le_bytes());
89        Value::new(raw)
90    }
91}
92
93/// Errors encountered when converting JSON numbers into [`F64`] values.
94#[derive(Debug, Clone, PartialEq)]
95pub enum JsonNumberToF64Error {
96    /// The numeric value could not be represented as an `f64` (non-finite or out of range).
97    Unrepresentable,
98}
99
100impl fmt::Display for JsonNumberToF64Error {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        match self {
103            JsonNumberToF64Error::Unrepresentable => {
104                write!(f, "number is too large to represent as f64")
105            }
106        }
107    }
108}
109
110impl std::error::Error for JsonNumberToF64Error {}
111
112impl TryToValue<F64> for JsonNumber {
113    type Error = JsonNumberToF64Error;
114
115    fn try_to_value(self) -> Result<Value<F64>, Self::Error> {
116        (&self).try_to_value()
117    }
118}
119
120impl TryToValue<F64> for &JsonNumber {
121    type Error = JsonNumberToF64Error;
122
123    fn try_to_value(self) -> Result<Value<F64>, Self::Error> {
124        if let Some(value) = self.as_f64().filter(|v| v.is_finite()) {
125            return Ok(value.to_value());
126        }
127        Err(JsonNumberToF64Error::Unrepresentable)
128    }
129}