1#![doc = include_str ! ("../README.md")]
11#![cfg_attr(not(feature = "std"), no_std)]
12#![deny(non_ascii_idents)]
14#![deny(unsafe_code)]
15#![warn(missing_debug_implementations)]
16#![warn(missing_docs)]
17#![warn(trivial_casts)]
18#![warn(unused)]
19#![allow(dead_code)]
20#![warn(clippy::cast_possible_truncation)]
22#![warn(clippy::cast_possible_wrap)]
23#![warn(clippy::cast_precision_loss)]
24#![warn(clippy::cast_sign_loss)]
25#![warn(clippy::cognitive_complexity)]
26#![warn(clippy::decimal_literal_representation)]
27#![warn(clippy::enum_glob_use)]
28#![warn(clippy::equatable_if_let)]
29#![warn(clippy::fallible_impl_from)]
30#![warn(clippy::if_not_else)]
31#![warn(clippy::if_then_some_else_none)]
32#![warn(clippy::implicit_clone)]
33#![warn(clippy::integer_division)]
34#![warn(clippy::manual_assert)]
35#![warn(clippy::match_same_arms)]
36#![warn(clippy::mismatching_type_param_order)]
37#![warn(clippy::missing_const_for_fn)]
38#![warn(clippy::missing_errors_doc)]
39#![warn(clippy::missing_panics_doc)]
40#![warn(clippy::multiple_crate_versions)]
41#![warn(clippy::multiple_inherent_impl)]
42#![warn(clippy::must_use_candidate)]
43#![warn(clippy::needless_pass_by_value)]
44#![warn(clippy::print_stderr)]
45#![warn(clippy::print_stdout)]
46#![warn(clippy::semicolon_if_nothing_returned)]
47#![warn(clippy::str_to_string)]
48#![warn(clippy::string_to_string)]
49#![warn(clippy::undocumented_unsafe_blocks)]
50#![warn(clippy::unicode_not_nfc)]
51#![warn(clippy::unimplemented)]
52#![warn(clippy::unseparated_literal_suffix)]
53#![warn(clippy::unused_self)]
54#![warn(clippy::unwrap_in_result)]
55#![warn(clippy::use_self)]
56#![warn(clippy::used_underscore_binding)]
57#![warn(clippy::wildcard_imports)]
58
59extern crate alloc;
60
61use alloc::{borrow::ToOwned, format, string::String};
62use core::{
63 cmp::Ordering,
64 fmt,
65 ops::{Add, Div, Mul, Sub},
66};
67
68#[cfg(feature = "fpdec")]
69pub use amnt_dec::{AmountT, Dec, Decimal, AMNT_ONE, AMNT_ZERO};
70#[cfg(all(not(feature = "fpdec"), target_pointer_width = "32"))]
71pub use amnt_f32::{AmountT, AMNT_ONE, AMNT_ZERO};
72#[cfg(all(not(feature = "fpdec"), target_pointer_width = "64"))]
73pub use amnt_f64::{AmountT, AMNT_ONE, AMNT_ZERO};
74pub use converter::{ConversionTable, Converter};
75pub use rate::Rate;
76pub use si_prefixes::SIPrefix;
77
78mod converter;
79pub mod prelude;
80mod rate;
81mod si_prefixes;
82
83#[cfg(feature = "fpdec")]
84#[doc(hidden)]
85pub mod amnt_dec;
86#[cfg(all(not(feature = "fpdec"), target_pointer_width = "32"))]
87#[doc(hidden)]
88pub mod amnt_f32;
89#[cfg(all(not(feature = "fpdec"), target_pointer_width = "64"))]
90#[doc(hidden)]
91pub mod amnt_f64;
92
93#[cfg(feature = "acceleration")]
94pub mod acceleration;
95#[cfg(feature = "area")]
96pub mod area;
97#[cfg(feature = "datathroughput")]
98pub mod datathroughput;
99#[cfg(feature = "datavolume")]
100pub mod datavolume;
101#[cfg(feature = "duration")]
102pub mod duration;
103#[cfg(feature = "energy")]
104pub mod energy;
105#[cfg(feature = "force")]
106pub mod force;
107#[cfg(feature = "frequency")]
108pub mod frequency;
109#[cfg(feature = "length")]
110pub mod length;
111#[cfg(feature = "mass")]
112pub mod mass;
113#[cfg(feature = "power")]
114pub mod power;
115#[cfg(feature = "speed")]
116pub mod speed;
117#[cfg(feature = "temperature")]
118pub mod temperature;
119#[cfg(feature = "volume")]
120pub mod volume;
121
122pub trait Unit:
124 Copy + Eq + PartialEq + Sized + Mul<AmountT> + fmt::Display
125{
126 type QuantityType: Quantity<UnitType = Self>;
128
129 fn iter() -> impl Iterator<Item = Self>;
131
132 #[must_use]
135 fn from_symbol(symbol: &str) -> Option<Self> {
136 Self::iter().find(|&unit| unit.symbol() == symbol)
137 }
138
139 fn name(&self) -> String;
141
142 fn symbol(&self) -> String;
144
145 fn si_prefix(&self) -> Option<SIPrefix>;
147
148 fn as_qty(&self) -> Self::QuantityType {
150 Self::QuantityType::new(AMNT_ONE, *self)
151 }
152
153 fn fmt(&self, form: &mut fmt::Formatter<'_>) -> fmt::Result {
160 fmt::Display::fmt(&self.symbol(), form)
161 }
162}
163
164pub trait LinearScaledUnit: Unit {
166 const REF_UNIT: Self;
168
169 #[must_use]
172 fn from_scale(amnt: AmountT) -> Option<Self> {
173 Self::iter().find(|&unit| unit.scale() == amnt)
174 }
175
176 #[inline(always)]
178 fn is_ref_unit(&self) -> bool {
179 *self == Self::REF_UNIT
180 }
181
182 fn scale(&self) -> AmountT;
184
185 #[inline(always)]
187 fn ratio(&self, other: &Self) -> AmountT {
188 self.scale() / other.scale()
189 }
190}
191
192pub trait Quantity: Copy + Sized + Mul<AmountT> {
194 type UnitType: Unit<QuantityType = Self>;
196
197 #[must_use]
199 fn iter_units() -> impl Iterator<Item = Self::UnitType> {
200 Self::UnitType::iter()
201 }
202
203 #[must_use]
206 fn unit_from_symbol(symbol: &str) -> Option<Self::UnitType> {
207 Self::iter_units().find(|&unit| unit.symbol() == symbol)
208 }
209
210 fn new(amount: AmountT, unit: Self::UnitType) -> Self;
212
213 fn amount(&self) -> AmountT;
215
216 fn unit(&self) -> Self::UnitType;
218
219 #[inline(always)]
222 fn eq(&self, other: &Self) -> bool {
223 self.unit() == other.unit() && self.amount() == other.amount()
224 }
225
226 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
229 if self.unit() == other.unit() {
230 PartialOrd::partial_cmp(&self.amount(), &other.amount())
231 } else {
232 None
233 }
234 }
235
236 fn add(self, rhs: Self) -> Self {
242 if self.unit() == rhs.unit() {
243 return Self::new(self.amount() + rhs.amount(), self.unit());
244 }
245 panic!(
246 "Can't add '{}' and '{}'.",
247 self.unit().symbol(),
248 rhs.unit().symbol()
249 );
250 }
251
252 fn sub(self, rhs: Self) -> Self {
259 if self.unit() == rhs.unit() {
260 return Self::new(self.amount() - rhs.amount(), self.unit());
261 }
262 panic!(
263 "Can't subtract '{}' and '{}'.",
264 self.unit().symbol(),
265 rhs.unit().symbol(),
266 );
267 }
268
269 fn div(self, rhs: Self) -> AmountT {
275 if self.unit() == rhs.unit() {
276 return self.amount() / rhs.amount();
277 }
278 panic!(
279 "Can't divide '{}' and '{}'.",
280 self.unit().symbol(),
281 rhs.unit().symbol()
282 );
283 }
284
285 fn fmt(&self, form: &mut fmt::Formatter<'_>) -> fmt::Result {
292 if self.unit().symbol().is_empty() {
293 fmt::Display::fmt(&self.amount(), form)
294 } else {
295 let tmp: String;
296 let amnt_non_neg = self.amount() >= AMNT_ZERO;
297 #[cfg(feature = "fpdec")]
298 let abs_amnt = self.amount().abs();
299 #[cfg(not(feature = "fpdec"))]
300 let abs_amnt = if amnt_non_neg {
301 self.amount()
302 } else {
303 -self.amount()
304 };
305 if let Some(prec) = form.precision() {
306 tmp = format!("{:.*} {}", prec, abs_amnt, self.unit());
307 } else {
308 tmp = format!("{} {}", abs_amnt, self.unit());
309 }
310 form.pad_integral(amnt_non_neg, "", &tmp)
311 }
312 }
313}
314
315pub trait HasRefUnit: Quantity + Add<Self> + Sub<Self> + Div<Self>
317where
318 <Self as Quantity>::UnitType: LinearScaledUnit,
319{
320 const REF_UNIT: <Self as Quantity>::UnitType;
322
323 #[must_use]
326 fn unit_from_scale(amnt: AmountT) -> Option<Self::UnitType> {
327 Self::iter_units().find(|&unit| unit.scale() == amnt)
328 }
329
330 #[inline(always)]
332 fn equiv_amount(&self, unit: Self::UnitType) -> AmountT {
333 if self.unit() == unit {
334 self.amount()
335 } else {
336 self.unit().ratio(&unit) * self.amount()
337 }
338 }
339
340 fn convert(&self, to_unit: Self::UnitType) -> Self {
342 Self::new(self.equiv_amount(to_unit), to_unit)
343 }
344
345 #[inline(always)]
348 fn eq(&self, other: &Self) -> bool {
349 self.amount() == other.equiv_amount(self.unit())
350 }
351
352 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
355 if self.unit() == other.unit() {
356 PartialOrd::partial_cmp(&self.amount(), &other.amount())
357 } else {
358 PartialOrd::partial_cmp(
359 &self.amount(),
360 &other.equiv_amount(self.unit()),
361 )
362 }
363 }
364
365 #[inline]
367 fn add(self, rhs: Self) -> Self {
368 Self::new(self.amount() + rhs.equiv_amount(self.unit()), self.unit())
369 }
370
371 #[inline]
373 fn sub(self, rhs: Self) -> Self {
374 Self::new(self.amount() - rhs.equiv_amount(self.unit()), self.unit())
375 }
376
377 #[inline]
379 fn div(self, rhs: Self) -> AmountT {
380 self.amount() / rhs.equiv_amount(self.unit())
381 }
382
383 #[doc(hidden)]
384 #[must_use]
391 fn _fit(amount: AmountT) -> Self {
392 let take_all = Self::REF_UNIT.si_prefix().is_none();
393 let mut it = Self::iter_units()
394 .filter(|u| take_all || u.si_prefix().is_some());
395 let first = it.next().unwrap();
397 let last = it
398 .filter(|u| u.scale() > first.scale() && u.scale() <= amount)
399 .last();
400 match last {
401 Some(unit) => Self::new(amount / unit.scale(), unit),
402 None => Self::new(amount / first.scale(), first),
403 }
404 }
405}
406
407#[derive(Copy, Clone, Debug, Eq, PartialEq)]
409pub enum One {
410 One,
412}
413
414impl One {
415 const VARIANTS: [Self; 1] = [ONE];
416}
417
418pub const ONE: One = One::One;
420
421impl Unit for One {
422 type QuantityType = AmountT;
423 fn iter() -> impl Iterator<Item = Self> {
424 Self::VARIANTS.iter().cloned()
425 }
426 fn name(&self) -> String {
427 "One".to_owned()
428 }
429 fn symbol(&self) -> String {
430 "".to_owned()
431 }
432 fn si_prefix(&self) -> Option<SIPrefix> {
433 None
434 }
435}
436
437impl fmt::Display for One {
438 #[inline(always)]
439 fn fmt(&self, form: &mut fmt::Formatter<'_>) -> fmt::Result {
440 <Self as Unit>::fmt(self, form)
441 }
442}
443
444impl LinearScaledUnit for One {
445 const REF_UNIT: Self = ONE;
446 fn scale(&self) -> AmountT {
447 AMNT_ONE
448 }
449}
450
451impl Mul<One> for AmountT {
452 type Output = Self;
453 #[inline(always)]
454 fn mul(self, _rhs: One) -> Self::Output {
455 self
456 }
457}
458
459impl Mul<AmountT> for One {
460 type Output = AmountT;
461 #[inline(always)]
462 fn mul(self, rhs: AmountT) -> Self::Output {
463 rhs
464 }
465}
466
467impl Quantity for AmountT {
468 type UnitType = One;
469
470 #[inline(always)]
471 fn new(amount: AmountT, _unit: Self::UnitType) -> Self {
472 amount
473 }
474
475 #[inline(always)]
476 fn amount(&self) -> AmountT {
477 *self
478 }
479
480 #[inline(always)]
481 fn unit(&self) -> Self::UnitType {
482 ONE
483 }
484}
485
486impl HasRefUnit for AmountT {
487 const REF_UNIT: One = ONE;
488
489 #[inline(always)]
490 fn _fit(amount: AmountT) -> Self {
491 amount
492 }
493}