Skip to main content

qtty_core/units/area/
mod.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (C) 2026 Vallés Puig, Ramon
3
4//! Area units.
5//!
6//! The canonical scaling unit for this dimension is the **square metre** (`SquareMeter::RATIO == 1.0`).
7//! All other area units are expressed as exact ratios to square metres.
8//!
9//! This module provides:
10//!
11//! - **Metric squares**: square millimetre, square centimetre, square metre, square kilometre.
12//! - **Land measurement**: hectare, are.
13//! - **Imperial/US**: square inch, square foot, square yard, square mile, acre.
14//!
15//! Area units arise *directly* from multiplying two length quantities — no
16//! conversion needed:
17//!
18//! ```rust
19//! use qtty_core::length::Meters;
20//! use qtty_core::area::SquareMeters;
21//!
22//! let side = Meters::new(5.0);
23//! let area: SquareMeters = side * side;             // Direct product
24//! assert!((area.value() - 25.0).abs() < 1e-12);
25//! ```
26//!
27//! ## All area units (default)
28//!
29//! ```rust
30//! use qtty_core::area::*;
31//!
32//! macro_rules! touch {
33//!     ($T:ty, $v:expr) => {{ let q = <$T>::new($v); let _c = q; assert!(q == q); }};
34//! }
35//!
36//! touch!(SquareMeters, 1.0);     touch!(SquareKilometers, 1.0);
37//! touch!(SquareCentimeters, 1.0);touch!(SquareMillimeters, 1.0);
38//! ```
39
40use crate::units::length::{Centimeter, Kilometer, Meter, Millimeter};
41use crate::{Prod, Quantity, Unit};
42
43/// Re-export the area dimension from the dimension module.
44pub use crate::dimension::Area;
45
46/// Marker trait for any [`Unit`] whose dimension is [`Area`].
47pub trait AreaUnit: Unit<Dim = Area> {}
48impl<T: Unit<Dim = Area>> AreaUnit for T {}
49
50/// A composed area quantity from squaring a length unit.
51///
52/// `SquareOf<L>` is `Quantity<Prod<L, L>>`. Since the metric area types are
53/// themselves `Prod` aliases, `SquareOf<Meter>` and [`SquareMeters`] are the
54/// **same type**.
55///
56/// # Examples
57///
58/// ```rust
59/// use qtty_core::area::{SquareOf, SquareMeters};
60/// use qtty_core::length::{Meter, Meters};
61///
62/// let side = Meters::new(5.0);
63/// let area: SquareOf<Meter> = side * side;
64/// assert!((area.value() - 25.0).abs() < 1e-12);
65///
66/// // SquareOf<Meter> IS SquareMeters — same type:
67/// let named: SquareMeters = area;
68/// assert!((named.value() - 25.0).abs() < 1e-12);
69/// ```
70pub type SquareOf<L> = Quantity<Prod<L, L>>;
71
72#[cfg(feature = "land-area")]
73mod land_area;
74#[cfg(feature = "land-area")]
75pub use land_area::*;
76#[cfg(feature = "customary")]
77mod customary;
78#[cfg(feature = "customary")]
79pub use customary::*;
80
81// ─────────────────────────────────────────────────────────────────────────────
82// SI / metric area units
83// ─────────────────────────────────────────────────────────────────────────────
84
85/// Square metre — product of two [`Meter`] units (1 m²).
86pub type SquareMeter = Prod<Meter, Meter>;
87/// A quantity measured in square metres (= [`SquareOf<Meter>`]).
88pub type SquareMeters = Quantity<SquareMeter>;
89
90/// Square kilometre — product of two [`Kilometer`] units (10⁶ m²).
91pub type SquareKilometer = Prod<Kilometer, Kilometer>;
92/// A quantity measured in square kilometres.
93pub type SquareKilometers = Quantity<SquareKilometer>;
94
95/// Square centimetre — product of two [`Centimeter`] units (10⁻⁴ m²).
96pub type SquareCentimeter = Prod<Centimeter, Centimeter>;
97/// A quantity measured in square centimetres.
98pub type SquareCentimeters = Quantity<SquareCentimeter>;
99
100/// Square millimetre — product of two [`Millimeter`] units (10⁻⁶ m²).
101pub type SquareMillimeter = Prod<Millimeter, Millimeter>;
102/// A quantity measured in square millimetres.
103pub type SquareMillimeters = Quantity<SquareMillimeter>;
104
105// ─────────────────────────────────────────────────────────────────────────────
106// Imperial / US customary area units
107// ─────────────────────────────────────────────────────────────────────────────
108
109/// Canonical list of all area units.
110///
111/// Pass a macro identifier as the single argument; it will be invoked with all
112/// area unit types as its token list. Drives:
113/// - `impl_unit_from_conversions!` — bidirectional `From` impls between all pairs.
114/// - `impl_unit_cross_unit_ops!` — cross-unit `PartialEq`/`PartialOrd` (feature-gated).
115/// - `assert_units_are_builtin!` — compile-time check that every unit is in
116///   `register_builtin_units!` (under `#[cfg(test)]`).
117///
118/// The macro is exported (`#[doc(hidden)]`) so the `qtty` facade can use it
119/// in compile-time consistency checks (`inventory_consistency.rs`).
120///
121/// ```rust,ignore
122/// area_units!(crate::impl_unit_from_conversions);
123/// ```
124#[macro_export]
125#[doc(hidden)]
126macro_rules! area_units {
127    ($cb:path) => {
128        $cb!(
129            SquareMeter,
130            SquareKilometer,
131            SquareCentimeter,
132            SquareMillimeter
133        );
134    };
135}
136
137// Generate all bidirectional From implementations between area units.
138area_units!(crate::impl_unit_from_conversions);
139
140// Optional cross-unit operator support (`==`, `<`, etc.).
141#[cfg(feature = "cross-unit-ops")]
142area_units!(crate::impl_unit_cross_unit_ops);
143
144// ── Cross-feature: customary × land-area ─────────────────────────────────────
145#[cfg(all(feature = "customary", feature = "land-area"))]
146crate::__impl_from_each_extra_to_bases!(
147    {SquareInch, SquareFoot, SquareYard, SquareMile}
148    Hectare, Are, Acre
149);
150#[cfg(all(
151    feature = "customary",
152    feature = "land-area",
153    feature = "cross-unit-ops"
154))]
155crate::__impl_cross_ops_each_extra_to_bases!(
156    {SquareInch, SquareFoot, SquareYard, SquareMile}
157    Hectare, Are, Acre
158);
159
160// Compile-time check: every unit in the inventory is registered as BuiltinUnit.
161#[cfg(test)]
162area_units!(crate::assert_units_are_builtin);
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use approx::assert_abs_diff_eq;
168
169    #[test]
170    fn sqm_to_sqkm() {
171        let a = SquareMeters::new(1_000_000.0);
172        let b: SquareKilometers = a.to();
173        assert_abs_diff_eq!(b.value(), 1.0, epsilon = 1e-12);
174    }
175
176    #[test]
177    #[cfg(feature = "land-area")]
178    fn hectare_to_sqm() {
179        let a = Hectares::new(1.0);
180        let b: SquareMeters = a.to();
181        assert_abs_diff_eq!(b.value(), 10_000.0, epsilon = 1e-9);
182    }
183
184    #[test]
185    #[cfg(feature = "land-area")]
186    fn acre_to_hectare() {
187        let a = Acres::new(1.0);
188        let b: Hectares = a.to();
189        assert_abs_diff_eq!(b.value(), 0.404_685_642_24, epsilon = 1e-9);
190    }
191
192    #[test]
193    #[cfg(feature = "customary")]
194    fn sqft_to_sqm() {
195        let a = SquareFeet::new(1.0);
196        let b: SquareMeters = a.to();
197        assert_abs_diff_eq!(b.value(), 0.092_903_04, epsilon = 1e-9);
198    }
199
200    #[test]
201    fn length_product_is_area() {
202        use crate::length::Meters;
203
204        let side = Meters::new(5.0);
205        let area: SquareMeters = side * side; // direct — no .to() needed
206        assert_abs_diff_eq!(area.value(), 25.0, epsilon = 1e-12);
207    }
208
209    #[test]
210    #[cfg(feature = "customary")]
211    fn sqmile_to_sqkm() {
212        let a = SquareMiles::new(1.0);
213        let b: SquareKilometers = a.to();
214        assert_abs_diff_eq!(b.value(), 2.589_988_110_336, epsilon = 1e-6);
215    }
216
217    #[test]
218    fn sqcm_to_sqm() {
219        let a = SquareCentimeters::new(10_000.0);
220        let b: SquareMeters = a.to();
221        assert_abs_diff_eq!(b.value(), 1.0, epsilon = 1e-12);
222    }
223
224    #[test]
225    fn sqmm_to_sqcm() {
226        let a = SquareMillimeters::new(100.0);
227        let b: SquareCentimeters = a.to();
228        assert_abs_diff_eq!(b.value(), 1.0, epsilon = 1e-12);
229    }
230
231    #[test]
232    #[cfg(feature = "land-area")]
233    fn are_to_sqm() {
234        let a = Ares::new(1.0);
235        let b: SquareMeters = a.to();
236        assert_abs_diff_eq!(b.value(), 100.0, epsilon = 1e-12);
237    }
238
239    #[test]
240    #[cfg(feature = "customary")]
241    fn sqinch_to_sqcm() {
242        let a = SquareInches::new(1.0);
243        let b: SquareCentimeters = a.to();
244        // 1 in² = 6.4516 cm²
245        assert_abs_diff_eq!(b.value(), 6.4516, epsilon = 1e-9);
246    }
247
248    #[test]
249    #[cfg(feature = "customary")]
250    fn sqyard_to_sqm() {
251        let a = SquareYards::new(1.0);
252        let b: SquareMeters = a.to();
253        assert_abs_diff_eq!(b.value(), 0.836_127_36, epsilon = 1e-9);
254    }
255
256    #[test]
257    fn roundtrip_sqcm_sqm() {
258        let original = SquareCentimeters::new(250.0);
259        let converted = original.to::<SquareMeter>();
260        let back = converted.to::<SquareCentimeter>();
261        assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-10);
262    }
263
264    #[test]
265    fn sqrt_recovers_length_unit() {
266        use crate::length::{Meter, Meters};
267
268        let area = SquareMeters::new(36.0);
269        let side: crate::Quantity<Meter> = area.sqrt();
270        assert_abs_diff_eq!(side.value(), 6.0, epsilon = 1e-12);
271
272        // Round-trip: side² == area
273        let again: SquareMeters = side * side;
274        assert_abs_diff_eq!(again.value(), area.value(), epsilon = 1e-12);
275
276        // Pure type-level: sqrt of (Meters * Meters) is Meters
277        let s = Meters::new(7.0);
278        let a = s * s;
279        let r: Meters = a.sqrt();
280        assert_abs_diff_eq!(r.value(), 7.0, epsilon = 1e-12);
281    }
282
283    #[test]
284    fn squared_unit_conversion_uses_squared_scale() {
285        // 1 km² = 1_000_000 m² (scale factor squared)
286        let one_sqkm = SquareKilometers::new(1.0);
287        let in_sqm: SquareMeters = one_sqkm.to();
288        assert_abs_diff_eq!(in_sqm.value(), 1.0e6, epsilon = 1e-6);
289    }
290
291    #[test]
292    #[cfg(feature = "std")]
293    fn squared_unit_formatter_output() {
294        // Display: "value sym·sym"
295        assert_eq!(format!("{}", SquareMeters::new(2.5)), "2.5 m·m");
296        // LowerExp uses scientific notation on the value, same symbol layout.
297        assert_eq!(format!("{:e}", SquareMeters::new(1234.0)), "1.234e3 m·m");
298    }
299
300    #[test]
301    #[cfg(feature = "std")]
302    fn symbols_are_correct() {
303        // Prod-based aliases inherit the Prod Display ("m·m");
304        // SYMBOL is empty but Display writes component symbols.
305        assert_eq!(format!("{}", SquareMeters::new(1.0)), "1 m·m");
306        #[cfg(feature = "land-area")]
307        assert_eq!(Hectare::SYMBOL, "ha");
308        #[cfg(feature = "land-area")]
309        assert_eq!(Acre::SYMBOL, "ac");
310        #[cfg(feature = "customary")]
311        assert_eq!(SquareInch::SYMBOL, "in²");
312    }
313}