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