Skip to main content

qtty_core/units/
force.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (C) 2026 Vallés Puig, Ramon
3
4//! Force units.
5//!
6//! The canonical scaling unit for this dimension is the **newton** (`Newton::RATIO == 1.0`).
7//! All other force units are expressed as exact ratios to newtons.
8//!
9//! This module provides:
10//!
11//! - **SI newton** and the full SI prefix ladder.
12//! - **CGS dyne** (feature: `fundamental-physics`).
13//! - **Pound-force** (feature: `customary`).
14//!
15//! ```rust
16//! use qtty_core::force::{Kilonewtons, Newton};
17//!
18//! let kn = Kilonewtons::new(1.0);
19//! let n = kn.to::<Newton>();
20//! assert_eq!(n.value(), 1000.0);
21//! ```
22
23use crate::{Quantity, Unit};
24use qtty_derive::Unit;
25
26/// Re-export the force dimension from the dimension module.
27pub use crate::dimension::Force;
28
29/// Marker trait for any [`Unit`] whose dimension is [`Force`].
30pub trait ForceUnit: Unit<Dim = Force> {}
31impl<T: Unit<Dim = Force>> ForceUnit for T {}
32
33// ─────────────────────────────────────────────────────────────────────────────
34// SI newton
35// ─────────────────────────────────────────────────────────────────────────────
36
37/// Newton — SI coherent derived unit of force (kg·m/s²).
38#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
39#[unit(symbol = "N", dimension = Force, ratio = 1.0)]
40pub struct Newton;
41/// Type alias shorthand for [`Newton`].
42pub type N = Newton;
43/// A quantity measured in newtons.
44pub type Newtons = Quantity<N>;
45/// One newton.
46pub const NEWTON: Newtons = Newtons::new(1.0);
47
48macro_rules! si_newton {
49    ($name:ident, $sym:literal, $ratio:expr, $alias:ident, $qty:ident, $one:ident) => {
50        #[doc = concat!("SI-prefixed newton unit (", stringify!($ratio), " N).")]
51        #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
52        #[unit(symbol = $sym, dimension = Force, ratio = $ratio)]
53        pub struct $name;
54        #[doc = concat!("Type alias shorthand for [`", stringify!($name), "`].")]
55        pub type $alias = $name;
56        #[doc = concat!("A quantity measured in ", stringify!($name), "s.")]
57        pub type $qty = Quantity<$alias>;
58        #[doc = concat!("One ", stringify!($name), ".")]
59        pub const $one: $qty = $qty::new(1.0);
60    };
61}
62
63si_newton!(Micronewton, "µN", 1e-6, Un, Micronewtons, MICRONEWTON);
64si_newton!(Millinewton, "mN", 1e-3, Mn, Millinewtons, MILLINEWTON);
65si_newton!(Kilonewton, "kN", 1e3, Kn, Kilonewtons, KILONEWTON);
66si_newton!(Meganewton, "MN", 1e6, MN, Meganewtons, MEGANEWTON);
67si_newton!(Giganewton, "GN", 1e9, GN, Giganewtons, GIGANEWTON);
68
69// ─────────────────────────────────────────────────────────────────────────────
70// Feature-gated units
71// ─────────────────────────────────────────────────────────────────────────────
72
73/// Dyne — CGS unit of force (1 dyn = 10⁻⁵ N).
74#[cfg(feature = "fundamental-physics")]
75#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
76#[unit(symbol = "dyn", dimension = Force, ratio = 1e-5)]
77pub struct Dyne;
78/// A quantity measured in dynes.
79#[cfg(feature = "fundamental-physics")]
80pub type Dynes = Quantity<Dyne>;
81
82/// Pound-force (1 lbf = g₀ × 1 lb = 4.448 221 615 260 5 N exactly).
83///
84/// Derived from the exact definitions: `1 lb = 0.453 592 37 kg` and
85/// `g₀ = 9.806 65 m/s²`. NIST SP 1247 conversion factor.
86#[cfg(feature = "customary")]
87#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
88#[unit(symbol = "lbf", dimension = Force, ratio = 4.448_221_615_260_5)]
89pub struct PoundForce;
90/// A quantity measured in pounds-force.
91#[cfg(feature = "customary")]
92pub type PoundsForce = Quantity<PoundForce>;
93
94// ─────────────────────────────────────────────────────────────────────────────
95// Unit inventory macro
96// ─────────────────────────────────────────────────────────────────────────────
97
98/// Canonical list of always-available (metric SI) force units.
99#[macro_export]
100#[doc(hidden)]
101macro_rules! force_units {
102    ($cb:path) => {
103        $cb!(
104            Newton,
105            Micronewton,
106            Millinewton,
107            Kilonewton,
108            Meganewton,
109            Giganewton
110        );
111    };
112}
113
114// Generate bidirectional From impls between base metric SI force units.
115force_units!(crate::impl_unit_from_conversions);
116
117#[cfg(feature = "cross-unit-ops")]
118force_units!(crate::impl_unit_cross_unit_ops);
119
120// ── Cross-feature: customary × fundamental-physics ───────────────────────────
121#[cfg(all(feature = "customary", feature = "fundamental-physics"))]
122crate::__impl_from_each_extra_to_bases!(
123    {PoundForce}
124    Dyne
125);
126#[cfg(all(
127    feature = "customary",
128    feature = "fundamental-physics",
129    feature = "cross-unit-ops"
130))]
131crate::__impl_cross_ops_each_extra_to_bases!(
132    {PoundForce}
133    Dyne
134);
135
136// Compile-time check: every base force unit is registered as BuiltinUnit.
137#[cfg(test)]
138force_units!(crate::assert_units_are_builtin);
139
140/// Canonical list of `fundamental-physics`-gated force units (CGS dyne).
141#[cfg(feature = "fundamental-physics")]
142#[macro_export]
143#[doc(hidden)]
144macro_rules! force_fundamental_physics_units {
145    ($cb:path) => {
146        $cb!(Dyne);
147    };
148}
149
150/// Canonical list of `customary`-gated force units (pound-force).
151#[cfg(feature = "customary")]
152#[macro_export]
153#[doc(hidden)]
154macro_rules! force_customary_units {
155    ($cb:path) => {
156        $cb!(PoundForce);
157    };
158}
159
160#[cfg(all(test, feature = "std"))]
161mod tests {
162    use super::*;
163    use approx::assert_abs_diff_eq;
164
165    #[test]
166    fn kilonewton_to_newton() {
167        let kn = Kilonewtons::new(1.0);
168        let n: Newtons = kn.to();
169        assert_abs_diff_eq!(n.value(), 1_000.0, epsilon = 1e-12);
170    }
171
172    #[test]
173    fn newton_to_millinewton() {
174        let n = Newtons::new(1.0);
175        let mn: Millinewtons = n.to();
176        assert_abs_diff_eq!(mn.value(), 1_000.0, epsilon = 1e-12);
177    }
178
179    #[test]
180    #[cfg(feature = "fundamental-physics")]
181    fn newton_to_dyne() {
182        let n = Newtons::new(1.0);
183        let d: Dynes = n.to();
184        assert_abs_diff_eq!(d.value(), 1e5, epsilon = 1e-7);
185    }
186
187    #[test]
188    #[cfg(feature = "customary")]
189    fn newton_to_lbf() {
190        let n = Newtons::new(4.448_221_615_260_5);
191        let lbf: PoundsForce = n.to();
192        assert_abs_diff_eq!(lbf.value(), 1.0, epsilon = 1e-9);
193    }
194}