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    #[test]
324    fn const_max() {
325        let a = TU::new(3.0);
326        let b = TU::new(5.0);
327        // When self < other, max_const returns other (the else branch)
328        assert_eq!(a.max_const(b).value(), 5.0);
329        // When self > other, max_const returns self (the if branch)
330        assert_eq!(b.max_const(a).value(), 5.0);
331    }
332
333    #[test]
334    fn sum_quantities_owned() {
335        let qs = vec![TU::new(1.0), TU::new(2.0), TU::new(3.0)];
336        let total: TU = qs.into_iter().sum();
337        assert_eq!(total.value(), 6.0);
338    }
339
340    #[test]
341    fn sum_quantities_by_ref() {
342        let qs = [TU::new(1.0), TU::new(2.0), TU::new(3.0)];
343        let total: TU = qs.iter().sum();
344        assert_eq!(total.value(), 6.0);
345    }
346
347    #[test]
348    fn sum_quantities_into_f64() {
349        let qs = vec![TU::new(1.0), TU::new(2.0), TU::new(3.0)];
350        let total: f64 = qs.into_iter().sum();
351        assert_eq!(total, 6.0);
352    }
353
354    // ─────────────────────────────────────────────────────────────────────────────
355    // Operator traits: Add, Sub, Mul, Div, Neg, Rem
356    // ─────────────────────────────────────────────────────────────────────────────
357
358    #[test]
359    fn operator_add() {
360        let a = TU::new(3.0);
361        let b = TU::new(7.0);
362        assert_eq!((a + b).value(), 10.0);
363    }
364
365    #[test]
366    fn operator_sub() {
367        let a = TU::new(10.0);
368        let b = TU::new(3.0);
369        assert_eq!((a - b).value(), 7.0);
370    }
371
372    #[test]
373    fn operator_mul_by_f64() {
374        let q = TU::new(5.0);
375        assert_eq!((q * 3.0).value(), 15.0);
376        assert_eq!((3.0 * q).value(), 15.0);
377    }
378
379    #[test]
380    fn operator_div_by_f64() {
381        let q = TU::new(15.0);
382        assert_eq!((q / 3.0).value(), 5.0);
383    }
384
385    #[test]
386    fn operator_neg() {
387        let q = TU::new(5.0);
388        assert_eq!((-q).value(), -5.0);
389        assert_eq!((-(-q)).value(), 5.0);
390    }
391
392    #[test]
393    fn operator_rem() {
394        let q = TU::new(10.0);
395        assert_eq!((q % 3.0).value(), 1.0);
396    }
397
398    // ─────────────────────────────────────────────────────────────────────────────
399    // Assignment operators: AddAssign, SubAssign, DivAssign
400    // ─────────────────────────────────────────────────────────────────────────────
401
402    #[test]
403    fn operator_add_assign() {
404        let mut q = TU::new(5.0);
405        q += TU::new(3.0);
406        assert_eq!(q.value(), 8.0);
407    }
408
409    #[test]
410    fn operator_sub_assign() {
411        let mut q = TU::new(10.0);
412        q -= TU::new(3.0);
413        assert_eq!(q.value(), 7.0);
414    }
415
416    #[test]
417    fn operator_div_assign() {
418        let mut q = TU::new(20.0);
419        q /= 4.0;
420        assert_eq!(q.value(), 5.0);
421    }
422
423    // ─────────────────────────────────────────────────────────────────────────────
424    // PartialEq<f64>
425    // ─────────────────────────────────────────────────────────────────────────────
426
427    #[test]
428    fn partial_eq_f64() {
429        let q = TU::new(5.0);
430        assert!(q == 5.0);
431        assert!(!(q == 4.0));
432    }
433
434    // ─────────────────────────────────────────────────────────────────────────────
435    // Division yielding Per<N, D>
436    // ─────────────────────────────────────────────────────────────────────────────
437
438    #[test]
439    fn division_creates_per_type() {
440        let num = TU::new(100.0);
441        let den = Dtu::new(20.0);
442        let ratio: Quantity<Per<TestUnit, DoubleTestUnit>> = num / den;
443        assert!((ratio.value() - 5.0).abs() < 1e-12);
444    }
445
446    #[test]
447    fn per_ratio_conversion() {
448        let v1: Quantity<Per<DoubleTestUnit, TestUnit>> = Quantity::new(10.0);
449        let v2: Quantity<Per<TestUnit, TestUnit>> = v1.to();
450        assert!((v2.value() - 20.0).abs() < 1e-12);
451    }
452
453    #[test]
454    fn per_multiplication_recovers_numerator() {
455        let rate: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(5.0);
456        let time = Dtu::new(4.0);
457        // Blanket Mul gives Quantity<Prod<Per<TU,DTU>, DTU>>; .to() converts back to TU
458        // because Prod<Per<TU,DTU>, DTU> has dimension Length (same as TU).
459        let result: TU = (rate * time).to();
460        assert!((result.value() - 20.0).abs() < 1e-12);
461    }
462
463    #[test]
464    fn per_multiplication_commutative() {
465        let rate: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(5.0);
466        let time = Dtu::new(4.0);
467        let result1: TU = (rate * time).to();
468        let result2: TU = (time * rate).to();
469        assert!((result1.value() - result2.value()).abs() < 1e-12);
470    }
471
472    // ─────────────────────────────────────────────────────────────────────────────
473    // Simplify trait
474    // ─────────────────────────────────────────────────────────────────────────────
475
476    #[test]
477    fn simplify_per_u_u_to_unitless() {
478        let ratio: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(1.23456);
479        let unitless: Quantity<Unitless> = ratio.simplify();
480        assert!((unitless.value() - 1.23456).abs() < 1e-12);
481    }
482
483    #[test]
484    fn simplify_per_n_per_n_d_to_d() {
485        let q: Quantity<Per<TestUnit, Per<TestUnit, DoubleTestUnit>>> = Quantity::new(7.5);
486        let simplified: Dtu = q.simplify();
487        assert!((simplified.value() - 7.5).abs() < 1e-12);
488    }
489
490    // ─────────────────────────────────────────────────────────────────────────────
491    // Quantity<Per<U,U>>::asin()
492    // ─────────────────────────────────────────────────────────────────────────────
493
494    #[test]
495    fn per_u_u_asin() {
496        let ratio: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(0.5);
497        let result = ratio.asin();
498        assert!((result - 0.5_f64.asin()).abs() < 1e-12);
499    }
500
501    #[test]
502    fn per_u_u_asin_boundary_values() {
503        let one: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(1.0);
504        assert!((one.asin() - core::f64::consts::FRAC_PI_2).abs() < 1e-12);
505
506        let neg_one: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(-1.0);
507        assert!((neg_one.asin() - (-core::f64::consts::FRAC_PI_2)).abs() < 1e-12);
508
509        let zero: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(0.0);
510        assert!((zero.asin() - 0.0).abs() < 1e-12);
511    }
512
513    // ─────────────────────────────────────────────────────────────────────────────
514    // Display formatting
515    // ─────────────────────────────────────────────────────────────────────────────
516
517    #[test]
518    fn display_simple_quantity() {
519        let q = TU::new(42.5);
520        let s = format!("{}", q);
521        assert_eq!(s, "42.5 tu");
522    }
523
524    #[test]
525    fn display_per_quantity() {
526        let q: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(2.5);
527        let s = format!("{}", q);
528        assert_eq!(s, "2.5 tu/dtu");
529    }
530
531    #[test]
532    fn display_negative_value() {
533        let q = TU::new(-99.9);
534        let s = format!("{}", q);
535        assert_eq!(s, "-99.9 tu");
536    }
537
538    #[test]
539    fn display_double_test_unit() {
540        let q = Dtu::new(2.5);
541        let s = format!("{}", q);
542        assert_eq!(s, "2.5 dtu");
543    }
544
545    #[test]
546    fn display_half_test_unit() {
547        type Htu = Quantity<HalfTestUnit>;
548        let q = Htu::new(3.0);
549        let s = format!("{}", q);
550        assert_eq!(s, "3 htu");
551    }
552
553    // ─────────────────────────────────────────────────────────────────────────────
554    // Edge cases
555    // ─────────────────────────────────────────────────────────────────────────────
556
557    #[test]
558    fn edge_case_zero() {
559        let zero = TU::new(0.0);
560        assert_eq!(zero.value(), 0.0);
561        assert_eq!((-zero).value(), 0.0);
562        assert_eq!(zero.abs().value(), 0.0);
563    }
564
565    #[test]
566    fn edge_case_negative_values() {
567        let neg = TU::new(-10.0);
568        let pos = TU::new(5.0);
569
570        assert_eq!((neg + pos).value(), -5.0);
571        assert_eq!((neg - pos).value(), -15.0);
572        assert_eq!((neg * 2.0).value(), -20.0);
573        assert_eq!(neg.abs().value(), 10.0);
574    }
575
576    #[test]
577    fn edge_case_large_values() {
578        let large = TU::new(1e100);
579        let small = TU::new(1e-100);
580        assert_eq!(large.value(), 1e100);
581        assert_eq!(small.value(), 1e-100);
582    }
583
584    #[test]
585    fn edge_case_infinity() {
586        let inf = TU::new(f64::INFINITY);
587        let neg_inf = TU::new(f64::NEG_INFINITY);
588
589        assert!(inf.value().is_infinite());
590        assert!(neg_inf.value().is_infinite());
591        assert_eq!(inf.value().signum(), 1.0);
592        assert_eq!(neg_inf.value().signum(), -1.0);
593    }
594
595    // ─────────────────────────────────────────────────────────────────────────────
596    // Serde tests
597    // ─────────────────────────────────────────────────────────────────────────────
598
599    #[cfg(feature = "serde")]
600    mod serde_tests {
601        use super::*;
602        use serde::{Deserialize, Serialize};
603
604        #[test]
605        fn serialize_quantity() {
606            let q = TU::new(42.5);
607            let json = serde_json::to_string(&q).unwrap();
608            assert_eq!(json, "42.5");
609        }
610
611        #[test]
612        fn deserialize_quantity() {
613            let json = "42.5";
614            let q: TU = serde_json::from_str(json).unwrap();
615            assert_eq!(q.value(), 42.5);
616        }
617
618        #[test]
619        fn serde_roundtrip() {
620            let original = TU::new(123.456);
621            let json = serde_json::to_string(&original).unwrap();
622            let restored: TU = serde_json::from_str(&json).unwrap();
623            assert!((restored.value() - original.value()).abs() < 1e-12);
624        }
625
626        // ─────────────────────────────────────────────────────────────────────────
627        // serde_with_unit module tests
628        // ─────────────────────────────────────────────────────────────────────────
629
630        #[derive(Serialize, Deserialize, Debug)]
631        struct TestStruct {
632            #[serde(with = "crate::serde_with_unit")]
633            distance: TU,
634        }
635
636        #[test]
637        fn serde_with_unit_serialize() {
638            let data = TestStruct {
639                distance: TU::new(42.5),
640            };
641            let json = serde_json::to_string(&data).unwrap();
642            assert!(json.contains("\"value\""));
643            assert!(json.contains("\"unit\""));
644            assert!(json.contains("42.5"));
645            assert!(json.contains("\"tu\""));
646        }
647
648        #[test]
649        fn serde_with_unit_deserialize() {
650            let json = r#"{"distance":{"value":42.5,"unit":"tu"}}"#;
651            let data: TestStruct = serde_json::from_str(json).unwrap();
652            assert_eq!(data.distance.value(), 42.5);
653        }
654
655        #[test]
656        fn serde_with_unit_deserialize_no_unit_field() {
657            // Should work without unit field for backwards compatibility
658            let json = r#"{"distance":{"value":42.5}}"#;
659            let data: TestStruct = serde_json::from_str(json).unwrap();
660            assert_eq!(data.distance.value(), 42.5);
661        }
662
663        #[test]
664        fn serde_with_unit_deserialize_wrong_unit() {
665            let json = r#"{"distance":{"value":42.5,"unit":"wrong"}}"#;
666            let result: Result<TestStruct, _> = serde_json::from_str(json);
667            assert!(result.is_err());
668            let err_msg = result.unwrap_err().to_string();
669            assert!(err_msg.contains("unit mismatch") || err_msg.contains("expected"));
670        }
671
672        #[test]
673        fn serde_with_unit_deserialize_missing_value() {
674            let json = r#"{"distance":{"unit":"tu"}}"#;
675            let result: Result<TestStruct, _> = serde_json::from_str(json);
676            assert!(result.is_err());
677            let err_msg = result.unwrap_err().to_string();
678            assert!(err_msg.contains("missing field") || err_msg.contains("value"));
679        }
680
681        #[test]
682        fn serde_with_unit_deserialize_duplicate_value() {
683            let json = r#"{"distance":{"value":42.5,"value":100.0,"unit":"tu"}}"#;
684            let result: Result<TestStruct, _> = serde_json::from_str(json);
685            // This should either error or use one of the values (implementation-dependent)
686            // but we're testing that it doesn't panic
687            let _ = result;
688        }
689
690        #[test]
691        fn serde_with_unit_deserialize_duplicate_unit() {
692            let json = r#"{"distance":{"value":42.5,"unit":"tu","unit":"tu"}}"#;
693            let result: Result<TestStruct, _> = serde_json::from_str(json);
694            // Similar to above - just ensure no panic
695            let _ = result;
696        }
697
698        #[test]
699        fn serde_with_unit_deserialize_invalid_format() {
700            // Test the expecting() method by providing wrong format
701            let json = r#"{"distance":"not_an_object"}"#;
702            let result: Result<TestStruct, _> = serde_json::from_str(json);
703            assert!(result.is_err());
704        }
705
706        #[test]
707        fn serde_with_unit_deserialize_array() {
708            // Test the expecting() method with array format
709            let json = r#"{"distance":[42.5, "tu"]}"#;
710            let result: Result<TestStruct, _> = serde_json::from_str(json);
711            assert!(result.is_err());
712        }
713
714        #[test]
715        fn serde_with_unit_roundtrip() {
716            let original = TestStruct {
717                distance: TU::new(123.456),
718            };
719            let json = serde_json::to_string(&original).unwrap();
720            let restored: TestStruct = serde_json::from_str(&json).unwrap();
721            assert!((restored.distance.value() - original.distance.value()).abs() < 1e-12);
722        }
723
724        #[test]
725        fn serde_with_unit_special_values() {
726            // Note: JSON doesn't support Infinity and NaN natively.
727            // serde_json serializes them as null, which can't be deserialized
728            // back to f64. So we'll test with very large numbers instead.
729            let test_large = TestStruct {
730                distance: TU::new(1e100),
731            };
732            let json = serde_json::to_string(&test_large).unwrap();
733            let restored: TestStruct = serde_json::from_str(&json).unwrap();
734            assert!((restored.distance.value() - 1e100).abs() < 1e88);
735
736            let test_small = TestStruct {
737                distance: TU::new(-1e-100),
738            };
739            let json = serde_json::to_string(&test_small).unwrap();
740            let restored: TestStruct = serde_json::from_str(&json).unwrap();
741            assert!((restored.distance.value() + 1e-100).abs() < 1e-112);
742        }
743    }
744}