#[macro_export]
macro_rules! impl_quantity {
($type:ident, $unit:ident, $display_units:expr) => {
use core::cmp::Ordering;
use core::fmt;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, Sub, SubAssign};
#[cfg(feature = "ndarray")]
use ndarray::{Array1, Array2, ArrayView1, ArrayView2};
use num_traits::FromPrimitive;
use num_traits::Zero;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use $crate::prelude::*;
#[cfg(feature = "pyo3")]
use pyo3::pyclass;
#[cfg(feature = "pyo3")]
use pyo3::{
basic::CompareOp,
exceptions::PyTypeError,
pymethods,
types::{PyAnyMethods, PyType},
Bound, IntoPyObject, Py, PyAny, PyRef, PyResult,
};
#[cfg(feature = "strum")]
use strum_macros::EnumIter;
use $crate::small_linalg::{Matrix3, Vector3};
#[cfg(feature = "pyo3")]
use $crate::Quantity;
#[cfg_attr(feature = "pyo3", pyclass(module = "unitforge", from_py_object))]
#[repr(C)]
#[derive(Copy, Clone)]
pub struct $type {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
pub(crate) multiplier: f64,
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
pub(crate) power: i32,
#[cfg(feature = "storage-f64")]
pub(crate) value: f64,
#[cfg(feature = "storage-f32")]
pub(crate) value: f32,
pub(crate) kind_id: u32,
}
impl $type {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
fn from_parts(multiplier: f64, power: i32) -> Self {
let (multiplier, power) =
$crate::internal::canonicalize_quantity_parts(multiplier, power);
Self {
multiplier,
power,
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
}
}
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
pub(crate) fn from_base_value(value: f64) -> Self {
let (multiplier, power) = <Self as $crate::PhysicsQuantity>::split_value(value);
Self::from_parts(multiplier, power)
}
#[cfg(feature = "storage-f64")]
pub(crate) fn from_base_value(value: f64) -> Self {
Self {
value,
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
}
}
#[cfg(feature = "storage-f32")]
pub(crate) fn from_base_value(value: f64) -> Self {
Self {
value: value as f32,
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
}
}
#[allow(dead_code)]
pub(crate) fn from_quantity_parts(multiplier: f64, power: i32) -> Self {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
Self::from_parts(multiplier, power)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
Self::from_base_value($crate::internal::quantity_parts_to_value(
multiplier, power,
))
}
}
#[cfg(feature = "serde")]
fn deserialize_parts<'de, D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
#[derive(Deserialize)]
struct QuantitySerde {
multiplier: f64,
power: i32,
}
let parts = QuantitySerde::deserialize(deserializer)?;
Ok(Self::from_parts(parts.multiplier, parts.power))
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
#[derive(Deserialize)]
struct QuantitySerde {
value: f64,
}
let parts = QuantitySerde::deserialize(deserializer)?;
Ok(Self::from_base_value(parts.value))
}
}
}
impl $type {
pub const fn new(value: f64, unit: $unit) -> Self {
let (unit_multiplier, unit_power) = unit.base_per_x_const();
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let (multiplier, power) =
$crate::internal::value_to_quantity_parts_const(value);
let (multiplier, power) = $crate::internal::canonicalize_quantity_parts_const(
multiplier * unit_multiplier,
$crate::internal::saturating_power_add_const(power, unit_power),
);
Self {
multiplier,
power,
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
}
}
#[cfg(feature = "storage-f64")]
{
Self {
value: value
* $crate::internal::quantity_parts_to_value_const(
unit_multiplier,
unit_power,
),
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
}
}
#[cfg(feature = "storage-f32")]
{
Self {
value: (value
* $crate::internal::quantity_parts_to_value_const(
unit_multiplier,
unit_power,
)) as f32,
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
}
}
}
}
#[cfg(feature = "serde")]
impl Serialize for $type {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
#[derive(Serialize)]
struct QuantitySerde {
multiplier: f64,
power: i32,
}
QuantitySerde {
multiplier: self.multiplier,
power: self.power,
}
.serialize(serializer)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
#[derive(Serialize)]
struct QuantitySerde {
value: f64,
}
QuantitySerde {
value: self.as_f64(),
}
.serialize(serializer)
}
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for $type {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::deserialize_parts(deserializer)
}
}
impl PhysicsQuantity for $type {
fn as_f64(&self) -> f64 {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
if self.multiplier == 0.0 {
return 0.0;
}
self.multiplier * 10_f64.powi(self.power)
}
#[cfg(feature = "storage-f64")]
{
self.value
}
#[cfg(feature = "storage-f32")]
{
f64::from(self.value)
}
}
type Unit = $unit;
fn new(value: f64, unit: Self::Unit) -> $type {
Self::new(value, unit)
}
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
fn split_value(v: f64) -> (f64, i32) {
if v.is_zero() {
(0.0, 0)
} else if v.is_nan() {
(f64::NAN, 0)
} else if v.is_infinite() {
if v > 0.0 {
(f64::INFINITY, 0)
} else {
(f64::NEG_INFINITY, 0)
}
} else {
let power = v.abs().log10().floor() as i32;
let multiplier = v / 10f64.powi(power);
(multiplier, power)
}
}
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
fn get_value(&self) -> f64 {
if self.multiplier == 0.0 {
return 0.0;
}
self.multiplier * 10_f64.powi(self.power)
}
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
fn get_power(&self) -> i32 {
self.power
}
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
fn get_multiplier(&self) -> f64 {
self.multiplier
}
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
fn get_tuple(&self) -> (f64, i32) {
(self.multiplier, self.power)
}
fn to(&self, unit: Self::Unit) -> f64 {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
if self.multiplier == 0.0 {
return 0.0;
}
let (unit_multiplier, unit_power) = unit.base_per_x();
self.multiplier / unit_multiplier
* $crate::internal::pow10_delta(self.power, unit_power)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
let (unit_multiplier, unit_power) = unit.base_per_x();
self.as_f64()
/ $crate::internal::quantity_parts_to_value(unit_multiplier, unit_power)
}
}
fn abs(self) -> Self {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
Self::from_parts(self.multiplier.abs(), self.power)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
Self::from_base_value(self.as_f64().abs())
}
}
fn is_nan(&self) -> bool {
self.as_f64().is_nan()
}
fn from_raw(value: f64) -> Self {
Self::from_base_value(value)
}
fn nan() -> Self {
Self {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
multiplier: f64::NAN,
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
power: 0,
#[cfg(feature = "storage-f64")]
value: f64::NAN,
#[cfg(feature = "storage-f32")]
value: f32::NAN,
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
}
}
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
fn from_exponential(multiplier: f64, power: i32) -> Self {
Self::from_parts(multiplier, power)
}
fn min(self, other: Self) -> Self {
if self < other {
self
} else {
other
}
}
fn max(self, other: Self) -> Self {
if self > other {
self
} else {
other
}
}
fn is_close(&self, other: &Self, tolerance: &Self) -> bool {
(self.as_f64() - other.as_f64()).abs() <= tolerance.as_f64().abs()
}
fn optimize(&mut self) {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
*self = Self::from_parts(self.multiplier, self.power);
}
}
const INFINITY: Self = Self {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
multiplier: f64::INFINITY,
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
power: 0,
#[cfg(feature = "storage-f64")]
value: f64::INFINITY,
#[cfg(feature = "storage-f32")]
value: f32::INFINITY,
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
};
const NEG_INFINITY: Self = Self {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
multiplier: f64::NEG_INFINITY,
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
power: 0,
#[cfg(feature = "storage-f64")]
value: f64::NEG_INFINITY,
#[cfg(feature = "storage-f32")]
value: f32::NEG_INFINITY,
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
};
}
impl $crate::UnitforgeQuantity for $type {}
impl From<f64> for $type {
fn from(value: f64) -> Self {
Self::from_raw(value)
}
}
#[cfg(feature = "pyo3")]
#[pymethods]
impl $type {
fn __mul__(lhs: PyRef<Self>, rhs: Py<PyAny>) -> PyResult<Py<PyAny>> {
let py = lhs.py();
let rhs_ref = rhs.bind(py);
let lhs_ref = lhs.into_pyobject(py)?;
let lhs_quantity = Quantity::from_py_any(&lhs_ref)
.map_err(|e| PyTypeError::new_err(e.to_string()))?;
if let Ok(rhs_vector) = rhs_ref.extract::<$crate::Vector3Py>() {
return Ok(rhs_vector
.mul_quantity_left(lhs_quantity)?
.into_pyobject(py)?
.into_any()
.unbind());
}
let rhs_quantity = Quantity::from_py_any(rhs_ref)
.map_err(|e| PyTypeError::new_err(e.to_string()))?;
match lhs_quantity * rhs_quantity {
Ok(value) => Ok(value.to_pyobject(py)?),
Err(_) => Err(PyTypeError::new_err(
"Multiplication of given objects is not possible.",
)),
}
}
fn __truediv__(lhs: PyRef<Self>, rhs: Py<PyAny>) -> PyResult<Py<PyAny>> {
let py = lhs.py();
let rhs_ref = rhs.bind(py);
let lhs_ref = lhs.into_pyobject(py)?;
let rhs_quantity = Quantity::from_py_any(rhs_ref)
.map_err(|e| PyTypeError::new_err(e.to_string()))?;
let lhs_quantity = Quantity::from_py_any(&lhs_ref)
.map_err(|e| PyTypeError::new_err(e.to_string()))?;
match lhs_quantity / rhs_quantity {
Ok(value) => Ok(value.to_pyobject(py)?),
Err(_) => Err(PyTypeError::new_err(
"Division of given objects is not possible.",
)),
}
}
fn __mod__(lhs: PyRef<Self>, rhs: Py<PyAny>) -> PyResult<Py<PyAny>> {
let py = lhs.py();
let rhs_ref = rhs.bind(py);
let lhs_ref = lhs.into_pyobject(py)?;
let rhs_quantity = Quantity::from_py_any(rhs_ref)
.map_err(|e| PyTypeError::new_err(e.to_string()))?;
let lhs_quantity = Quantity::from_py_any(&lhs_ref)
.map_err(|e| PyTypeError::new_err(e.to_string()))?;
match lhs_quantity % rhs_quantity {
Ok(value) => Ok(value.to_pyobject(py)?),
Err(_) => Err(PyTypeError::new_err(
"Modulo of given objects is not possible.",
)),
}
}
fn __rmul__(&self, rhs: f64) -> PyResult<Self> {
Ok(*self * rhs)
}
fn __pow__(
lhs: PyRef<Self>,
rhs: i32,
_modulo: Option<Py<PyAny>>,
) -> PyResult<Py<PyAny>> {
let py = lhs.py();
let lhs_ref = lhs.into_pyobject(py)?;
let lhs_quantity = Quantity::from_py_any(&lhs_ref)
.map_err(|e| PyTypeError::new_err(e.to_string()))?;
match lhs_quantity.powi(rhs) {
Ok(value) => Ok(value.to_pyobject(py)?),
Err(_) => Err(PyTypeError::new_err(
"Power of given object is not possible.",
)),
}
}
}
impl fmt::Display for $type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "no_std")]
{
let mut first = true;
for base_unit in $display_units {
if !first {
write!(f, ", ")?;
}
first = false;
let value = self.to(base_unit);
write!(f, "{}", value)?;
if !base_unit.name().starts_with('°') {
write!(f, " ")?;
}
write!(f, "{}", base_unit.name())?;
}
Ok(())
}
#[cfg(not(feature = "no_std"))]
{
use std::collections::HashMap;
let mut groups: HashMap<String, Vec<String>> = HashMap::new();
for base_unit in $display_units {
let value = self.to(base_unit);
let rounded = if value.is_zero() {
value
} else {
let digits = value.abs().log10().ceil() as i32;
let rounding_multiplier = 10_f64.powi(3 - digits);
(value * rounding_multiplier).round() / rounding_multiplier
};
let value = format!("{}", rounded);
groups
.entry(value)
.or_default()
.push(base_unit.name().to_string());
}
let mut parts = Vec::new();
for (value, units) in groups {
let joined_units = units.join(", ");
parts.push(format!(
"{}{}{}",
value,
if joined_units.starts_with('°') {
""
} else {
" "
},
joined_units
));
}
write!(f, "{}", parts.join(", "))
}
}
}
impl Neg for $type {
type Output = Self;
fn neg(self) -> Self::Output {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
Self::from_parts(-self.multiplier, self.power)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
Self::from_base_value(-self.as_f64())
}
}
}
impl PartialEq<Self> for $type
where
$type: PhysicsQuantity,
{
fn eq(&self, other: &Self) -> bool {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
self.is_close(
other,
&Self::from_parts(
self.multiplier,
$crate::internal::saturating_power_sub(self.power, 9),
),
)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
self.is_close(other, &Self::from_base_value(self.as_f64().abs() * 1.0e-9))
}
}
}
impl PartialOrd for $type
where
$type: PhysicsQuantity,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.as_f64().partial_cmp(&other.as_f64())
}
}
impl FromPrimitive for $type {
fn from_i64(n: i64) -> Option<Self> {
Some(Self::from_raw(n as f64))
}
fn from_u64(n: u64) -> Option<Self> {
Some(Self::from_raw(n as f64))
}
fn from_f64(n: f64) -> Option<Self> {
Some(Self::from_raw(n))
}
}
impl Add for $type {
type Output = Self;
fn add(self, other: Self) -> Self {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let common_power = self.power.max(other.power);
let multiplier = self.multiplier
* $crate::internal::pow10_delta(self.power, common_power)
+ other.multiplier
* $crate::internal::pow10_delta(other.power, common_power);
Self::from_parts(multiplier, common_power)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
Self::from_base_value(self.as_f64() + other.as_f64())
}
}
}
impl Sub for $type {
type Output = Self;
fn sub(self, other: Self) -> Self {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
if self.multiplier.is_infinite()
&& other.multiplier.is_infinite()
&& self.multiplier.is_sign_positive() == other.multiplier.is_sign_positive()
{
return Self::zero();
}
let common_power = $crate::internal::average_power(self.power, other.power);
let multiplier = self.multiplier
* $crate::internal::pow10_delta(self.power, common_power)
- other.multiplier
* $crate::internal::pow10_delta(other.power, common_power);
Self::from_parts(multiplier, common_power)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
Self::from_base_value(self.as_f64() - other.as_f64())
}
}
}
impl Div<f64> for $type {
type Output = Self;
fn div(self, rhs: f64) -> Self::Output {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let (rhs_multiplier, rhs_power) = Self::split_value(rhs);
Self::from_parts(
self.multiplier / rhs_multiplier,
$crate::internal::saturating_power_sub(self.power, rhs_power),
)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
Self::from_base_value(self.as_f64() / rhs)
}
}
}
impl Rem<f64> for $type {
type Output = Self;
fn rem(self, rhs: f64) -> Self::Output {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let (rhs_multiplier, rhs_power) = Self::split_value(rhs);
let (multiplier, power) = $crate::internal::quantity_parts_rem(
self.multiplier,
self.power,
rhs_multiplier,
rhs_power,
);
Self::from_parts(multiplier, power)
}
#[cfg(feature = "storage-f64")]
{
Self::from_base_value(self.value % rhs)
}
#[cfg(feature = "storage-f32")]
{
Self::from_base_value(f64::from(self.value % rhs as f32))
}
}
}
impl Mul<f64> for $type {
type Output = Self;
fn mul(self, rhs: f64) -> Self::Output {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let (rhs_multiplier, rhs_power) = Self::split_value(rhs);
Self::from_parts(
self.multiplier * rhs_multiplier,
$crate::internal::saturating_power_add(self.power, rhs_power),
)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
Self::from_base_value(self.as_f64() * rhs)
}
}
}
impl Mul<$type> for f64 {
type Output = $type;
fn mul(self, rhs: $type) -> Self::Output {
rhs * self
}
}
impl AddAssign for $type {
fn add_assign(&mut self, other: Self) {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let common_power = $crate::internal::average_power(self.power, other.power);
let multiplier = self.multiplier
* $crate::internal::pow10_delta(self.power, common_power)
+ other.multiplier
* $crate::internal::pow10_delta(other.power, common_power);
*self = Self::from_parts(multiplier, common_power);
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
*self = Self::from_base_value(self.as_f64() + other.as_f64());
}
}
}
impl SubAssign for $type {
fn sub_assign(&mut self, other: Self) {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
if self.multiplier.is_infinite()
&& other.multiplier.is_infinite()
&& self.multiplier.is_sign_positive() == other.multiplier.is_sign_positive()
{
*self = Self::zero();
return;
}
let common_power = $crate::internal::average_power(self.power, other.power);
let multiplier = self.multiplier
* $crate::internal::pow10_delta(self.power, common_power)
- other.multiplier
* $crate::internal::pow10_delta(other.power, common_power);
*self = Self::from_parts(multiplier, common_power);
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
*self = Self::from_base_value(self.as_f64() - other.as_f64());
}
}
}
impl MulAssign<f64> for $type {
fn mul_assign(&mut self, rhs: f64) {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let (rhs_multiplier, rhs_power) = Self::split_value(rhs);
*self = Self::from_parts(
self.multiplier * rhs_multiplier,
$crate::internal::saturating_power_add(self.power, rhs_power),
);
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
*self = Self::from_base_value(self.as_f64() * rhs);
}
}
}
impl DivAssign<f64> for $type {
fn div_assign(&mut self, rhs: f64) {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let (rhs_multiplier, rhs_power) = Self::split_value(rhs);
*self = Self::from_parts(
self.multiplier / rhs_multiplier,
$crate::internal::saturating_power_sub(self.power, rhs_power),
);
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
*self = Self::from_base_value(self.as_f64() / rhs);
}
}
}
impl Mul<Vector3<f64>> for $type {
type Output = Vector3<$type>;
fn mul(self, rhs: Vector3<f64>) -> Self::Output {
Vector3::new([self * rhs[0], self * rhs[1], self * rhs[2]])
}
}
impl Mul<Matrix3<f64>> for $type {
type Output = Matrix3<$type>;
fn mul(self, rhs: Matrix3<f64>) -> Self::Output {
Matrix3::new([
[self * rhs[(0, 0)], self * rhs[(0, 1)], self * rhs[(0, 2)]],
[self * rhs[(1, 0)], self * rhs[(1, 1)], self * rhs[(1, 2)]],
[self * rhs[(2, 0)], self * rhs[(2, 1)], self * rhs[(2, 2)]],
])
}
}
impl Zero for $type {
fn zero() -> Self {
Self {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
multiplier: 0.0,
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
power: 0,
#[cfg(feature = "storage-f64")]
value: 0.0,
#[cfg(feature = "storage-f32")]
value: 0.0,
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
}
}
fn is_zero(&self) -> bool {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
self.multiplier == 0.0
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
self.as_f64() == 0.0
}
}
}
impl fmt::Debug for $type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[cfg(feature = "ndarray")]
impl QuantityArray2<$unit> for Array2<$type> {
fn from_raw(raw: ArrayView2<f64>, unit: $unit) -> Self {
let mut res = Array2::zeros(raw.dim());
for i in 0..raw.dim().0 {
for j in 0..raw.dim().1 {
res[[i, j]] = <$type>::new(raw[[i, j]], unit);
}
}
res
}
fn to_raw(&self) -> Array2<f64> {
let mut res = Array2::zeros(self.dim());
for i in 0..self.dim().0 {
for j in 0..self.dim().1 {
res[[i, j]] = self[[i, j]].as_f64();
}
}
res
}
fn to(&self, unit: $unit) -> Array2<f64> {
let mut res = Array2::zeros(self.dim());
for i in 0..self.dim().0 {
for j in 0..self.dim().1 {
res[[i, j]] = self[[i, j]].to(unit);
}
}
res
}
}
#[cfg(feature = "ndarray")]
impl QuantityArray1<$unit> for Array1<$type> {
fn from_raw(raw: ArrayView1<f64>, unit: $unit) -> Self {
let mut res = Array1::zeros(raw.dim());
for i in 0..raw.dim() {
res[i] = <$type>::new(raw[i], unit);
}
res
}
fn to_raw(&self) -> Array1<f64> {
let mut res = Array1::zeros(self.dim());
for i in 0..self.dim() {
res[i] = self[i].as_f64();
}
res
}
fn to(&self, unit: $unit) -> Array1<f64> {
let mut res = Array1::zeros(self.dim());
for i in 0..self.dim() {
res[i] = self[i].to(unit);
}
res
}
}
#[cfg(feature = "pyo3")]
#[pymethods]
impl $type {
#[classmethod]
#[pyo3(name = "zero")]
fn zero_py(_cls: &Bound<'_, PyType>) -> Self {
Self::zero()
}
#[new]
fn new_py(value: f64, unit: $unit) -> PyResult<Self> {
Ok(Self::new(value, unit))
}
fn close_abs(&self, other: PyRef<Self>, threshold: Self) -> bool {
(self.clone() - other.clone()).abs() <= threshold
}
fn close_rel(&self, other: PyRef<Self>, threshold: f64) -> bool {
let mean = (self.clone() + other.clone()) / 2.;
(self.clone() - other.clone()).abs() <= mean * threshold
}
fn __richcmp__(&self, other: PyRef<Self>, op: CompareOp) -> bool {
match op {
CompareOp::Lt => self.clone() < other.clone(),
CompareOp::Le => self.clone() <= other.clone(),
CompareOp::Eq => self.clone() == other.clone(),
CompareOp::Ne => self.clone() != other.clone(),
CompareOp::Gt => self.clone() > other.clone(),
CompareOp::Ge => self.clone() >= other.clone(),
}
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!("{:?}", self))
}
fn __str__(&self) -> PyResult<String> {
Ok(format!("{:?}", self))
}
fn __add__(lhs: PyRef<Self>, rhs: PyRef<Self>) -> PyResult<Self> {
Ok(lhs.clone() + rhs.clone())
}
fn __neg__(lhs: PyRef<Self>) -> PyResult<Self> {
Ok(-lhs.clone())
}
fn __sub__(lhs: PyRef<Self>, rhs: PyRef<Self>) -> PyResult<Self> {
Ok(lhs.clone() - rhs.clone())
}
fn __abs__(&self) -> Self {
self.abs()
}
#[allow(clippy::wrong_self_convention)]
#[pyo3(name = "to")]
fn to_py(&self, unit: $unit) -> f64 {
self.to(unit)
}
}
impl $type {
pub fn kind_id(&self) -> u32 {
self.kind_id
}
pub fn optimal_unit<I>(&self, choices: I) -> Option<$unit>
where
I: IntoIterator<Item = $unit>,
{
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let mut min_value = i64::MAX;
let mut best_unit = None;
for unit in choices {
let difference =
(i64::from(self.power) - i64::from(unit.base_per_x().1)).abs();
if difference < min_value {
min_value = difference;
best_unit = Some(unit);
}
}
best_unit
}
#[cfg(all(
feature = "no_std",
any(feature = "storage-f64", feature = "storage-f32")
))]
{
let mut min_value = f64::INFINITY;
let mut best_unit = None;
for unit in choices {
let (unit_multiplier, unit_power) = unit.base_per_x();
let unit_scale =
$crate::internal::quantity_parts_to_value(unit_multiplier, unit_power);
let converted = (self.as_f64() / unit_scale).abs();
let difference = if converted == 0.0 {
f64::INFINITY
} else if converted < 1.0 {
1.0 / converted
} else {
converted
};
if difference < min_value {
min_value = difference;
best_unit = Some(unit);
}
}
best_unit
}
#[cfg(all(
not(feature = "no_std"),
any(feature = "storage-f64", feature = "storage-f32")
))]
{
let mut min_value = f64::INFINITY;
let mut best_unit = None;
for unit in choices {
let (unit_multiplier, unit_power) = unit.base_per_x();
let unit_scale =
$crate::internal::quantity_parts_to_value(unit_multiplier, unit_power);
let converted = (self.as_f64() / unit_scale).abs();
let difference = if converted == 0.0 {
f64::INFINITY
} else {
converted.log10().abs()
};
if difference < min_value {
min_value = difference;
best_unit = Some(unit);
}
}
best_unit
}
}
}
};
}
#[macro_export]
macro_rules! make_alias {
($base_quantity:ty, $base_unit:ty, $alias_quantity:ident, $alias_unit:ident) => {
pub type $alias_unit = $base_unit;
pub type $alias_quantity = $base_quantity;
};
}
#[macro_export]
macro_rules! impl_const {
($type:ident, $name:ident, $py_name:literal, $multiplier:expr, $power:expr) => {
impl $type {
pub const fn $name() -> Self {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let (multiplier, power) =
$crate::internal::canonicalize_quantity_parts_const($multiplier, $power);
Self {
multiplier,
power,
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
}
}
#[cfg(feature = "storage-f64")]
{
Self {
value: $crate::internal::quantity_parts_to_value_const($multiplier, $power),
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
}
}
#[cfg(feature = "storage-f32")]
{
Self {
value: $crate::internal::quantity_parts_to_value_const($multiplier, $power)
as f32,
kind_id: <Self as $crate::QuantityKindId>::KIND_ID,
}
}
}
}
#[cfg(feature = "pyo3")]
$crate::__private::paste::paste! {
#[pymethods]
impl $type {
#[staticmethod]
#[pyo3(name = $py_name)]
pub fn [<__unitforge_const_ $name _py>]() -> Self {
Self::$name()
}
}
}
};
}
#[macro_export]
macro_rules! impl_div_with_self_to_f64 {
($lhs:ty) => {
impl Div<$lhs> for $lhs {
type Output = f64;
fn div(self, rhs: Self) -> Self::Output {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
(self.multiplier / rhs.multiplier)
* $crate::internal::pow10_delta(self.power, rhs.power)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
self.as_f64() / rhs.as_f64()
}
}
}
impl core::ops::Rem<$lhs> for $lhs {
type Output = f64;
fn rem(self, rhs: Self) -> Self::Output {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let (multiplier, power) = $crate::internal::quantity_parts_rem(
self.multiplier,
self.power,
rhs.multiplier,
rhs.power,
);
$crate::internal::quantity_parts_to_value(multiplier, power)
}
#[cfg(feature = "storage-f64")]
{
self.value % rhs.value
}
#[cfg(feature = "storage-f32")]
{
f64::from(self.value % rhs.value)
}
}
}
};
}
#[macro_export]
macro_rules! impl_mul {
($lhs:ty, $rhs:ty, $result:ty) => {
impl core::ops::Mul<$rhs> for $lhs {
type Output = $result;
fn mul(self, rhs: $rhs) -> Self::Output {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
<$result>::from_exponential(
self.multiplier * rhs.multiplier,
$crate::internal::saturating_power_add(self.power, rhs.power),
)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
<$result as $crate::PhysicsQuantity>::from_raw(self.as_f64() * rhs.as_f64())
}
}
}
impl core::ops::Mul<$lhs> for $rhs {
type Output = $result;
fn mul(self, rhs: $lhs) -> Self::Output {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
<$result>::from_exponential(
self.multiplier * rhs.multiplier,
$crate::internal::saturating_power_add(self.power, rhs.power),
)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
<$result as $crate::PhysicsQuantity>::from_raw(self.as_f64() * rhs.as_f64())
}
}
}
#[cfg(feature = "ndarray")]
impl MulArray1<$rhs> for Array1<$lhs> {
type Output = Array1<$result>;
fn mul_array1(self, rhs: Array1<$rhs>) -> Array1<$result> {
self.into_iter()
.zip(rhs.into_iter())
.map(|(force, distance)| force * distance)
.collect()
}
}
#[cfg(feature = "ndarray")]
impl MulArray2<$rhs> for Array2<$lhs> {
type Output = Array2<$result>;
fn mul_array2(self, rhs: Array2<$rhs>) -> $crate::LinalgResult<Array2<$result>> {
if self.dim() != rhs.dim() {
let (expected_rows, expected_cols) = self.dim();
let (actual_rows, actual_cols) = rhs.dim();
return Err($crate::LinalgError::ShapeMismatch {
expected: $crate::LinalgShape::Matrix {
rows: expected_rows,
cols: expected_cols,
},
actual: $crate::LinalgShape::Matrix {
rows: actual_rows,
cols: actual_cols,
},
});
}
let mut results = Vec::new();
for (lhs_row, rhs_row) in self.outer_iter().zip(rhs.outer_iter()) {
let result_row: Array1<$result> = lhs_row
.iter()
.zip(rhs_row.iter())
.map(|(&lhs, &rhs)| lhs * rhs)
.collect();
results.push(result_row);
}
let nrows = results.len();
let ncols = if nrows > 0 { results[0].len() } else { 0 };
let data: Vec<$result> = results
.into_iter()
.flat_map(|r| {
let (raw_vec, _) = r.into_raw_vec_and_offset();
raw_vec
})
.collect();
Array2::from_shape_vec((nrows, ncols), data)
.map_err(|_| $crate::LinalgError::ShapeConstructionFailed)
}
}
#[cfg(feature = "ndarray")]
impl MulArray1<$lhs> for Array1<$rhs> {
type Output = Array1<$result>;
fn mul_array1(self, rhs: Array1<$lhs>) -> Array1<$result> {
self.into_iter()
.zip(rhs.into_iter())
.map(|(force, distance)| force * distance)
.collect()
}
}
#[cfg(feature = "ndarray")]
impl MulArray2<$lhs> for Array2<$rhs> {
type Output = Array2<$result>;
fn mul_array2(self, rhs: Array2<$lhs>) -> $crate::LinalgResult<Array2<$result>> {
if self.dim() != rhs.dim() {
let (expected_rows, expected_cols) = self.dim();
let (actual_rows, actual_cols) = rhs.dim();
return Err($crate::LinalgError::ShapeMismatch {
expected: $crate::LinalgShape::Matrix {
rows: expected_rows,
cols: expected_cols,
},
actual: $crate::LinalgShape::Matrix {
rows: actual_rows,
cols: actual_cols,
},
});
}
let mut results = Vec::new();
for (force_row, distance_row) in self.outer_iter().zip(rhs.outer_iter()) {
let result_row: Array1<$result> = force_row
.iter()
.zip(distance_row.iter())
.map(|(&force, &distance)| force * distance)
.collect();
results.push(result_row);
}
let nrows = results.len();
let ncols = if nrows > 0 { results[0].len() } else { 0 };
let data: Vec<$result> = results
.into_iter()
.flat_map(|r| {
let (raw_vec, _) = r.into_raw_vec_and_offset();
raw_vec
})
.collect();
Array2::from_shape_vec((nrows, ncols), data)
.map_err(|_| $crate::LinalgError::ShapeConstructionFailed)
}
}
};
}
#[macro_export]
macro_rules! impl_mul_with_self {
($lhs:ty,$result:ty) => {
impl core::ops::Mul<$lhs> for $lhs {
type Output = $result;
fn mul(self, rhs: $lhs) -> Self::Output {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
<$result>::from_exponential(
self.multiplier * rhs.multiplier,
$crate::internal::saturating_power_add(self.power, rhs.power),
)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
<$result as $crate::PhysicsQuantity>::from_raw(self.as_f64() * rhs.as_f64())
}
}
}
#[cfg(feature = "ndarray")]
impl MulArray1<$lhs> for Array1<$lhs> {
type Output = Array1<$result>;
fn mul_array1(self, rhs: Array1<$lhs>) -> Array1<$result> {
self.into_iter()
.zip(rhs.into_iter())
.map(|(force, distance)| force * distance)
.collect()
}
}
#[cfg(feature = "ndarray")]
impl MulArray2<$lhs> for Array2<$lhs> {
type Output = Array2<$result>;
fn mul_array2(self, rhs: Array2<$lhs>) -> $crate::LinalgResult<Array2<$result>> {
if self.dim() != rhs.dim() {
let (expected_rows, expected_cols) = self.dim();
let (actual_rows, actual_cols) = rhs.dim();
return Err($crate::LinalgError::ShapeMismatch {
expected: $crate::LinalgShape::Matrix {
rows: expected_rows,
cols: expected_cols,
},
actual: $crate::LinalgShape::Matrix {
rows: actual_rows,
cols: actual_cols,
},
});
}
let mut results = Vec::new();
for (force_row, distance_row) in self.outer_iter().zip(rhs.outer_iter()) {
let result_row: Array1<$result> = force_row
.iter()
.zip(distance_row.iter())
.map(|(&force, &distance)| force * distance)
.collect();
results.push(result_row);
}
let nrows = results.len();
let ncols = if nrows > 0 { results[0].len() } else { 0 };
let data: Vec<$result> = results
.into_iter()
.flat_map(|r| {
let (raw_vec, _) = r.into_raw_vec_and_offset();
raw_vec
})
.collect();
Array2::from_shape_vec((nrows, ncols), data)
.map_err(|_| $crate::LinalgError::ShapeConstructionFailed)
}
}
};
}
#[macro_export]
macro_rules! impl_div {
($lhs:ty, $rhs:ty, $result:ty) => {
impl core::ops::Div<$rhs> for $lhs {
type Output = $result;
fn div(self, rhs: $rhs) -> Self::Output {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
<$result>::from_exponential(
self.multiplier / rhs.multiplier,
$crate::internal::saturating_power_sub(self.power, rhs.power),
)
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
<$result as $crate::PhysicsQuantity>::from_raw(self.as_f64() / rhs.as_f64())
}
}
}
impl core::ops::Rem<$rhs> for $lhs {
type Output = $result;
fn rem(self, rhs: $rhs) -> Self::Output {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
let (multiplier, power) = $crate::internal::quantity_parts_rem(
self.multiplier,
self.power,
rhs.multiplier,
rhs.power,
);
<$result>::from_exponential(multiplier, power)
}
#[cfg(feature = "storage-f64")]
{
<$result as $crate::PhysicsQuantity>::from_raw(self.value % rhs.value)
}
#[cfg(feature = "storage-f32")]
{
<$result as $crate::PhysicsQuantity>::from_raw(f64::from(
self.value % rhs.value,
))
}
}
}
#[cfg(feature = "ndarray")]
impl DivArray1<$rhs> for Array1<$lhs> {
type Output = Array1<$result>;
fn div_array1(self, rhs: Array1<$rhs>) -> Array1<$result> {
self.into_iter()
.zip(rhs.into_iter())
.map(|(force, distance)| force / distance)
.collect()
}
}
#[cfg(feature = "ndarray")]
impl DivArray2<$rhs> for Array2<$lhs> {
type Output = Array2<$result>;
fn div_array2(self, rhs: Array2<$rhs>) -> $crate::LinalgResult<Array2<$result>> {
if self.dim() != rhs.dim() {
let (expected_rows, expected_cols) = self.dim();
let (actual_rows, actual_cols) = rhs.dim();
return Err($crate::LinalgError::ShapeMismatch {
expected: $crate::LinalgShape::Matrix {
rows: expected_rows,
cols: expected_cols,
},
actual: $crate::LinalgShape::Matrix {
rows: actual_rows,
cols: actual_cols,
},
});
}
let mut results = Vec::new();
for (force_row, distance_row) in self.outer_iter().zip(rhs.outer_iter()) {
let result_row: Array1<$result> = force_row
.iter()
.zip(distance_row.iter())
.map(|(&force, &distance)| force / distance)
.collect();
results.push(result_row);
}
let nrows = results.len();
let ncols = if nrows > 0 { results[0].len() } else { 0 };
let data: Vec<$result> = results
.into_iter()
.flat_map(|r| {
let (raw_vec, _) = r.into_raw_vec_and_offset();
raw_vec
})
.collect();
Array2::from_shape_vec((nrows, ncols), data)
.map_err(|_| $crate::LinalgError::ShapeConstructionFailed)
}
}
};
}
#[macro_export]
macro_rules! impl_sqrt {
($lhs:ty, $res:ty) => {
impl Sqrt<$res> for $lhs {
fn sqrt(self) -> $res {
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
{
if self.power % 2 == 0 {
<$res>::from_exponential(self.multiplier.sqrt(), self.power / 2)
} else {
<$res>::from_exponential(
self.multiplier.sqrt() * 10_f64.sqrt(),
$crate::internal::saturating_power_sub(self.power, 1) / 2,
)
}
}
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
{
<$res as $crate::PhysicsQuantity>::from_raw($crate::internal::sqrt(
self.as_f64(),
))
}
}
}
#[cfg(feature = "pyo3")]
#[pymethods]
impl $lhs {
#[pyo3(name = "sqrt")]
fn sqrt_py(&self) -> $res {
self.sqrt()
}
}
};
}
#[macro_export]
macro_rules! impl_mul_relation_with_self {
($lhs:ty, $res:ty) => {
impl_mul_with_self!($lhs, $res);
impl_sqrt!($res, $lhs);
impl_div!($res, $lhs, $lhs);
};
}
#[macro_export]
macro_rules! impl_mul_relation_with_other {
($lhs:ty, $rhs:ty, $res:ty) => {
impl_mul!($lhs, $rhs, $res);
impl_div!($res, $lhs, $rhs);
impl_div!($res, $rhs, $lhs);
};
}
pub mod macros {
pub use crate::impl_const;
pub use crate::impl_div;
pub use crate::impl_div_with_self_to_f64;
pub use crate::impl_mul;
pub use crate::impl_mul_relation_with_other;
pub use crate::impl_mul_relation_with_self;
pub use crate::impl_mul_with_self;
pub use crate::impl_quantity;
pub use crate::impl_sqrt;
pub use crate::make_alias;
}