1use crate::{Quantity, Unit};
34use qtty_derive::Unit;
35
36pub use crate::dimension::Mass;
38
39pub trait MassUnit: Unit<Dim = Mass> {}
41impl<T: Unit<Dim = Mass>> MassUnit for T {}
42
43#[cfg(feature = "customary")]
44mod customary;
45#[cfg(feature = "customary")]
46pub use customary::*;
47#[cfg(feature = "fundamental-physics")]
48mod fundamental_physics;
49#[cfg(feature = "fundamental-physics")]
50pub use fundamental_physics::*;
51#[cfg(feature = "astro")]
52mod astro;
53#[cfg(feature = "astro")]
54pub use astro::*;
55
56#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
58#[unit(symbol = "g", dimension = Mass, ratio = 1.0)]
59pub struct Gram;
60pub type Grams = Quantity<Gram>;
62pub const G: Grams = Grams::new(1.0);
64
65macro_rules! si_gram {
76 ($name:ident, $sym:literal, $ratio:expr, $alias:ident, $qty:ident, $one:ident) => {
77 #[doc = concat!("SI mass unit `", stringify!($name), "` with gram-based prefix (symbol `", $sym,"`).")]
78 #[doc = concat!("By definition, `1 ", $sym, " = ", stringify!($ratio), " g`.")]
79 #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
80 #[unit(symbol = $sym, dimension = Mass, ratio = $ratio)]
81 pub struct $name;
82
83 #[doc = concat!("Shorthand alias for [`", stringify!($name), "`]." )]
84 pub type $alias = $name;
85
86 #[doc = concat!("Quantity measured in ", stringify!($name), " (",$sym,").")]
87 pub type $qty = Quantity<$alias>;
88
89 #[doc = concat!("Constant equal to one ", stringify!($name), " (1 ",$sym,").")]
90 pub const $one: $qty = $qty::new(1.0);
91 };
92}
93
94si_gram!(Yoctogram, "yg", 1e-24, Yg, Yoctograms, YG);
96si_gram!(Zeptogram, "zg", 1e-21, Zg, Zeptograms, ZG);
97si_gram!(Attogram, "ag", 1e-18, Ag, Attograms, AG);
98si_gram!(Femtogram, "fg", 1e-15, Fg, Femtograms, FG);
99si_gram!(Picogram, "pg", 1e-12, Pg, Picograms, PG);
100si_gram!(Nanogram, "ng", 1e-9, Ng, Nanograms, NG);
101si_gram!(Microgram, "µg", 1e-6, Ug, Micrograms, UG);
102si_gram!(Milligram, "mg", 1e-3, Mg, Milligrams, MG);
103si_gram!(Centigram, "cg", 1e-2, Cg, Centigrams, CG);
104si_gram!(Decigram, "dg", 1e-1, Dg, Decigrams, DG);
105
106si_gram!(Decagram, "dag", 1e1, Dag, Decagrams, DAG);
107si_gram!(Hectogram, "hg", 1e2, Hg, Hectograms, HG);
108si_gram!(Kilogram, "kg", 1e3, Kg, Kilograms, KG);
109si_gram!(Megagram, "Mg", 1e6, MgG, Megagrams, MEGAGRAM);
110si_gram!(Gigagram, "Gg", 1e9, Gg, Gigagrams, GG);
111si_gram!(Teragram, "Tg", 1e12, Tg, Teragrams, TG);
112si_gram!(Petagram, "Pg", 1e15, PgG, Petagrams, PETAGRAM);
113si_gram!(Exagram, "Eg", 1e18, Eg, Exagrams, EG);
114si_gram!(Zettagram, "Zg", 1e21, ZgG, Zettagrams, ZETTAGRAM);
115si_gram!(Yottagram, "Yg", 1e24, YgG, Yottagrams, YOTTAGRAM);
116
117#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
119#[unit(symbol = "t", dimension = Mass, ratio = 1_000_000.0)]
120pub struct Tonne;
121pub type T = Tonne;
123pub type Tonnes = Quantity<T>;
125pub const TONE: Tonnes = Tonnes::new(1.0);
127
128#[macro_export]
134#[doc(hidden)]
135macro_rules! mass_units {
136 ($cb:path) => {
137 $cb!(
138 Gram, Yoctogram, Zeptogram, Attogram, Femtogram, Picogram, Nanogram, Microgram,
139 Milligram, Centigram, Decigram, Decagram, Hectogram, Kilogram, Megagram, Gigagram,
140 Teragram, Petagram, Exagram, Zettagram, Yottagram, Tonne
141 );
142 };
143}
144
145mass_units!(crate::impl_unit_from_conversions);
147
148#[cfg(feature = "cross-unit-ops")]
152mass_units!(crate::impl_unit_cross_unit_ops);
153
154#[cfg(all(feature = "astro", feature = "customary"))]
156crate::__impl_from_each_extra_to_bases!(
157 {SolarMass}
158 Carat, Grain, Pound, Ounce, Stone, ShortTon, LongTon
159);
160#[cfg(all(feature = "astro", feature = "customary", feature = "cross-unit-ops"))]
161crate::__impl_cross_ops_each_extra_to_bases!(
162 {SolarMass}
163 Carat, Grain, Pound, Ounce, Stone, ShortTon, LongTon
164);
165
166#[cfg(all(feature = "astro", feature = "fundamental-physics"))]
167crate::__impl_from_each_extra_to_bases!(
168 {SolarMass}
169 AtomicMassUnit
170);
171#[cfg(all(
172 feature = "astro",
173 feature = "fundamental-physics",
174 feature = "cross-unit-ops"
175))]
176crate::__impl_cross_ops_each_extra_to_bases!(
177 {SolarMass}
178 AtomicMassUnit
179);
180
181#[cfg(all(feature = "customary", feature = "fundamental-physics"))]
182crate::__impl_from_each_extra_to_bases!(
183 {Carat, Grain, Pound, Ounce, Stone, ShortTon, LongTon}
184 AtomicMassUnit
185);
186#[cfg(all(
187 feature = "customary",
188 feature = "fundamental-physics",
189 feature = "cross-unit-ops"
190))]
191crate::__impl_cross_ops_each_extra_to_bases!(
192 {Carat, Grain, Pound, Ounce, Stone, ShortTon, LongTon}
193 AtomicMassUnit
194);
195
196#[cfg(test)]
198mass_units!(crate::assert_units_are_builtin);
199
200#[cfg(all(test, feature = "std"))]
201mod tests {
202 use super::*;
203 use approx::{assert_abs_diff_eq, assert_relative_eq};
204 use proptest::prelude::*;
205
206 #[test]
211 fn gram_to_kilogram() {
212 let g = Grams::new(1000.0);
213 let kg = g.to::<Kilogram>();
214 assert_abs_diff_eq!(kg.value(), 1.0, epsilon = 1e-12);
215 }
216
217 #[test]
218 fn kilogram_to_gram() {
219 let kg = Kilograms::new(1.0);
220 let g = kg.to::<Gram>();
221 assert_abs_diff_eq!(g.value(), 1000.0, epsilon = 1e-9);
222 }
223
224 #[test]
225 #[cfg(feature = "astro")]
226 fn solar_mass_to_grams() {
227 let sm = SolarMasses::new(1.0);
228 let g = sm.to::<Gram>();
229 assert_relative_eq!(g.value(), 1.988416e33, max_relative = 1e-5);
231 }
232
233 #[test]
234 #[cfg(feature = "astro")]
235 fn solar_mass_to_kilograms() {
236 let sm = SolarMasses::new(1.0);
237 let kg = sm.to::<Kilogram>();
238 assert_relative_eq!(kg.value(), 1.988416e30, max_relative = 1e-5);
240 }
241
242 #[test]
243 #[cfg(feature = "astro")]
244 fn kilograms_to_solar_mass() {
245 let earth_kg = Kilograms::new(5.97e24);
247 let earth_sm = earth_kg.to::<SolarMass>();
248 assert_relative_eq!(earth_sm.value(), 3.0e-6, max_relative = 0.01);
249 }
250
251 #[test]
256 #[cfg(feature = "astro")]
257 fn solar_mass_ratio_sanity() {
258 assert_relative_eq!(SolarMass::RATIO, 1.988416e33, max_relative = 1e-5);
260 }
261
262 #[test]
263 #[cfg(feature = "astro")]
264 fn solar_mass_order_of_magnitude() {
265 let sun = SolarMasses::new(1.0);
267 let kg = sun.to::<Kilogram>();
268 assert!(kg.value() > 1e30);
269 assert!(kg.value() < 1e31);
270 }
271
272 #[test]
277 fn roundtrip_g_kg() {
278 let original = Grams::new(5000.0);
279 let converted = original.to::<Kilogram>();
280 let back = converted.to::<Gram>();
281 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-9);
282 }
283
284 #[test]
285 #[cfg(feature = "astro")]
286 fn roundtrip_kg_solar() {
287 let original = Kilograms::new(1e30);
288 let converted = original.to::<SolarMass>();
289 let back = converted.to::<Kilogram>();
290 assert_relative_eq!(back.value(), original.value(), max_relative = 1e-12);
291 }
292
293 proptest! {
298 #[test]
299 fn prop_roundtrip_g_kg(g in 1e-6..1e6f64) {
300 let original = Grams::new(g);
301 let converted = original.to::<Kilogram>();
302 let back = converted.to::<Gram>();
303 prop_assert!((back.value() - original.value()).abs() < 1e-9 * g.abs().max(1.0));
304 }
305
306 #[test]
307 fn prop_g_kg_ratio(g in 1e-6..1e6f64) {
308 let grams = Grams::new(g);
309 let kg = grams.to::<Kilogram>();
310 prop_assert!((grams.value() / kg.value() - 1000.0).abs() < 1e-9);
312 }
313 }
314
315 #[test]
318 fn tonne_to_kilogram() {
319 let t = Tonnes::new(1.0);
320 let kg = t.to::<Kilogram>();
321 assert_relative_eq!(kg.value(), 1_000.0, max_relative = 1e-12);
322 }
323
324 #[test]
325 #[cfg(feature = "customary")]
326 fn carat_to_gram() {
327 let ct = Carats::new(5.0);
328 let g = ct.to::<Gram>();
329 assert_relative_eq!(g.value(), 1.0, max_relative = 1e-12);
331 }
332
333 #[test]
334 #[cfg(feature = "customary")]
335 fn grain_to_milligram() {
336 let gr = Grains::new(1.0);
337 let mg = gr.to::<Milligram>();
338 assert_relative_eq!(mg.value(), 64.798_91, max_relative = 1e-6);
340 }
341
342 #[test]
343 #[cfg(feature = "customary")]
344 fn pound_to_gram() {
345 let lb = Pounds::new(1.0);
346 let g = lb.to::<Gram>();
347 assert_relative_eq!(g.value(), 453.592_37, max_relative = 1e-9);
349 }
350
351 #[test]
352 #[cfg(feature = "customary")]
353 fn ounce_to_gram() {
354 let oz = Ounces::new(16.0);
355 let g = oz.to::<Gram>();
356 assert_relative_eq!(g.value(), 453.592_37, max_relative = 1e-9);
358 }
359
360 #[test]
361 #[cfg(feature = "customary")]
362 fn stone_to_pound() {
363 let st = Stones::new(1.0);
364 let lb = st.to::<Pound>();
365 assert_relative_eq!(lb.value(), 14.0, max_relative = 1e-12);
367 }
368
369 #[test]
370 #[cfg(feature = "customary")]
371 fn short_ton_to_pound() {
372 let ton = ShortTons::new(1.0);
373 let lb = ton.to::<Pound>();
374 assert_relative_eq!(lb.value(), 2000.0, max_relative = 1e-12);
376 }
377
378 #[test]
379 #[cfg(feature = "customary")]
380 fn long_ton_to_pound() {
381 let ton = LongTons::new(1.0);
382 let lb = ton.to::<Pound>();
383 assert_relative_eq!(lb.value(), 2240.0, max_relative = 1e-12);
385 }
386
387 #[test]
388 #[cfg(feature = "fundamental-physics")]
389 fn atomic_mass_unit_to_gram() {
390 let u = AtomicMassUnits::new(1.0);
392 let g = u.to::<Gram>();
393 assert_relative_eq!(g.value(), 1.660_539_068_92e-24, max_relative = 1e-6);
394 }
395
396 #[test]
399 fn milligram_to_gram() {
400 let mg = Milligrams::new(1000.0);
401 let g = mg.to::<Gram>();
402 assert_relative_eq!(g.value(), 1.0, max_relative = 1e-12);
403 }
404
405 #[test]
406 fn microgram_to_milligram() {
407 let ug = Micrograms::new(1000.0);
408 let mg = ug.to::<Milligram>();
409 assert_relative_eq!(mg.value(), 1.0, max_relative = 1e-12);
410 }
411
412 #[test]
413 fn symbols_are_correct() {
414 assert_eq!(Kilogram::SYMBOL, "kg");
415 assert_eq!(Gram::SYMBOL, "g");
416 #[cfg(feature = "customary")]
417 assert_eq!(Pound::SYMBOL, "lb");
418 assert_eq!(Tonne::SYMBOL, "t");
419 }
420}