Skip to main content

qtty_core/
lib.rs

1//! Core type system for strongly typed physical quantities.
2//!
3//! `qtty-core` provides a minimal, zero-cost units model:
4//!
5//! - A *unit* is a zero-sized marker type implementing [`Unit`].
6//! - A value tagged with a unit is a [`Quantity<U>`], backed by an `f64`.
7//! - Conversion is an explicit, type-checked scaling via [`Quantity::to`].
8//! - Derived units like velocity are expressed as [`Per<N, D>`] (e.g. `Meter/Second`).
9//!
10//! Most users should depend on `qtty` (the facade crate) unless they need direct access to these primitives.
11//!
12//! # What this crate solves
13//!
14//! - Compile-time separation of dimensions (length vs time vs angle, …).
15//! - Zero runtime overhead for unit tags (phantom types only).
16//! - Full dimensional arithmetic: `m * m → m²`, `m / s → velocity`, `m² / m → m`.
17//! - Automatic compile-time verification that multiplied/divided quantities produce the correct dimension.
18//!
19//! # What this crate does not try to solve
20//!
21//! - Exact arithmetic (`Quantity` is `f64`).
22//! - General-purpose symbolic simplification of arbitrary unit expressions.
23//!
24//! # Quick start
25//!
26//! Convert between predefined units:
27//!
28//! ```rust
29//! use qtty_core::length::{Kilometers, Meter};
30//!
31//! let km = Kilometers::new(1.25);
32//! let m = km.to::<Meter>();
33//! assert!((m.value() - 1250.0).abs() < 1e-12);
34//! ```
35//!
36//! Compose derived units using `/`:
37//!
38//! ```rust
39//! use qtty_core::length::{Meter, Meters};
40//! use qtty_core::time::{Second, Seconds};
41//! use qtty_core::velocity::Velocity;
42//!
43//! let d = Meters::new(100.0);
44//! let t = Seconds::new(20.0);
45//! let v: Velocity<Meter, Second> = d / t;
46//! assert!((v.value() - 5.0).abs() < 1e-12);
47//! ```
48//!
49//! # `no_std`
50//!
51//! Disable default features to build `qtty-core` without `std`:
52//!
53//! ```toml
54//! [dependencies]
55//! qtty-core = { version = "0.1.0", default-features = false }
56//! ```
57//!
58//! When `std` is disabled, floating-point math that isn't available in `core` is provided via `libm`.
59//!
60//! # Feature flags
61//!
62//! - `std` (default): enables `std` support.
63//! - `serde`: enables `serde` support for `Quantity<U>`; serialization is the raw `f64` value only.
64//! - `pyo3`: enables PyO3 bindings for Python interop via `#[pyclass]` and `#[pymethods]`.
65//!
66//! # Panics and errors
67//!
68//! This crate does not define an error type and does not return `Result` from its core operations. Conversions and
69//! arithmetic are pure `f64` computations; they do not panic on their own, but they follow IEEE-754 behavior (NaN and
70//! infinities propagate according to the underlying operation).
71//!
72//! # SemVer and stability
73//!
74//! This crate is currently `0.x`. Expect breaking changes between minor versions until `1.0`.
75
76#![deny(missing_docs)]
77#![cfg_attr(not(feature = "std"), no_std)]
78#![forbid(unsafe_code)]
79
80#[cfg(not(feature = "std"))]
81extern crate libm;
82
83// ─────────────────────────────────────────────────────────────────────────────
84// Core modules
85// ─────────────────────────────────────────────────────────────────────────────
86
87mod dimension;
88#[cfg(feature = "diesel")]
89mod feature_diesel;
90#[cfg(feature = "pyo3")]
91mod feature_pyo3;
92#[cfg(feature = "serde")]
93mod feature_serde;
94#[cfg(feature = "tiberius")]
95mod feature_tiberius;
96mod macros;
97mod quantity;
98pub mod scalar;
99mod unit;
100
101// ─────────────────────────────────────────────────────────────────────────────
102// Public re-exports of core types
103// ─────────────────────────────────────────────────────────────────────────────
104
105pub use dimension::{
106    // Derived dimensions
107    Acceleration,
108    // Additional base dimensions (less commonly used)
109    AmountOfSubstance,
110    // Base dimensions
111    Angular,
112    Area,
113    Current,
114    Dim,
115    DimDiv,
116    DimMul,
117    Dimension,
118    Dimensionless,
119    DivDim,
120    Energy,
121    Force,
122    FrequencyDim,
123    Length,
124    LuminousIntensity,
125    Mass,
126    MulDim,
127    Power,
128    Temperature,
129    Time,
130    VelocityDim,
131    Volume,
132};
133pub use quantity::{
134    Quantity, Quantity32, Quantity64, QuantityI128, QuantityI16, QuantityI32, QuantityI64,
135    QuantityI8,
136};
137pub use scalar::{Exact, IntegerScalar, Real, Scalar, Transcendental};
138pub use unit::{Per, Prod, Simplify, Unit, Unitless};
139
140#[cfg(feature = "scalar-decimal")]
141pub use quantity::QuantityDecimal;
142
143#[cfg(feature = "scalar-rational")]
144pub use quantity::QuantityRational;
145
146#[cfg(feature = "serde")]
147pub use feature_serde::serde_with_unit;
148
149#[cfg(feature = "serde")]
150pub use feature_serde::serde_scalar;
151
152// ─────────────────────────────────────────────────────────────────────────────
153// Predefined unit modules (grouped by dimension)
154// ─────────────────────────────────────────────────────────────────────────────
155
156/// Predefined unit modules (grouped by dimension).
157///
158/// These are defined in `qtty-core` so they can implement formatting and helper traits without running into Rust's
159/// orphan rules.
160pub mod units;
161
162pub use units::angular;
163pub use units::area;
164pub use units::frequency;
165pub use units::length;
166pub use units::mass;
167pub use units::power;
168pub use units::time;
169pub use units::unitless;
170pub use units::velocity;
171pub use units::volume;
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    // ─────────────────────────────────────────────────────────────────────────────
178    // Test dimension and unit for lib.rs tests
179    // ─────────────────────────────────────────────────────────────────────────────
180
181    // Use Length as the test dimension (it's a type alias for Dim<P1, Z0, …>).
182    type TestDim = Length;
183
184    #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
185    pub enum TestUnit {}
186    impl Unit for TestUnit {
187        const RATIO: f64 = 1.0;
188        type Dim = TestDim;
189        const SYMBOL: &'static str = "tu";
190    }
191    impl core::fmt::Display for Quantity<TestUnit> {
192        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
193            write!(f, "{} tu", self.value())
194        }
195    }
196
197    #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
198    pub enum DoubleTestUnit {}
199    impl Unit for DoubleTestUnit {
200        const RATIO: f64 = 2.0;
201        type Dim = TestDim;
202        const SYMBOL: &'static str = "dtu";
203    }
204    impl core::fmt::Display for Quantity<DoubleTestUnit> {
205        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
206            write!(f, "{} dtu", self.value())
207        }
208    }
209
210    #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
211    pub enum HalfTestUnit {}
212    impl Unit for HalfTestUnit {
213        const RATIO: f64 = 0.5;
214        type Dim = TestDim;
215        const SYMBOL: &'static str = "htu";
216    }
217    impl core::fmt::Display for Quantity<HalfTestUnit> {
218        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
219            write!(f, "{} htu", self.value())
220        }
221    }
222
223    type TU = Quantity<TestUnit>;
224    type Dtu = Quantity<DoubleTestUnit>;
225
226    // ─────────────────────────────────────────────────────────────────────────────
227    // Quantity core behavior
228    // ─────────────────────────────────────────────────────────────────────────────
229
230    #[test]
231    fn quantity_new_and_value() {
232        let q = TU::new(42.0);
233        assert_eq!(q.value(), 42.0);
234    }
235
236    #[test]
237    fn quantity_nan_constant() {
238        assert!(TU::NAN.value().is_nan());
239    }
240
241    #[test]
242    fn quantity_abs() {
243        assert_eq!(TU::new(-5.0).abs().value(), 5.0);
244        assert_eq!(TU::new(5.0).abs().value(), 5.0);
245        assert_eq!(TU::new(0.0).abs().value(), 0.0);
246    }
247
248    #[test]
249    fn quantity_from_f64() {
250        let q: TU = 123.456.into();
251        assert_eq!(q.value(), 123.456);
252    }
253
254    // ─────────────────────────────────────────────────────────────────────────────
255    // Conversion via `to`
256    // ─────────────────────────────────────────────────────────────────────────────
257
258    #[test]
259    fn quantity_conversion_to_same_unit() {
260        let q = TU::new(10.0);
261        let converted = q.to::<TestUnit>();
262        assert_eq!(converted.value(), 10.0);
263    }
264
265    #[test]
266    fn quantity_conversion_to_different_unit() {
267        // 1 DoubleTestUnit = 2 TestUnit (in canonical terms)
268        // So 10 TU -> 10 * (1.0 / 2.0) = 5 DTU
269        let q = TU::new(10.0);
270        let converted = q.to::<DoubleTestUnit>();
271        assert!((converted.value() - 5.0).abs() < 1e-12);
272    }
273
274    #[test]
275    fn quantity_conversion_roundtrip() {
276        let original = TU::new(100.0);
277        let converted = original.to::<DoubleTestUnit>();
278        let back = converted.to::<TestUnit>();
279        assert!((back.value() - original.value()).abs() < 1e-12);
280    }
281
282    // ─────────────────────────────────────────────────────────────────────────────
283    // Const helper methods: add/sub/mul/div/min
284    // ─────────────────────────────────────────────────────────────────────────────
285
286    #[test]
287    fn const_add() {
288        let a = TU::new(3.0);
289        let b = TU::new(7.0);
290        assert_eq!(a.const_add(b).value(), 10.0);
291    }
292
293    #[test]
294    fn const_sub() {
295        let a = TU::new(10.0);
296        let b = TU::new(3.0);
297        assert_eq!(a.const_sub(b).value(), 7.0);
298    }
299
300    #[test]
301    fn const_mul() {
302        let a = TU::new(4.0);
303        let b = 5.0;
304        assert_eq!(a.const_mul(b).value(), 20.0);
305    }
306
307    #[test]
308    fn const_div() {
309        let a = TU::new(20.0);
310        let b = 4.0;
311        assert_eq!(a.const_div(b).value(), 5.0);
312    }
313
314    #[test]
315    fn const_min() {
316        let a = TU::new(5.0);
317        let b = TU::new(3.0);
318        assert_eq!(a.min_const(b).value(), 3.0);
319        assert_eq!(b.min_const(a).value(), 3.0);
320    }
321
322    // ─────────────────────────────────────────────────────────────────────────────
323    // Operator traits: Add, Sub, Mul, Div, Neg, Rem
324    // ─────────────────────────────────────────────────────────────────────────────
325
326    #[test]
327    fn operator_add() {
328        let a = TU::new(3.0);
329        let b = TU::new(7.0);
330        assert_eq!((a + b).value(), 10.0);
331    }
332
333    #[test]
334    fn operator_sub() {
335        let a = TU::new(10.0);
336        let b = TU::new(3.0);
337        assert_eq!((a - b).value(), 7.0);
338    }
339
340    #[test]
341    fn operator_mul_by_f64() {
342        let q = TU::new(5.0);
343        assert_eq!((q * 3.0).value(), 15.0);
344        assert_eq!((3.0 * q).value(), 15.0);
345    }
346
347    #[test]
348    fn operator_div_by_f64() {
349        let q = TU::new(15.0);
350        assert_eq!((q / 3.0).value(), 5.0);
351    }
352
353    #[test]
354    fn operator_neg() {
355        let q = TU::new(5.0);
356        assert_eq!((-q).value(), -5.0);
357        assert_eq!((-(-q)).value(), 5.0);
358    }
359
360    #[test]
361    fn operator_rem() {
362        let q = TU::new(10.0);
363        assert_eq!((q % 3.0).value(), 1.0);
364    }
365
366    // ─────────────────────────────────────────────────────────────────────────────
367    // Assignment operators: AddAssign, SubAssign, DivAssign
368    // ─────────────────────────────────────────────────────────────────────────────
369
370    #[test]
371    fn operator_add_assign() {
372        let mut q = TU::new(5.0);
373        q += TU::new(3.0);
374        assert_eq!(q.value(), 8.0);
375    }
376
377    #[test]
378    fn operator_sub_assign() {
379        let mut q = TU::new(10.0);
380        q -= TU::new(3.0);
381        assert_eq!(q.value(), 7.0);
382    }
383
384    #[test]
385    fn operator_div_assign() {
386        let mut q = TU::new(20.0);
387        q /= TU::new(4.0);
388        assert_eq!(q.value(), 5.0);
389    }
390
391    // ─────────────────────────────────────────────────────────────────────────────
392    // PartialEq<f64>
393    // ─────────────────────────────────────────────────────────────────────────────
394
395    #[test]
396    fn partial_eq_f64() {
397        let q = TU::new(5.0);
398        assert!(q == 5.0);
399        assert!(!(q == 4.0));
400    }
401
402    // ─────────────────────────────────────────────────────────────────────────────
403    // Division yielding Per<N, D>
404    // ─────────────────────────────────────────────────────────────────────────────
405
406    #[test]
407    fn division_creates_per_type() {
408        let num = TU::new(100.0);
409        let den = Dtu::new(20.0);
410        let ratio: Quantity<Per<TestUnit, DoubleTestUnit>> = num / den;
411        assert!((ratio.value() - 5.0).abs() < 1e-12);
412    }
413
414    #[test]
415    fn per_ratio_conversion() {
416        let v1: Quantity<Per<DoubleTestUnit, TestUnit>> = Quantity::new(10.0);
417        let v2: Quantity<Per<TestUnit, TestUnit>> = v1.to();
418        assert!((v2.value() - 20.0).abs() < 1e-12);
419    }
420
421    #[test]
422    fn per_multiplication_recovers_numerator() {
423        let rate: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(5.0);
424        let time = Dtu::new(4.0);
425        // Blanket Mul gives Quantity<Prod<Per<TU,DTU>, DTU>>; .to() converts back to TU
426        // because Prod<Per<TU,DTU>, DTU> has dimension Length (same as TU).
427        let result: TU = (rate * time).to();
428        assert!((result.value() - 20.0).abs() < 1e-12);
429    }
430
431    #[test]
432    fn per_multiplication_commutative() {
433        let rate: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(5.0);
434        let time = Dtu::new(4.0);
435        let result1: TU = (rate * time).to();
436        let result2: TU = (time * rate).to();
437        assert!((result1.value() - result2.value()).abs() < 1e-12);
438    }
439
440    // ─────────────────────────────────────────────────────────────────────────────
441    // Simplify trait
442    // ─────────────────────────────────────────────────────────────────────────────
443
444    #[test]
445    fn simplify_per_u_u_to_unitless() {
446        let ratio: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(1.23456);
447        let unitless: Quantity<Unitless> = ratio.simplify();
448        assert!((unitless.value() - 1.23456).abs() < 1e-12);
449    }
450
451    #[test]
452    fn simplify_per_n_per_n_d_to_d() {
453        let q: Quantity<Per<TestUnit, Per<TestUnit, DoubleTestUnit>>> = Quantity::new(7.5);
454        let simplified: Dtu = q.simplify();
455        assert!((simplified.value() - 7.5).abs() < 1e-12);
456    }
457
458    // ─────────────────────────────────────────────────────────────────────────────
459    // Quantity<Per<U,U>>::asin()
460    // ─────────────────────────────────────────────────────────────────────────────
461
462    #[test]
463    fn per_u_u_asin() {
464        let ratio: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(0.5);
465        let result = ratio.asin();
466        assert!((result - 0.5_f64.asin()).abs() < 1e-12);
467    }
468
469    #[test]
470    fn per_u_u_asin_boundary_values() {
471        let one: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(1.0);
472        assert!((one.asin() - core::f64::consts::FRAC_PI_2).abs() < 1e-12);
473
474        let neg_one: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(-1.0);
475        assert!((neg_one.asin() - (-core::f64::consts::FRAC_PI_2)).abs() < 1e-12);
476
477        let zero: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(0.0);
478        assert!((zero.asin() - 0.0).abs() < 1e-12);
479    }
480
481    // ─────────────────────────────────────────────────────────────────────────────
482    // Display formatting
483    // ─────────────────────────────────────────────────────────────────────────────
484
485    #[test]
486    fn display_simple_quantity() {
487        let q = TU::new(42.5);
488        let s = format!("{}", q);
489        assert_eq!(s, "42.5 tu");
490    }
491
492    #[test]
493    fn display_per_quantity() {
494        let q: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(2.5);
495        let s = format!("{}", q);
496        assert_eq!(s, "2.5 tu/dtu");
497    }
498
499    #[test]
500    fn display_negative_value() {
501        let q = TU::new(-99.9);
502        let s = format!("{}", q);
503        assert_eq!(s, "-99.9 tu");
504    }
505
506    // ─────────────────────────────────────────────────────────────────────────────
507    // Edge cases
508    // ─────────────────────────────────────────────────────────────────────────────
509
510    #[test]
511    fn edge_case_zero() {
512        let zero = TU::new(0.0);
513        assert_eq!(zero.value(), 0.0);
514        assert_eq!((-zero).value(), 0.0);
515        assert_eq!(zero.abs().value(), 0.0);
516    }
517
518    #[test]
519    fn edge_case_negative_values() {
520        let neg = TU::new(-10.0);
521        let pos = TU::new(5.0);
522
523        assert_eq!((neg + pos).value(), -5.0);
524        assert_eq!((neg - pos).value(), -15.0);
525        assert_eq!((neg * 2.0).value(), -20.0);
526        assert_eq!(neg.abs().value(), 10.0);
527    }
528
529    #[test]
530    fn edge_case_large_values() {
531        let large = TU::new(1e100);
532        let small = TU::new(1e-100);
533        assert_eq!(large.value(), 1e100);
534        assert_eq!(small.value(), 1e-100);
535    }
536
537    #[test]
538    fn edge_case_infinity() {
539        let inf = TU::new(f64::INFINITY);
540        let neg_inf = TU::new(f64::NEG_INFINITY);
541
542        assert!(inf.value().is_infinite());
543        assert!(neg_inf.value().is_infinite());
544        assert_eq!(inf.value().signum(), 1.0);
545        assert_eq!(neg_inf.value().signum(), -1.0);
546    }
547
548    // ─────────────────────────────────────────────────────────────────────────────
549    // Serde tests
550    // ─────────────────────────────────────────────────────────────────────────────
551
552    #[cfg(feature = "serde")]
553    mod serde_tests {
554        use super::*;
555        use serde::{Deserialize, Serialize};
556
557        #[test]
558        fn serialize_quantity() {
559            let q = TU::new(42.5);
560            let json = serde_json::to_string(&q).unwrap();
561            assert_eq!(json, "42.5");
562        }
563
564        #[test]
565        fn deserialize_quantity() {
566            let json = "42.5";
567            let q: TU = serde_json::from_str(json).unwrap();
568            assert_eq!(q.value(), 42.5);
569        }
570
571        #[test]
572        fn serde_roundtrip() {
573            let original = TU::new(123.456);
574            let json = serde_json::to_string(&original).unwrap();
575            let restored: TU = serde_json::from_str(&json).unwrap();
576            assert!((restored.value() - original.value()).abs() < 1e-12);
577        }
578
579        // ─────────────────────────────────────────────────────────────────────────
580        // serde_with_unit module tests
581        // ─────────────────────────────────────────────────────────────────────────
582
583        #[derive(Serialize, Deserialize, Debug)]
584        struct TestStruct {
585            #[serde(with = "crate::serde_with_unit")]
586            distance: TU,
587        }
588
589        #[test]
590        fn serde_with_unit_serialize() {
591            let data = TestStruct {
592                distance: TU::new(42.5),
593            };
594            let json = serde_json::to_string(&data).unwrap();
595            assert!(json.contains("\"value\""));
596            assert!(json.contains("\"unit\""));
597            assert!(json.contains("42.5"));
598            assert!(json.contains("\"tu\""));
599        }
600
601        #[test]
602        fn serde_with_unit_deserialize() {
603            let json = r#"{"distance":{"value":42.5,"unit":"tu"}}"#;
604            let data: TestStruct = serde_json::from_str(json).unwrap();
605            assert_eq!(data.distance.value(), 42.5);
606        }
607
608        #[test]
609        fn serde_with_unit_deserialize_no_unit_field() {
610            // Should work without unit field for backwards compatibility
611            let json = r#"{"distance":{"value":42.5}}"#;
612            let data: TestStruct = serde_json::from_str(json).unwrap();
613            assert_eq!(data.distance.value(), 42.5);
614        }
615
616        #[test]
617        fn serde_with_unit_deserialize_wrong_unit() {
618            let json = r#"{"distance":{"value":42.5,"unit":"wrong"}}"#;
619            let result: Result<TestStruct, _> = serde_json::from_str(json);
620            assert!(result.is_err());
621            let err_msg = result.unwrap_err().to_string();
622            assert!(err_msg.contains("unit mismatch") || err_msg.contains("expected"));
623        }
624
625        #[test]
626        fn serde_with_unit_deserialize_missing_value() {
627            let json = r#"{"distance":{"unit":"tu"}}"#;
628            let result: Result<TestStruct, _> = serde_json::from_str(json);
629            assert!(result.is_err());
630            let err_msg = result.unwrap_err().to_string();
631            assert!(err_msg.contains("missing field") || err_msg.contains("value"));
632        }
633
634        #[test]
635        fn serde_with_unit_deserialize_duplicate_value() {
636            let json = r#"{"distance":{"value":42.5,"value":100.0,"unit":"tu"}}"#;
637            let result: Result<TestStruct, _> = serde_json::from_str(json);
638            // This should either error or use one of the values (implementation-dependent)
639            // but we're testing that it doesn't panic
640            let _ = result;
641        }
642
643        #[test]
644        fn serde_with_unit_deserialize_duplicate_unit() {
645            let json = r#"{"distance":{"value":42.5,"unit":"tu","unit":"tu"}}"#;
646            let result: Result<TestStruct, _> = serde_json::from_str(json);
647            // Similar to above - just ensure no panic
648            let _ = result;
649        }
650
651        #[test]
652        fn serde_with_unit_deserialize_invalid_format() {
653            // Test the expecting() method by providing wrong format
654            let json = r#"{"distance":"not_an_object"}"#;
655            let result: Result<TestStruct, _> = serde_json::from_str(json);
656            assert!(result.is_err());
657        }
658
659        #[test]
660        fn serde_with_unit_deserialize_array() {
661            // Test the expecting() method with array format
662            let json = r#"{"distance":[42.5, "tu"]}"#;
663            let result: Result<TestStruct, _> = serde_json::from_str(json);
664            assert!(result.is_err());
665        }
666
667        #[test]
668        fn serde_with_unit_roundtrip() {
669            let original = TestStruct {
670                distance: TU::new(123.456),
671            };
672            let json = serde_json::to_string(&original).unwrap();
673            let restored: TestStruct = serde_json::from_str(&json).unwrap();
674            assert!((restored.distance.value() - original.distance.value()).abs() < 1e-12);
675        }
676
677        #[test]
678        fn serde_with_unit_special_values() {
679            // Note: JSON doesn't support Infinity and NaN natively.
680            // serde_json serializes them as null, which can't be deserialized
681            // back to f64. So we'll test with very large numbers instead.
682            let test_large = TestStruct {
683                distance: TU::new(1e100),
684            };
685            let json = serde_json::to_string(&test_large).unwrap();
686            let restored: TestStruct = serde_json::from_str(&json).unwrap();
687            assert!((restored.distance.value() - 1e100).abs() < 1e88);
688
689            let test_small = TestStruct {
690                distance: TU::new(-1e-100),
691            };
692            let json = serde_json::to_string(&test_small).unwrap();
693            let restored: TestStruct = serde_json::from_str(&json).unwrap();
694            assert!((restored.distance.value() + 1e-100).abs() < 1e-112);
695        }
696    }
697}