triblespace_core/value/schemas/
f64.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 serde_json::Number as JsonNumber;
16use std::convert::Infallible;
17use std::fmt;
18
19#[cfg(feature = "wasm")]
20use crate::blob::schemas::wasmcode::WasmCode;
21pub 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#[derive(Debug, Clone, PartialEq)]
94pub enum JsonNumberToF64Error {
95 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}