diff --git a/src/lib.rs b/src/lib.rs index c095a00d..abab873f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -447,6 +447,28 @@ pub trait Conversion { ::zero() } + /// Base portion of [conversion factor](https://jcgm.bipm.org/vim/en/1.24.html) for converting + /// the given logarithmic unit to the base unit for the quantity: `base().pow((value * + /// coefficient() + constant()) / scale())`. Implementation should return zero + /// (`Self::T::zero()`) if no base exists. + #[cfg(any(feature = "std", feature = "libm"))] + #[must_use = "method returns a new number and does not mutate the original value"] + #[inline(always)] + fn base() -> Self::T { + ::zero() + } + + /// Scale portion of [conversion factor](https://jcgm.bipm.org/vim/en/1.24.html) for converting + /// the given logarithmic unit to the base unit for the quantity: `base().pow((value * + /// coefficient() + constant()) / scale())`. Implementation should return zero + /// (`Self::T::zero()`) if no base exists. + #[cfg(any(feature = "std", feature = "libm"))] + #[must_use = "method returns a new number and does not mutate the original value"] + #[inline(always)] + fn scale() -> Self::T { + ::zero() + } + /// Instance [conversion factor](https://jcgm.bipm.org/vim/en/1.24.html). /// /// Default implementation returns the coefficient: `Self::coefficient()`. @@ -480,6 +502,16 @@ pub trait ConversionFactor: #[must_use = "method returns a new number and does not mutate the original value"] fn powi(self, e: i32) -> Self; + /// Raises a `ConversionFactor` to a power. + #[cfg(any(feature = "std", feature = "libm"))] + #[must_use = "method returns a new number and does not mutate the original value"] + fn pow(self, e: Self) -> Self; + + /// Takes the log_`ConversionFactor` of a value. + #[cfg(any(feature = "std", feature = "libm"))] + #[must_use = "method returns a new number and does not mutate the original value"] + fn log(self, base: Self) -> Self; + /// Converts a `ConversionFactor` into its underlying storage type. #[must_use = "method returns a new number and does not mutate the original value"] fn value(self) -> V; @@ -545,6 +577,18 @@ storage_types! { ::powi(self, e) } + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn pow(self, e: Self) -> Self { + ::powf(self, e) + } + + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn log(self, base: Self) -> Self { + ::log(self, base) + } + #[inline(always)] fn value(self) -> V { self @@ -571,7 +615,19 @@ storage_types! { impl crate::ConversionFactor for crate::num::rational::Ratio { #[inline(always)] fn powi(self, e: i32) -> Self { - self.pow(e) + crate::num::rational::Ratio::::pow(&self, e) + } + + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn pow(self, _e: Self) -> Self { + unimplemented!(); + } + + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn log(self, _base: Self) -> Self { + unimplemented!(); } #[inline(always)] @@ -607,6 +663,18 @@ storage_types! { } } + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn pow(self, e: Self) -> Self { + crate::num::rational::Ratio::::pow(&self, e) + } + + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn log(self, base: Self) -> Self { + crate::num::rational::Ratio::::log(&self, base) + } + #[inline(always)] fn value(self) -> V { self.to_integer() @@ -629,7 +697,19 @@ storage_types! { impl crate::ConversionFactor for V { #[inline(always)] fn powi(self, e: i32) -> Self { - self.pow(e) + V::pow(&self, e) + } + + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn pow(self, _e: Self) -> Self { + unimplemented!(); + } + + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn log(self, _base: Self) -> Self { + unimplemented!(); } #[inline(always)] @@ -661,6 +741,18 @@ storage_types! { } } + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn pow(self, _e: Self) -> Self { + unimplemented!(); + } + + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn log(self, _base: Self) -> Self { + unimplemented!(); + } + #[inline(always)] fn value(self) -> V { self @@ -695,6 +787,18 @@ storage_types! { self.powi(e) } + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn pow(self, e: Self) -> Self { + self.powf(e) + } + + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + fn log(self, base: Self) -> Self { + VV::log(self, base) + } + #[inline(always)] fn value(self) -> V { // Conversion by scaling (multiplication with only real number). Scaling a normalized diff --git a/src/quantity.rs b/src/quantity.rs index e9ba92a0..1a0ce968 100644 --- a/src/quantity.rs +++ b/src/quantity.rs @@ -18,8 +18,8 @@ /// * `$conversion`: Conversion (coefficient and constant factor) from the unit to the base unit of /// the quantity (e.g. `3.048_E-1` to convert `foot` to `meter`. `1.0_E0, 273.15_E0` to convert /// `celsius` to `kelvin`.). The coefficient is required and the constant factor is optional. -/// Note that using a unit with a non-zero constant factor is not currently supported as a base -/// unit. +/// Note that using a unit with a non-zero constant factor, base, or scale is not currently +/// supported as a base unit. /// * `$abbreviation`: Unit abbreviation (e.g. `"m"`). /// * `$singular`: Singular unit description (e.g. `"meter"`). /// * `$plural`: Plural unit description (e.g. `"meters"`). @@ -157,8 +157,11 @@ macro_rules! quantity { #[allow(clippy::manual_non_exhaustive)] #[derive(Debug, Clone, Copy)] pub enum Units { - $(#[doc=$plural] - $unit($unit),)+ + $(unit! { + @supported_attr $($conversion),+; + #[doc=$plural] + $unit($unit), + })+ #[doc(hidden)] __nonexhaustive, @@ -170,7 +173,10 @@ macro_rules! quantity { #[allow(dead_code)] pub fn abbreviation(&self) -> &'static str { match self { - $(Units::$unit(_) => <$unit as __system::Unit>::abbreviation(),)+ + $(unit! { + @supported_attr $($conversion),+; + Units::$unit(_) => <$unit as __system::Unit>::abbreviation(), + })+ Units::__nonexhaustive => "unknown", } diff --git a/src/si/electric_potential.rs b/src/si/electric_potential.rs index 0bce3ccb..fd8f5566 100644 --- a/src/si/electric_potential.rs +++ b/src/si/electric_potential.rs @@ -38,6 +38,13 @@ quantity! { @abvolt: 1.0_E-8; "abV", "abvolt", "abvolts"; @statvolt: 2.997_925_E2; "statV", "statvolt", "statvolts"; + + @decibel_volt: prefix!(none), 10.0, 20.0; "dBV", "decibel-volt", "decibel-volts"; + @decibel_millivolt: prefix!(milli), 10.0, 20.0; "dBmV", "decibel-millivolt", + "decibel-millivolts"; + @decibel_microvolt: prefix!(micro), 10.0, 20.0; "dBµV", "decibel-microvolt", + "decibel-microvolts"; + @decibel_unit: 7.746_E-1, 10.0, 20.0; "dBu", "decibel-unit", "decibel-units"; } } diff --git a/src/si/power.rs b/src/si/power.rs index 844c74a8..c83d3d8c 100644 --- a/src/si/power.rs +++ b/src/si/power.rs @@ -36,6 +36,12 @@ quantity! { @zeptowatt: prefix!(zepto); "zW", "zeptowatt", "zeptowatts"; @yoctowatt: prefix!(yocto); "yW", "yoctowatt", "yoctowatts"; + @decibel_watt: prefix!(none), 10.0, 10.0; "dBW", "decibel-watt", "decibel-watts"; + @decibel_milliwatt: prefix!(milli), 10.0, 10.0; "dBmW", "decibel-milliwatt", + "decibel-milliwatts"; + @decibel_microwatt: prefix!(micro), 10.0, 10.0; "dBµW", "decibel-microwatt", + "decibel-microwatts"; + @erg_per_second: 1.0_E-7; "erg/s", "erg per second", "ergs per second"; @foot_pound_per_hour: 3.766_161_111_111_111_E-4; "ft · lbf/h", "foot pound-force per hour", "foot pounds-force per hour"; diff --git a/src/si/ratio.rs b/src/si/ratio.rs index bd789871..26b44106 100644 --- a/src/si/ratio.rs +++ b/src/si/ratio.rs @@ -29,6 +29,8 @@ quantity! { @part_per_billion: 1.0_E-9; "ppb", "part per billion", "parts per billion"; @part_per_trillion: 1.0_E-12; "ppt", "part per trillion", "parts per trillion"; @part_per_quadrillion: 1.0_E-15; "ppq", "part per quadrillion", "parts per quadrillion"; + + @decibel: 1.0, 10.0, 10.0; "dB", "decibel", "decibels"; } } @@ -180,7 +182,7 @@ mod convert { #[cfg(test)] mod tests { storage_types! { - use crate::num::{FromPrimitive, One}; + use crate::num::{FromPrimitive, One, Zero}; use crate::si::quantities::*; use crate::si::ratio as r; use crate::tests::Test; @@ -217,6 +219,10 @@ mod tests { Test::assert_eq(&Ratio::new::(V::one() / V::from_f64(1.0_E15).unwrap()), &Ratio::new::(V::one())); + Test::assert_eq(&Ratio::new::(V::one()), + &Ratio::new::(V::zero())); + Test::assert_eq(&Ratio::new::(V::from_u8(10).unwrap()), + &Ratio::new::(V::from_u8(10).unwrap())); } } diff --git a/src/system.rs b/src/system.rs index f8f4e37c..fe83908c 100644 --- a/src/system.rs +++ b/src/system.rs @@ -317,12 +317,28 @@ macro_rules! system { let f = V::coefficient() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+; let n_cons = N::constant($crate::ConstantOp::Sub); - if n_coef < f { - (v * (f / n_coef) - n_cons).value() - } - else { - (v / (n_coef / f) - n_cons).value() - } + let result = if n_coef < f { + v * (f / n_coef) - n_cons + } + else { + v / (n_coef / f) - n_cons + }; + #[cfg(any(feature = "std", feature = "libm"))] + let result = { + use $crate::num::Zero; + + let scale = N::scale(); + let base = N::base(); + + if scale != V::T::zero() && base != V::T::zero() { + scale * result.log(base) + } + else { + result + } + }; + + result.value() } /// Convert a value from the given unit to base units. @@ -348,12 +364,29 @@ macro_rules! system { let f = V::coefficient() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+; let n_cons = N::constant($crate::ConstantOp::Add); - if n_coef >= f { - ((v + n_cons) * (n_coef / f)).value() - } - else { - (((v + n_cons) * n_coef) / f).value() - } + let result = + if n_coef >= f { + (v + n_cons) * (n_coef / f) + } + else { + ((v + n_cons) * n_coef) / f + }; + #[cfg(any(feature = "std", feature = "libm"))] + let result = { + use $crate::num::Zero; + + let scale = N::scale(); + let base = N::base(); + + if scale != V::T::zero() && base != V::T::zero() { + base.pow(result / scale) + } + else { + result + } + }; + + result.value() } autoconvert_test! { @@ -1548,8 +1581,8 @@ macro_rules! system { /// * `$V`: Underlying value storage type (e.g. `f32`). /// * `$U`: Optional. Base units. Pass as a tuple with the desired units: `(meter, kilogram, /// second, ampere, kelvin, mole, candela)`. The system's base units will be used if no - /// value is provided. Note that a unit with a non-zero constant factor is not currently - /// supported as a base unit. + /// value is provided. Note that a unit with a non-zero constant factor, base, or scale + /// is not currently supported as a base unit. /// /// An example invocation is given below for a meter-kilogram-second system setup in the /// module `mks` with a system of quantities name `Q`. The `#[macro_use]` attribute must be diff --git a/src/unit.rs b/src/unit.rs index cf86fc7b..a43ad806 100644 --- a/src/unit.rs +++ b/src/unit.rs @@ -12,11 +12,12 @@ /// * `quantity`: Path to the module where the [`quantity!`](macro.quantity.html) macro was run /// (e.g. `uom::si::length`). /// * `$unit`: Unit name (e.g. `meter`, `foot`). -/// * `$conversion`: Conversion (coefficient and constant factor) from the unit to the base unit of -/// the quantity (e.g. `3.048_E-1` to convert `foot` to `meter`. `1.0_E0, 273.15_E0` to convert -/// `celsius` to `kelvin`.). The coefficient is required and the constant factor is optional. -/// Note that using a unit with a non-zero constant factor is not currently supported as a base -/// unit. +/// * `$conversion`: Conversion (coefficient; coefficient and constant; or coefficient, base, and +/// scale) from the unit to the base unit of the quantity (e.g. `3.048_E-1` to convert `foot` to +/// `meter`. `1.0_E0, 273.15_E0` to convert `celsius` to `kelvin`. `1.0_E0, 10.0_E0, 20.0_E0` to +/// convert `decibel-volt` to `volt`). The coefficient is always required. The constant factor or +/// base and scale are optional. Note that using a unit with a non-zero constant factor, base, or +/// scale is not currently supported as a base unit. /// * `$abbreviation`: Unit abbreviation (e.g. `"m"`). /// * `$singular`: Singular unit description (e.g. `"meter"`). /// * `$plural`: Plural unit description (e.g. `"meters"`). @@ -119,48 +120,69 @@ macro_rules! unit { @units $($(#[$unit_attr:meta])* @$unit:ident: $($conversion:expr),+; $abbreviation:expr, $singular:expr, $plural:expr;)+ ) => { - $(unit!(@unit $(#[$unit_attr])* @$unit $plural); + $(//#[cfg(any())] + unit! { + @supported $($conversion),+; + unit!(@unit $(#[$unit_attr])* @$unit $plural); - impl __system::Unit for $unit { - #[inline(always)] - fn abbreviation() -> &'static str { - $abbreviation - } + impl __system::Unit for $unit { + #[inline(always)] + fn abbreviation() -> &'static str { + $abbreviation + } - #[inline(always)] - fn singular() -> &'static str { - $singular - } + #[inline(always)] + fn singular() -> &'static str { + $singular + } - #[inline(always)] - fn plural() -> &'static str { - $plural + #[inline(always)] + fn plural() -> &'static str { + $plural + } } - } - impl Unit for $unit {})+ + impl Unit for $unit {} + })+ storage_types! { types: Float; - $(impl $crate::Conversion for super::$unit { - type T = V; - - #[inline(always)] - #[allow(clippy::inconsistent_digit_grouping)] - fn coefficient() -> Self::T { - unit!(@coefficient $($conversion),+) - } - - #[inline(always)] - #[allow(unused_variables)] - #[allow(clippy::inconsistent_digit_grouping)] - fn constant(op: $crate::ConstantOp) -> Self::T { - unit!(@constant op $($conversion),+) + $(unit! { + @supported $($conversion),+; + impl $crate::Conversion for super::$unit { + type T = V; + + #[inline(always)] + #[allow(clippy::inconsistent_digit_grouping)] + fn coefficient() -> Self::T { + unit!(@coefficient $($conversion),+) + } + + #[inline(always)] + #[allow(unused_variables)] + #[allow(clippy::inconsistent_digit_grouping)] + fn constant(op: $crate::ConstantOp) -> Self::T { + unit!(@constant op $($conversion),+) + } + + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + #[allow(clippy::inconsistent_digit_grouping)] + fn base() -> Self::T { + unit!(@base $($conversion),+) + } + + #[cfg(any(feature = "std", feature = "libm"))] + #[inline(always)] + #[allow(clippy::inconsistent_digit_grouping)] + fn scale() -> Self::T { + unit!(@scale $($conversion),+) + } } - } - impl super::Conversion for super::$unit {})+ + impl super::Conversion for super::$unit {} + })+ } storage_types! { @@ -172,7 +194,8 @@ macro_rules! unit { ::from_f64(value).unwrap() } - $(impl $crate::Conversion for super::$unit { + $(unit!(@supported $($conversion),+); + impl $crate::Conversion for super::$unit { type T = T; #[inline(always)] @@ -187,6 +210,7 @@ macro_rules! unit { } } + unit!(@supported $($conversion),+); impl super::Conversion for super::$unit {})+ } @@ -204,7 +228,8 @@ macro_rules! unit { T::new(c.numer().to_biguint().unwrap(), c.denom().to_biguint().unwrap()) } - $(impl $crate::Conversion for super::$unit { + $(unit!(@supported $($conversion),+); + impl $crate::Conversion for super::$unit { type T = T; #[inline(always)] @@ -219,6 +244,7 @@ macro_rules! unit { } } + unit!(@supported $($conversion),+); impl super::Conversion for super::$unit {})+ } @@ -230,7 +256,8 @@ macro_rules! unit { ::from_f64(value).unwrap() } - $(impl $crate::Conversion for super::$unit { + $(unit!(@supported $($conversion),+); + impl $crate::Conversion for super::$unit { type T = V; #[inline(always)] @@ -245,13 +272,15 @@ macro_rules! unit { } } + unit!(@supported $($conversion),+); impl super::Conversion for super::$unit {})+ } storage_types! { types: Complex; - $(impl $crate::Conversion for super::$unit { + $(unit!(@supported $($conversion),+); + impl $crate::Conversion for super::$unit { type T = VV; #[inline(always)] @@ -268,6 +297,7 @@ macro_rules! unit { } } + unit!(@supported $($conversion),+); impl super::Conversion for super::$unit {})+ } }; @@ -283,13 +313,36 @@ macro_rules! unit { #[derive(Clone, Copy, Debug, Hash)] pub struct $unit; }; + (@supported $factor:expr, $base:expr, $scale:expr; $($tt:tt)+) => { + std! { + $($tt)+ + } + }; + (@supported $($conversion:expr),+; $($tt:tt)+) => { + $($tt)+ + }; + (@supported_attr $factor:expr, $base:expr, $scale:expr; $($tt:tt)+) => { + #[cfg(any(feature = "std", feature = "libm"))] + $($tt)+ + }; + (@supported_attr $($conversion:expr),+; $($tt:tt)+) => { + $($tt)+ + }; (@coefficient $factor:expr, $const:expr) => { $factor }; + (@coefficient $factor:expr, $base:expr, $scale:expr) => { $factor }; (@coefficient $factor:expr) => { $factor }; (@constant $op:ident $factor:expr, $const:expr) => { $const }; + (@constant $op:ident $factor:expr, $base:expr, $scale:expr) => { unit!(@constant $op $factor) }; (@constant $op:ident $factor:expr) => { match $op { $crate::ConstantOp::Add => -0.0, $crate::ConstantOp::Sub => 0.0, } }; + (@base $factor:expr, $const:expr) => { 1.0 }; + (@base $factor:expr, $base:expr, $scale:expr) => { $base }; + (@base $factor:expr) => { 1.0 }; + (@scale $factor:expr, $const:expr) => { 0.0 }; + (@scale $factor:expr, $base:expr, $scale:expr) => { $scale }; + (@scale $factor:expr) => { 0.0 }; }