unitforge 0.5.12__tar.gz → 0.5.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unitforge-0.5.12 → unitforge-0.5.13}/Cargo.lock +2 -2
- {unitforge-0.5.12 → unitforge-0.5.13}/Cargo.toml +2 -2
- {unitforge-0.5.12 → unitforge-0.5.13}/PKG-INFO +17 -1
- {unitforge-0.5.12 → unitforge-0.5.13}/Readme.md +11 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/Readme_py.md +14 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/install_hooks.sh +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/impl_macros.rs +135 -176
- unitforge-0.5.13/src/internal.rs +684 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/prelude.rs +2 -20
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/acceleration.rs +2 -2
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/angle.rs +3 -2
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/area.rs +3 -3
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/density.rs +4 -4
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/distance.rs +12 -12
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/force.rs +2 -2
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/force_distance.rs +2 -2
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/inverse_distance.rs +4 -4
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/mass.rs +6 -6
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/rotational_stiffness.rs +4 -2
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/stress.rs +1 -1
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/stress_rate.rs +1 -1
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/time.rs +11 -2
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/velocity.rs +9 -2
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/volume.rs +3 -3
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities.template.rs +20 -3
- {unitforge-0.5.12 → unitforge-0.5.13}/src/relations.rs +0 -1
- {unitforge-0.5.12 → unitforge-0.5.13}/src/small_linalg/bindings/matrix3_py.template.rs +31 -17
- {unitforge-0.5.12 → unitforge-0.5.13}/src/small_linalg/bindings/vector3_py.template.rs +46 -58
- {unitforge-0.5.12 → unitforge-0.5.13}/src/small_linalg/matrix2.rs +8 -11
- {unitforge-0.5.12 → unitforge-0.5.13}/src/small_linalg/matrix2x3.rs +6 -4
- {unitforge-0.5.12 → unitforge-0.5.13}/src/small_linalg/matrix3.rs +8 -10
- {unitforge-0.5.12 → unitforge-0.5.13}/src/small_linalg/matrix3x2.rs +6 -3
- {unitforge-0.5.12 → unitforge-0.5.13}/src/small_linalg/vector2.rs +4 -3
- {unitforge-0.5.12 → unitforge-0.5.13}/src/small_linalg/vector3.rs +5 -5
- {unitforge-0.5.12 → unitforge-0.5.13}/test.sh +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/quantity_core/constants.rs +2 -2
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/quantity_core/construction.rs +38 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/quantity_core/normalization.rs +73 -2
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/quantity_core/runtime_quantity.rs +46 -0
- unitforge-0.5.13/tests/quantity_core/scalar_edges.rs +126 -0
- unitforge-0.5.13/tests/quantity_core/unit_factors.rs +125 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/quantity_core.rs +2 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/small_linalg_edge_cases.rs +75 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/test_quantities.py +26 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/test_small_linalg.py +50 -5
- {unitforge-0.5.12 → unitforge-0.5.13}/unitforge_macros/Cargo.toml +1 -1
- {unitforge-0.5.12 → unitforge-0.5.13}/unitforge_macros/src/lib.rs +108 -16
- unitforge-0.5.12/python/unitforge/__init__.pyi +0 -3548
- unitforge-0.5.12/python/unitforge/py.typed +0 -0
- unitforge-0.5.12/src/internal.rs +0 -331
- unitforge-0.5.12/tests/quantity_core/scalar_edges.rs +0 -46
- {unitforge-0.5.12 → unitforge-0.5.13}/.gitignore +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/.gitlab-ci.yml +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/pyproject.toml +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/python/unitforge/__init__.py +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/scripts/generate_python_stubs.py +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/errors.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/lib.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/angular_acceleration.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/angular_velocity.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/area_of_moment.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/charge.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/compliance.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/density_rate.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/density_temperature.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/density_temperature_rate.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/force_area.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/force_div_distance_power_four.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/force_per_volume.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/force_stress.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/force_volume.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/frequency.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/heat_capacity.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/heat_flux.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/inverse_angle.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/inverse_area.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/inverse_stress.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/inverse_temperature.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/mass_per_distance.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/molar_mass.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/power.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/pressure_rate.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/specific_energy.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/specific_gas_constant.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/specific_heat_capacity.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/stiffness.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/strain.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/strain_squared.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/stress_cubed.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/stress_per_temperature.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/stress_rate_per_temperature.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/stress_squared.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/temperature.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/temperature_gradient.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/thermal_conductance.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/thermal_conductivity.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/universal_gas_constant.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/velocity_squared.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/voltage.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/volumetric_energy_density.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/src/quantities/volumetric_heat_capacity.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/common/mod.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/matrix2.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/matrix2x3.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/matrix3.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/matrix3_eigen.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/matrix3x2.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/ndarray_dot.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/quantities.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/quantity_core/enum_iter.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/quantity_core/metadata.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/quantity_core/plain_storage.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/quantity_core/relations.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/quantity_serde.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/small_linalg_interfaces.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/thermal_transient.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/typecheck/mypy_smoke.py +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/vector2.rs +0 -0
- {unitforge-0.5.12 → unitforge-0.5.13}/tests/vector3.rs +0 -0
|
@@ -351,7 +351,7 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
|
|
351
351
|
|
|
352
352
|
[[package]]
|
|
353
353
|
name = "unitforge"
|
|
354
|
-
version = "0.5.
|
|
354
|
+
version = "0.5.13"
|
|
355
355
|
dependencies = [
|
|
356
356
|
"ndarray",
|
|
357
357
|
"num-traits",
|
|
@@ -367,7 +367,7 @@ dependencies = [
|
|
|
367
367
|
|
|
368
368
|
[[package]]
|
|
369
369
|
name = "unitforge_macros"
|
|
370
|
-
version = "0.5.
|
|
370
|
+
version = "0.5.13"
|
|
371
371
|
dependencies = [
|
|
372
372
|
"regex",
|
|
373
373
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "unitforge"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.13"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["Dr.Q team <info@askdrq.com>", "Henrik Stromberg <henrik@askdrq.com>"]
|
|
6
6
|
description = "A library for unit and quantity consistent computations in Rust"
|
|
@@ -20,7 +20,7 @@ pyo3 = { version = "0.28.3", features = ["extension-module", "auto-initialize",
|
|
|
20
20
|
numpy = { version = "0.28.0", optional = true }
|
|
21
21
|
strum = { version = "0.28.0", optional = true }
|
|
22
22
|
strum_macros = { version = "0.28.0", optional = true }
|
|
23
|
-
unitforge_macros = { version = "0.5.
|
|
23
|
+
unitforge_macros = { version = "0.5.13", path = "unitforge_macros" }
|
|
24
24
|
|
|
25
25
|
[features]
|
|
26
26
|
default = []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unitforge
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.13
|
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
|
5
5
|
Classifier: Programming Language :: Rust
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -9,7 +9,9 @@ Classifier: Topic :: Scientific/Engineering :: Physics
|
|
|
9
9
|
Requires-Dist: numpy>=2.2.0
|
|
10
10
|
Summary: A library for unit and quantity consistent computations in Rust
|
|
11
11
|
Keywords: units,quantities,simulation,physics
|
|
12
|
+
Author: Dr.Q team <info@askdrq.com>, Henrik Stromberg <henrik@askdrq.com>
|
|
12
13
|
Author-email: "Dr.Q team" <info@askdrq.com>, Henrik Stromberg <henrik@askdrq.com>
|
|
14
|
+
License: MIT
|
|
13
15
|
Requires-Python: >=3.7
|
|
14
16
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
15
17
|
Project-URL: Repository, https://gitlab.com/henrikjstromberg/unitforge
|
|
@@ -72,6 +74,20 @@ f_sum = f1 + f2
|
|
|
72
74
|
print(f_sum.to(ForceUnit.N)) # → 8.0
|
|
73
75
|
```
|
|
74
76
|
|
|
77
|
+
### Comparing Quantities
|
|
78
|
+
|
|
79
|
+
`==` is tolerance-based: two quantities are equal when they differ by at most `1e-9` of the larger
|
|
80
|
+
magnitude. An infinity equals only itself, and NaN equals nothing. `<`, `<=`, `>` and `>=` compare
|
|
81
|
+
exactly, so for nearly equal values `a == b` and `a < b` can both be true. For explicit tolerances
|
|
82
|
+
use `a.close_abs(b, threshold)` (absolute, `threshold` is a quantity) or
|
|
83
|
+
`a.close_rel(b, threshold)` (relative to the mean magnitude). Quantities are not hashable.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
a = Force(1.0, ForceUnit.N)
|
|
87
|
+
b = Force(1.0 + 1e-12, ForceUnit.N)
|
|
88
|
+
print(a == b, a < b) # → True True
|
|
89
|
+
```
|
|
90
|
+
|
|
75
91
|
---
|
|
76
92
|
|
|
77
93
|
## Vector and Matrix Support
|
|
@@ -15,6 +15,17 @@
|
|
|
15
15
|
- **Serialization**: Optional support for serde using `features = ["serde"]`; combine with `ndarray` for ndarray serialization support.
|
|
16
16
|
- **python interface** Optional Python interface when building with flag pyo3; also available on pypi
|
|
17
17
|
|
|
18
|
+
## Comparing quantities
|
|
19
|
+
|
|
20
|
+
`==` is tolerance-based: two quantities are equal when they differ by at most `1e-9` of the larger
|
|
21
|
+
magnitude. An infinity equals only itself, and NaN equals nothing. Ordering (`<`, `<=`, `>`, `>=`)
|
|
22
|
+
compares the stored values exactly. As a consequence:
|
|
23
|
+
|
|
24
|
+
- `==` is not transitive, and for nearly equal values `a == b` and `a < b` can both be true.
|
|
25
|
+
- `Hash` hashes the exact stored value, so quantities that compare equal can hash differently. Do
|
|
26
|
+
not rely on hash-based collections to merge nearly equal quantities.
|
|
27
|
+
- For an explicit, absolute tolerance use `a.is_close(&b, &tolerance)`.
|
|
28
|
+
|
|
18
29
|
## Contribute
|
|
19
30
|
|
|
20
31
|
All contributions are welcome! Feel free to implement new quantities or define relations using `impl_macros.rs`. 🚀
|
|
@@ -56,6 +56,20 @@ f_sum = f1 + f2
|
|
|
56
56
|
print(f_sum.to(ForceUnit.N)) # → 8.0
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
### Comparing Quantities
|
|
60
|
+
|
|
61
|
+
`==` is tolerance-based: two quantities are equal when they differ by at most `1e-9` of the larger
|
|
62
|
+
magnitude. An infinity equals only itself, and NaN equals nothing. `<`, `<=`, `>` and `>=` compare
|
|
63
|
+
exactly, so for nearly equal values `a == b` and `a < b` can both be true. For explicit tolerances
|
|
64
|
+
use `a.close_abs(b, threshold)` (absolute, `threshold` is a quantity) or
|
|
65
|
+
`a.close_rel(b, threshold)` (relative to the mean magnitude). Quantities are not hashable.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
a = Force(1.0, ForceUnit.N)
|
|
69
|
+
b = Force(1.0 + 1e-12, ForceUnit.N)
|
|
70
|
+
print(a == b, a < b) # → True True
|
|
71
|
+
```
|
|
72
|
+
|
|
59
73
|
---
|
|
60
74
|
|
|
61
75
|
## Vector and Matrix Support
|
|
File without changes
|
|
@@ -56,6 +56,9 @@ macro_rules! impl_quantity {
|
|
|
56
56
|
pub(crate) kind_id: u32,
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// Hashes the stored value: canonical parts (which every constructor produces
|
|
60
|
+
// deterministically) or the plain float with `-0.0` folded into `0.0`. `==` allows a
|
|
61
|
+
// relative tolerance, so values that are merely close can still hash differently.
|
|
59
62
|
impl core::hash::Hash for $type {
|
|
60
63
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
61
64
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
@@ -64,11 +67,26 @@ macro_rules! impl_quantity {
|
|
|
64
67
|
}
|
|
65
68
|
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
|
|
66
69
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
67
|
-
core::hash::Hash::hash(
|
|
70
|
+
core::hash::Hash::hash(
|
|
71
|
+
&$crate::internal::canonical_float_bits(f64::from(self.value)),
|
|
72
|
+
state,
|
|
73
|
+
);
|
|
68
74
|
}
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
impl $type {
|
|
78
|
+
/// Whether both hold exactly the same value (never true for NaN).
|
|
79
|
+
fn same_value(&self, other: &Self) -> bool {
|
|
80
|
+
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
81
|
+
{
|
|
82
|
+
self.multiplier == other.multiplier && self.power == other.power
|
|
83
|
+
}
|
|
84
|
+
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
|
|
85
|
+
{
|
|
86
|
+
self.value == other.value
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
72
90
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
73
91
|
fn from_parts(multiplier: f64, power: i32) -> Self {
|
|
74
92
|
let (multiplier, power) =
|
|
@@ -82,8 +100,7 @@ macro_rules! impl_quantity {
|
|
|
82
100
|
|
|
83
101
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
84
102
|
pub(crate) fn from_base_value(value: f64) -> Self {
|
|
85
|
-
|
|
86
|
-
Self::from_parts(multiplier, power)
|
|
103
|
+
Self::from_parts(value, 0)
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
#[cfg(feature = "storage-f64")]
|
|
@@ -160,8 +177,9 @@ macro_rules! impl_quantity {
|
|
|
160
177
|
);
|
|
161
178
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
162
179
|
{
|
|
180
|
+
// Same normalization as `from_raw`, so both agree bit for bit.
|
|
163
181
|
let (multiplier, power) =
|
|
164
|
-
$crate::internal::
|
|
182
|
+
$crate::internal::canonicalize_quantity_parts_const(value, 0);
|
|
165
183
|
let (multiplier, power) = $crate::internal::canonicalize_quantity_parts_const(
|
|
166
184
|
multiplier * unit_multiplier,
|
|
167
185
|
$crate::internal::saturating_power_add_const(power, unit_power),
|
|
@@ -246,10 +264,7 @@ macro_rules! impl_quantity {
|
|
|
246
264
|
fn as_f64(&self) -> f64 {
|
|
247
265
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
248
266
|
{
|
|
249
|
-
|
|
250
|
-
return 0.0;
|
|
251
|
-
}
|
|
252
|
-
self.multiplier * 10_f64.powi(self.power)
|
|
267
|
+
$crate::internal::quantity_parts_to_value(self.multiplier, self.power)
|
|
253
268
|
}
|
|
254
269
|
#[cfg(feature = "storage-f64")]
|
|
255
270
|
{
|
|
@@ -269,29 +284,12 @@ macro_rules! impl_quantity {
|
|
|
269
284
|
|
|
270
285
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
271
286
|
fn split_value(v: f64) -> (f64, i32) {
|
|
272
|
-
|
|
273
|
-
(0.0, 0)
|
|
274
|
-
} else if v.is_nan() {
|
|
275
|
-
(f64::NAN, 0)
|
|
276
|
-
} else if v.is_infinite() {
|
|
277
|
-
if v > 0.0 {
|
|
278
|
-
(f64::INFINITY, 0)
|
|
279
|
-
} else {
|
|
280
|
-
(f64::NEG_INFINITY, 0)
|
|
281
|
-
}
|
|
282
|
-
} else {
|
|
283
|
-
let power = v.abs().log10().floor() as i32;
|
|
284
|
-
let multiplier = v / 10f64.powi(power);
|
|
285
|
-
(multiplier, power)
|
|
286
|
-
}
|
|
287
|
+
$crate::internal::split_decimal(v)
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
290
291
|
fn get_value(&self) -> f64 {
|
|
291
|
-
|
|
292
|
-
return 0.0;
|
|
293
|
-
}
|
|
294
|
-
self.multiplier * 10_f64.powi(self.power)
|
|
292
|
+
self.as_f64()
|
|
295
293
|
}
|
|
296
294
|
|
|
297
295
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
@@ -314,12 +312,11 @@ macro_rules! impl_quantity {
|
|
|
314
312
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
315
313
|
{
|
|
316
314
|
if offset == 0.0 {
|
|
317
|
-
if self.multiplier == 0.0 {
|
|
318
|
-
return 0.0;
|
|
319
|
-
}
|
|
320
315
|
let (unit_multiplier, unit_power) = unit.base_per_x();
|
|
321
|
-
return
|
|
322
|
-
|
|
316
|
+
return $crate::internal::quantity_parts_to_value(
|
|
317
|
+
self.multiplier / unit_multiplier,
|
|
318
|
+
$crate::internal::saturating_power_sub(self.power, unit_power),
|
|
319
|
+
);
|
|
323
320
|
}
|
|
324
321
|
// Affine units (non-zero offset) go through `as_f64()`
|
|
325
322
|
// instead of the mantissa/power fast path above, since
|
|
@@ -394,7 +391,8 @@ macro_rules! impl_quantity {
|
|
|
394
391
|
}
|
|
395
392
|
|
|
396
393
|
fn is_close(&self, other: &Self, tolerance: &Self) -> bool {
|
|
397
|
-
|
|
394
|
+
// Identical values, equal infinities included, are always close.
|
|
395
|
+
self.same_value(other) || (*self - *other).abs() <= tolerance.abs()
|
|
398
396
|
}
|
|
399
397
|
|
|
400
398
|
fn optimize(&mut self) {
|
|
@@ -540,24 +538,18 @@ macro_rules! impl_quantity {
|
|
|
540
538
|
}
|
|
541
539
|
#[cfg(not(feature = "no_std"))]
|
|
542
540
|
{
|
|
543
|
-
|
|
544
|
-
let mut groups:
|
|
545
|
-
|
|
541
|
+
// Units showing the same rounded value share an entry, in display-unit order.
|
|
542
|
+
let mut groups: Vec<(String, Vec<String>)> = Vec::new();
|
|
546
543
|
for base_unit in $display_units {
|
|
547
|
-
let value =
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
(
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
let value = format!("{}", rounded);
|
|
557
|
-
groups
|
|
558
|
-
.entry(value)
|
|
559
|
-
.or_default()
|
|
560
|
-
.push(base_unit.name().to_string());
|
|
544
|
+
let value = format!(
|
|
545
|
+
"{}",
|
|
546
|
+
$crate::internal::round_to_significant_digits(self.to(base_unit), 3)
|
|
547
|
+
);
|
|
548
|
+
let name = base_unit.name().to_string();
|
|
549
|
+
match groups.iter_mut().find(|(existing, _)| *existing == value) {
|
|
550
|
+
Some((_, units)) => units.push(name),
|
|
551
|
+
None => groups.push((value, vec![name])),
|
|
552
|
+
}
|
|
561
553
|
}
|
|
562
554
|
|
|
563
555
|
let mut parts = Vec::new();
|
|
@@ -599,30 +591,45 @@ macro_rules! impl_quantity {
|
|
|
599
591
|
where
|
|
600
592
|
$type: PhysicsQuantity,
|
|
601
593
|
{
|
|
594
|
+
/// Equal up to `RELATIVE_EQUALITY_TOLERANCE` (1e-9) of the larger magnitude.
|
|
595
|
+
/// Symmetric; an infinity equals only itself and NaN equals nothing.
|
|
602
596
|
fn eq(&self, other: &Self) -> bool {
|
|
603
597
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
604
598
|
{
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
),
|
|
599
|
+
$crate::internal::quantity_parts_approx_eq(
|
|
600
|
+
self.multiplier,
|
|
601
|
+
self.power,
|
|
602
|
+
other.multiplier,
|
|
603
|
+
other.power,
|
|
611
604
|
)
|
|
612
605
|
}
|
|
613
606
|
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
|
|
614
607
|
{
|
|
615
|
-
|
|
608
|
+
$crate::internal::float_approx_eq(self.as_f64(), other.as_f64())
|
|
616
609
|
}
|
|
617
610
|
}
|
|
618
611
|
}
|
|
619
612
|
|
|
613
|
+
/// Exact ordering of the stored values (unlike the tolerance-based `==`), so sorting
|
|
614
|
+
/// stays a strict weak ordering.
|
|
620
615
|
impl PartialOrd for $type
|
|
621
616
|
where
|
|
622
617
|
$type: PhysicsQuantity,
|
|
623
618
|
{
|
|
624
619
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
625
|
-
|
|
620
|
+
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
621
|
+
{
|
|
622
|
+
$crate::internal::compare_quantity_parts(
|
|
623
|
+
self.multiplier,
|
|
624
|
+
self.power,
|
|
625
|
+
other.multiplier,
|
|
626
|
+
other.power,
|
|
627
|
+
)
|
|
628
|
+
}
|
|
629
|
+
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
|
|
630
|
+
{
|
|
631
|
+
self.as_f64().partial_cmp(&other.as_f64())
|
|
632
|
+
}
|
|
626
633
|
}
|
|
627
634
|
}
|
|
628
635
|
|
|
@@ -646,11 +653,16 @@ macro_rules! impl_quantity {
|
|
|
646
653
|
fn add(self, other: Self) -> Self {
|
|
647
654
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
648
655
|
{
|
|
656
|
+
// Align to the larger power: its multiplier stays exact and the smaller
|
|
657
|
+
// operand can only underflow, never overflow.
|
|
649
658
|
let common_power = self.power.max(other.power);
|
|
650
|
-
let multiplier =
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
659
|
+
let multiplier = $crate::internal::mul_pow10(
|
|
660
|
+
self.multiplier,
|
|
661
|
+
$crate::internal::saturating_power_sub(self.power, common_power),
|
|
662
|
+
) + $crate::internal::mul_pow10(
|
|
663
|
+
other.multiplier,
|
|
664
|
+
$crate::internal::saturating_power_sub(other.power, common_power),
|
|
665
|
+
);
|
|
654
666
|
Self::from_parts(multiplier, common_power)
|
|
655
667
|
}
|
|
656
668
|
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
|
|
@@ -666,20 +678,8 @@ macro_rules! impl_quantity {
|
|
|
666
678
|
fn sub(self, other: Self) -> Self {
|
|
667
679
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
668
680
|
{
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
&& self.multiplier.is_sign_positive() == other.multiplier.is_sign_positive()
|
|
672
|
-
{
|
|
673
|
-
return Self::zero();
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
let common_power = $crate::internal::average_power(self.power, other.power);
|
|
677
|
-
let multiplier = self.multiplier
|
|
678
|
-
* $crate::internal::pow10_delta(self.power, common_power)
|
|
679
|
-
- other.multiplier
|
|
680
|
-
* $crate::internal::pow10_delta(other.power, common_power);
|
|
681
|
-
|
|
682
|
-
Self::from_parts(multiplier, common_power)
|
|
681
|
+
// Negation is exact, so this rounds exactly like a direct subtraction.
|
|
682
|
+
self + (-other)
|
|
683
683
|
}
|
|
684
684
|
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
|
|
685
685
|
{
|
|
@@ -762,45 +762,13 @@ macro_rules! impl_quantity {
|
|
|
762
762
|
|
|
763
763
|
impl AddAssign for $type {
|
|
764
764
|
fn add_assign(&mut self, other: Self) {
|
|
765
|
-
|
|
766
|
-
{
|
|
767
|
-
let common_power = $crate::internal::average_power(self.power, other.power);
|
|
768
|
-
let multiplier = self.multiplier
|
|
769
|
-
* $crate::internal::pow10_delta(self.power, common_power)
|
|
770
|
-
+ other.multiplier
|
|
771
|
-
* $crate::internal::pow10_delta(other.power, common_power);
|
|
772
|
-
*self = Self::from_parts(multiplier, common_power);
|
|
773
|
-
}
|
|
774
|
-
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
|
|
775
|
-
{
|
|
776
|
-
*self = Self::from_base_value(self.as_f64() + other.as_f64());
|
|
777
|
-
}
|
|
765
|
+
*self = *self + other;
|
|
778
766
|
}
|
|
779
767
|
}
|
|
780
768
|
|
|
781
769
|
impl SubAssign for $type {
|
|
782
770
|
fn sub_assign(&mut self, other: Self) {
|
|
783
|
-
|
|
784
|
-
{
|
|
785
|
-
if self.multiplier.is_infinite()
|
|
786
|
-
&& other.multiplier.is_infinite()
|
|
787
|
-
&& self.multiplier.is_sign_positive() == other.multiplier.is_sign_positive()
|
|
788
|
-
{
|
|
789
|
-
*self = Self::zero();
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
let common_power = $crate::internal::average_power(self.power, other.power);
|
|
794
|
-
let multiplier = self.multiplier
|
|
795
|
-
* $crate::internal::pow10_delta(self.power, common_power)
|
|
796
|
-
- other.multiplier
|
|
797
|
-
* $crate::internal::pow10_delta(other.power, common_power);
|
|
798
|
-
*self = Self::from_parts(multiplier, common_power);
|
|
799
|
-
}
|
|
800
|
-
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
|
|
801
|
-
{
|
|
802
|
-
*self = Self::from_base_value(self.as_f64() - other.as_f64());
|
|
803
|
-
}
|
|
771
|
+
*self = *self - other;
|
|
804
772
|
}
|
|
805
773
|
}
|
|
806
774
|
|
|
@@ -966,12 +934,15 @@ macro_rules! impl_quantity {
|
|
|
966
934
|
}
|
|
967
935
|
|
|
968
936
|
fn close_abs(&self, other: PyRef<Self>, threshold: Self) -> bool {
|
|
969
|
-
|
|
937
|
+
self.is_close(&other, &threshold)
|
|
970
938
|
}
|
|
971
939
|
|
|
972
940
|
fn close_rel(&self, other: PyRef<Self>, threshold: f64) -> bool {
|
|
973
|
-
|
|
974
|
-
|
|
941
|
+
if self.same_value(&other) {
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
944
|
+
let mean = (*self + *other) / 2.;
|
|
945
|
+
(*self - *other).abs() <= mean.abs() * threshold
|
|
975
946
|
}
|
|
976
947
|
|
|
977
948
|
fn __richcmp__(&self, other: PyRef<Self>, op: CompareOp) -> bool {
|
|
@@ -1021,74 +992,36 @@ macro_rules! impl_quantity {
|
|
|
1021
992
|
self.kind_id
|
|
1022
993
|
}
|
|
1023
994
|
|
|
995
|
+
/// The unit in which this value's magnitude is closest to one, i.e. the one that
|
|
996
|
+
/// minimizes `|log10(|self.to(unit)|)|`. Zero and non-finite values have no
|
|
997
|
+
/// meaningful magnitude; for them the unit whose own size is closest to the base
|
|
998
|
+
/// unit wins. `None` only if `choices` is empty.
|
|
1024
999
|
pub fn optimal_unit<I>(&self, choices: I) -> Option<$unit>
|
|
1025
1000
|
where
|
|
1026
1001
|
I: IntoIterator<Item = $unit>,
|
|
1027
1002
|
{
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
if difference < min_value {
|
|
1036
|
-
min_value = difference;
|
|
1037
|
-
best_unit = Some(unit);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
best_unit
|
|
1041
|
-
}
|
|
1042
|
-
#[cfg(all(
|
|
1043
|
-
feature = "no_std",
|
|
1044
|
-
any(feature = "storage-f64", feature = "storage-f32")
|
|
1045
|
-
))]
|
|
1046
|
-
{
|
|
1047
|
-
let mut min_value = f64::INFINITY;
|
|
1048
|
-
let mut best_unit = None;
|
|
1049
|
-
for unit in choices {
|
|
1050
|
-
let (unit_multiplier, unit_power) = unit.base_per_x();
|
|
1051
|
-
let unit_scale =
|
|
1052
|
-
$crate::internal::quantity_parts_to_value(unit_multiplier, unit_power);
|
|
1053
|
-
let converted = (self.as_f64() / unit_scale).abs();
|
|
1054
|
-
let difference = if converted == 0.0 {
|
|
1055
|
-
f64::INFINITY
|
|
1056
|
-
} else if converted < 1.0 {
|
|
1057
|
-
1.0 / converted
|
|
1058
|
-
} else {
|
|
1059
|
-
converted
|
|
1060
|
-
};
|
|
1061
|
-
if difference < min_value {
|
|
1062
|
-
min_value = difference;
|
|
1063
|
-
best_unit = Some(unit);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
best_unit
|
|
1067
|
-
}
|
|
1068
|
-
#[cfg(all(
|
|
1069
|
-
not(feature = "no_std"),
|
|
1070
|
-
any(feature = "storage-f64", feature = "storage-f32")
|
|
1071
|
-
))]
|
|
1072
|
-
{
|
|
1073
|
-
let mut min_value = f64::INFINITY;
|
|
1074
|
-
let mut best_unit = None;
|
|
1075
|
-
for unit in choices {
|
|
1003
|
+
let value = self.as_f64();
|
|
1004
|
+
let has_magnitude = value.is_finite() && value != 0.0;
|
|
1005
|
+
let mut best: Option<($unit, f64)> = None;
|
|
1006
|
+
for unit in choices {
|
|
1007
|
+
let magnitude = if has_magnitude {
|
|
1008
|
+
self.to(unit).abs()
|
|
1009
|
+
} else {
|
|
1076
1010
|
let (unit_multiplier, unit_power) = unit.base_per_x();
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
}
|
|
1011
|
+
$crate::internal::quantity_parts_to_value(unit_multiplier, unit_power).abs()
|
|
1012
|
+
};
|
|
1013
|
+
// max(m, 1/m) orders like |log10(m)| without needing a logarithm.
|
|
1014
|
+
let difference = if magnitude.is_finite() && magnitude > 0.0 {
|
|
1015
|
+
magnitude.max(1.0 / magnitude)
|
|
1016
|
+
} else {
|
|
1017
|
+
f64::INFINITY
|
|
1018
|
+
};
|
|
1019
|
+
match best {
|
|
1020
|
+
Some((_, best_difference)) if difference >= best_difference => {}
|
|
1021
|
+
_ => best = Some((unit, difference)),
|
|
1089
1022
|
}
|
|
1090
|
-
best_unit
|
|
1091
1023
|
}
|
|
1024
|
+
best.map(|(unit, _)| unit)
|
|
1092
1025
|
}
|
|
1093
1026
|
}
|
|
1094
1027
|
};
|
|
@@ -1158,8 +1091,10 @@ macro_rules! impl_div_with_self_to_f64 {
|
|
|
1158
1091
|
fn div(self, rhs: Self) -> Self::Output {
|
|
1159
1092
|
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
|
|
1160
1093
|
{
|
|
1161
|
-
(
|
|
1162
|
-
|
|
1094
|
+
$crate::internal::quantity_parts_to_value(
|
|
1095
|
+
self.multiplier / rhs.multiplier,
|
|
1096
|
+
$crate::internal::saturating_power_sub(self.power, rhs.power),
|
|
1097
|
+
)
|
|
1163
1098
|
}
|
|
1164
1099
|
#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
|
|
1165
1100
|
{
|
|
@@ -1238,7 +1173,13 @@ macro_rules! impl_mul {
|
|
|
1238
1173
|
impl MulArray1<$rhs> for Array1<$lhs> {
|
|
1239
1174
|
type Output = Array1<$result>;
|
|
1240
1175
|
|
|
1176
|
+
#[track_caller]
|
|
1241
1177
|
fn mul_array1(self, rhs: Array1<$rhs>) -> Array1<$result> {
|
|
1178
|
+
assert_eq!(
|
|
1179
|
+
self.len(),
|
|
1180
|
+
rhs.len(),
|
|
1181
|
+
"mul_array1: operands must have the same length"
|
|
1182
|
+
);
|
|
1242
1183
|
self.into_iter()
|
|
1243
1184
|
.zip(rhs.into_iter())
|
|
1244
1185
|
.map(|(force, distance)| force * distance)
|
|
@@ -1296,7 +1237,13 @@ macro_rules! impl_mul {
|
|
|
1296
1237
|
impl MulArray1<$lhs> for Array1<$rhs> {
|
|
1297
1238
|
type Output = Array1<$result>;
|
|
1298
1239
|
|
|
1240
|
+
#[track_caller]
|
|
1299
1241
|
fn mul_array1(self, rhs: Array1<$lhs>) -> Array1<$result> {
|
|
1242
|
+
assert_eq!(
|
|
1243
|
+
self.len(),
|
|
1244
|
+
rhs.len(),
|
|
1245
|
+
"mul_array1: operands must have the same length"
|
|
1246
|
+
);
|
|
1300
1247
|
self.into_iter()
|
|
1301
1248
|
.zip(rhs.into_iter())
|
|
1302
1249
|
.map(|(force, distance)| force * distance)
|
|
@@ -1377,7 +1324,13 @@ macro_rules! impl_mul_with_self {
|
|
|
1377
1324
|
impl MulArray1<$lhs> for Array1<$lhs> {
|
|
1378
1325
|
type Output = Array1<$result>;
|
|
1379
1326
|
|
|
1327
|
+
#[track_caller]
|
|
1380
1328
|
fn mul_array1(self, rhs: Array1<$lhs>) -> Array1<$result> {
|
|
1329
|
+
assert_eq!(
|
|
1330
|
+
self.len(),
|
|
1331
|
+
rhs.len(),
|
|
1332
|
+
"mul_array1: operands must have the same length"
|
|
1333
|
+
);
|
|
1381
1334
|
self.into_iter()
|
|
1382
1335
|
.zip(rhs.into_iter())
|
|
1383
1336
|
.map(|(force, distance)| force * distance)
|
|
@@ -1485,7 +1438,13 @@ macro_rules! impl_div {
|
|
|
1485
1438
|
impl DivArray1<$rhs> for Array1<$lhs> {
|
|
1486
1439
|
type Output = Array1<$result>;
|
|
1487
1440
|
|
|
1441
|
+
#[track_caller]
|
|
1488
1442
|
fn div_array1(self, rhs: Array1<$rhs>) -> Array1<$result> {
|
|
1443
|
+
assert_eq!(
|
|
1444
|
+
self.len(),
|
|
1445
|
+
rhs.len(),
|
|
1446
|
+
"div_array1: operands must have the same length"
|
|
1447
|
+
);
|
|
1489
1448
|
self.into_iter()
|
|
1490
1449
|
.zip(rhs.into_iter())
|
|
1491
1450
|
.map(|(force, distance)| force / distance)
|