1use crate::{Quantity, Unit};
19use qtty_derive::Unit;
20
21pub use crate::dimension::Mass;
23
24pub trait MassUnit: Unit<Dim = Mass> {}
26impl<T: Unit<Dim = Mass>> MassUnit for T {}
27
28#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
30#[unit(symbol = "g", dimension = Mass, ratio = 1.0)]
31pub struct Gram;
32pub type Grams = Quantity<Gram>;
34pub const G: Grams = Grams::new(1.0);
36
37macro_rules! si_gram {
48 ($name:ident, $sym:literal, $ratio:expr, $alias:ident, $qty:ident, $one:ident) => {
49 #[doc = concat!("SI mass unit `", stringify!($name), "` with gram-based prefix (symbol `", $sym,"`).")]
50 #[doc = concat!("By definition, `1 ", $sym, " = ", stringify!($ratio), " g`.")]
51 #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
52 #[unit(symbol = $sym, dimension = Mass, ratio = $ratio)]
53 pub struct $name;
54
55 #[doc = concat!("Shorthand alias for [`", stringify!($name), "`]." )]
56 pub type $alias = $name;
57
58 #[doc = concat!("Quantity measured in ", stringify!($name), " (",$sym,").")]
59 pub type $qty = Quantity<$alias>;
60
61 #[doc = concat!("Constant equal to one ", stringify!($name), " (1 ",$sym,").")]
62 pub const $one: $qty = $qty::new(1.0);
63 };
64}
65
66si_gram!(Yoctogram, "yg", 1e-24, Yg, Yoctograms, YG);
68si_gram!(Zeptogram, "zg", 1e-21, Zg, Zeptograms, ZG);
69si_gram!(Attogram, "ag", 1e-18, Ag, Attograms, AG);
70si_gram!(Femtogram, "fg", 1e-15, Fg, Femtograms, FG);
71si_gram!(Picogram, "pg", 1e-12, Pg, Picograms, PG);
72si_gram!(Nanogram, "ng", 1e-9, Ng, Nanograms, NG);
73si_gram!(Microgram, "µg", 1e-6, Ug, Micrograms, UG);
74si_gram!(Milligram, "mg", 1e-3, Mg, Milligrams, MG);
75si_gram!(Centigram, "cg", 1e-2, Cg, Centigrams, CG);
76si_gram!(Decigram, "dg", 1e-1, Dg, Decigrams, DG);
77
78si_gram!(Decagram, "dag", 1e1, Dag, Decagrams, DAG);
79si_gram!(Hectogram, "hg", 1e2, Hg, Hectograms, HG);
80si_gram!(Kilogram, "kg", 1e3, Kg, Kilograms, KG);
81si_gram!(Megagram, "Mg", 1e6, MgG, Megagrams, MEGAGRAM);
82si_gram!(Gigagram, "Gg", 1e9, Gg, Gigagrams, GG);
83si_gram!(Teragram, "Tg", 1e12, Tg, Teragrams, TG);
84si_gram!(Petagram, "Pg", 1e15, PgG, Petagrams, PETAGRAM);
85si_gram!(Exagram, "Eg", 1e18, Eg, Exagrams, EG);
86si_gram!(Zettagram, "Zg", 1e21, ZgG, Zettagrams, ZETTAGRAM);
87si_gram!(Yottagram, "Yg", 1e24, YgG, Yottagrams, YOTTAGRAM);
88
89#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
91#[unit(symbol = "t", dimension = Mass, ratio = 1_000_000.0)]
92pub struct Tonne;
93pub type T = Tonne;
95pub type Tonnes = Quantity<T>;
97pub const TONE: Tonnes = Tonnes::new(1.0);
99
100#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
102#[unit(symbol = "ct", dimension = Mass, ratio = 1.0 / 5.0)]
103pub struct Carat;
104pub type Ct = Carat;
106pub type Carats = Quantity<Ct>;
108pub const CT: Carats = Carats::new(1.0);
110
111#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
113#[unit(symbol = "gr", dimension = Mass, ratio = 6_479_891.0 / 1_000_000_000.0)]
114pub struct Grain;
115pub type Gr = Grain;
117pub type Grains = Quantity<Gr>;
119pub const GR: Grains = Grains::new(1.0);
121
122#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
124#[unit(symbol = "lb", dimension = Mass, ratio = 45_359_237.0 / 100_000.0)]
125pub struct Pound;
126pub type Lb = Pound;
128pub type Pounds = Quantity<Lb>;
130pub const LB: Pounds = Pounds::new(1.0);
132
133#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
135#[unit(symbol = "oz", dimension = Mass, ratio = (45_359_237.0 / 100_000.0) / 16.0)]
136pub struct Ounce;
137pub type Oz = Ounce;
139pub type Ounces = Quantity<Oz>;
141pub const OZ: Ounces = Ounces::new(1.0);
143
144#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
146#[unit(symbol = "st", dimension = Mass, ratio = (45_359_237.0 / 100_000.0) * 14.0)]
147pub struct Stone;
148pub type St = Stone;
150pub type Stones = Quantity<St>;
152pub const ST: Stones = Stones::new(1.0);
154
155#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
157#[unit(symbol = "ton_us", dimension = Mass, ratio = (45_359_237.0 / 100_000.0) * 2000.0)]
158pub struct ShortTon;
159pub type ShortTons = Quantity<ShortTon>;
161pub const TON_US: ShortTons = ShortTons::new(1.0);
163
164#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
166#[unit(symbol = "ton_uk", dimension = Mass, ratio = (45_359_237.0 / 100_000.0) * 2240.0)]
167pub struct LongTon;
168pub type LongTons = Quantity<LongTon>;
170pub const TON_UK: LongTons = LongTons::new(1.0);
172
173#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
177#[unit(symbol = "u", dimension = Mass, ratio = 1.660_539_068_92e-24)]
178pub struct AtomicMassUnit;
179pub type Dalton = AtomicMassUnit;
181pub type AtomicMassUnits = Quantity<AtomicMassUnit>;
183pub const U: AtomicMassUnits = AtomicMassUnits::new(1.0);
185
186#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
190#[unit(symbol = "M☉", dimension = Mass, ratio = 1.988_416e33)]
191pub struct SolarMass;
192pub type SolarMasses = Quantity<SolarMass>;
194pub const MSUN: SolarMasses = SolarMasses::new(1.0);
196
197crate::impl_unit_conversions!(
199 Gram,
200 Yoctogram,
201 Zeptogram,
202 Attogram,
203 Femtogram,
204 Picogram,
205 Nanogram,
206 Microgram,
207 Milligram,
208 Centigram,
209 Decigram,
210 Decagram,
211 Hectogram,
212 Kilogram,
213 Megagram,
214 Gigagram,
215 Teragram,
216 Petagram,
217 Exagram,
218 Zettagram,
219 Yottagram,
220 Tonne,
221 Carat,
222 Grain,
223 Pound,
224 Ounce,
225 Stone,
226 ShortTon,
227 LongTon,
228 AtomicMassUnit,
229 SolarMass
230);
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use approx::{assert_abs_diff_eq, assert_relative_eq};
236 use proptest::prelude::*;
237
238 #[test]
243 fn gram_to_kilogram() {
244 let g = Grams::new(1000.0);
245 let kg = g.to::<Kilogram>();
246 assert_abs_diff_eq!(kg.value(), 1.0, epsilon = 1e-12);
247 }
248
249 #[test]
250 fn kilogram_to_gram() {
251 let kg = Kilograms::new(1.0);
252 let g = kg.to::<Gram>();
253 assert_abs_diff_eq!(g.value(), 1000.0, epsilon = 1e-9);
254 }
255
256 #[test]
257 fn solar_mass_to_grams() {
258 let sm = SolarMasses::new(1.0);
259 let g = sm.to::<Gram>();
260 assert_relative_eq!(g.value(), 1.988416e33, max_relative = 1e-5);
262 }
263
264 #[test]
265 fn solar_mass_to_kilograms() {
266 let sm = SolarMasses::new(1.0);
267 let kg = sm.to::<Kilogram>();
268 assert_relative_eq!(kg.value(), 1.988416e30, max_relative = 1e-5);
270 }
271
272 #[test]
273 fn kilograms_to_solar_mass() {
274 let earth_kg = Kilograms::new(5.97e24);
276 let earth_sm = earth_kg.to::<SolarMass>();
277 assert_relative_eq!(earth_sm.value(), 3.0e-6, max_relative = 0.01);
278 }
279
280 #[test]
285 fn solar_mass_ratio_sanity() {
286 assert_relative_eq!(SolarMass::RATIO, 1.988416e33, max_relative = 1e-5);
288 }
289
290 #[test]
291 fn solar_mass_order_of_magnitude() {
292 let sun = SolarMasses::new(1.0);
294 let kg = sun.to::<Kilogram>();
295 assert!(kg.value() > 1e30);
296 assert!(kg.value() < 1e31);
297 }
298
299 #[test]
304 fn roundtrip_g_kg() {
305 let original = Grams::new(5000.0);
306 let converted = original.to::<Kilogram>();
307 let back = converted.to::<Gram>();
308 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-9);
309 }
310
311 #[test]
312 fn roundtrip_kg_solar() {
313 let original = Kilograms::new(1e30);
314 let converted = original.to::<SolarMass>();
315 let back = converted.to::<Kilogram>();
316 assert_relative_eq!(back.value(), original.value(), max_relative = 1e-12);
317 }
318
319 proptest! {
324 #[test]
325 fn prop_roundtrip_g_kg(g in 1e-6..1e6f64) {
326 let original = Grams::new(g);
327 let converted = original.to::<Kilogram>();
328 let back = converted.to::<Gram>();
329 prop_assert!((back.value() - original.value()).abs() < 1e-9 * g.abs().max(1.0));
330 }
331
332 #[test]
333 fn prop_g_kg_ratio(g in 1e-6..1e6f64) {
334 let grams = Grams::new(g);
335 let kg = grams.to::<Kilogram>();
336 prop_assert!((grams.value() / kg.value() - 1000.0).abs() < 1e-9);
338 }
339 }
340}