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