use crate::error::{Error, Result};
#[cfg(feature = "double-precision")]
const B2_HUGE_FACTOR: f32 = 1.0e9;
#[cfg(not(feature = "double-precision"))]
const B2_HUGE_FACTOR: f32 = 1.0e5;
const B2_LINEAR_SLOP_FACTOR: f32 = 0.005;
const B2_DEFAULT_MAX_LINEAR_SPEED_FACTOR: f32 = 400.0;
const B2_AIR_DENSITY_FACTOR: f32 = 1.225;
#[inline]
pub(crate) fn is_safe_length_units_per_meter(length_units_per_meter: f32) -> bool {
if !length_units_per_meter.is_finite() || length_units_per_meter <= 0.0 {
return false;
}
let huge = B2_HUGE_FACTOR * length_units_per_meter;
let linear_slop = B2_LINEAR_SLOP_FACTOR * length_units_per_meter;
let linear_slop_squared = linear_slop * linear_slop;
let maximum_linear_speed = B2_DEFAULT_MAX_LINEAR_SPEED_FACTOR * length_units_per_meter;
let maximum_linear_speed_squared = maximum_linear_speed * maximum_linear_speed;
let volume_units = length_units_per_meter * length_units_per_meter * length_units_per_meter;
let air_density = B2_AIR_DENSITY_FACTOR / volume_units;
huge.is_finite()
&& huge > 1.0
&& linear_slop.is_finite()
&& linear_slop > 0.0
&& linear_slop_squared.is_finite()
&& linear_slop_squared > 0.0
&& maximum_linear_speed.is_finite()
&& maximum_linear_speed > 0.0
&& maximum_linear_speed_squared.is_finite()
&& maximum_linear_speed_squared > 0.0
&& volume_units.is_finite()
&& volume_units > 0.0
&& air_density.is_finite()
&& air_density > 0.0
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct LengthScale {
bits: u32,
}
impl LengthScale {
#[inline]
pub(crate) fn try_new(length_units_per_meter: f32) -> Option<Self> {
if is_safe_length_units_per_meter(length_units_per_meter) {
Some(Self {
bits: length_units_per_meter.to_bits(),
})
} else {
None
}
}
#[inline]
pub(crate) const fn units_per_meter(self) -> f32 {
f32::from_bits(self.bits)
}
#[inline]
pub(crate) const fn bits(self) -> u32 {
self.bits
}
#[inline]
pub(crate) fn check_definition(self, operation: &'static str, definition: Self) -> Result<()> {
if self == definition {
Ok(())
} else {
Err(Error::LengthScaleMismatch {
operation,
expected: self.bits(),
actual: definition.bits(),
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adjacent_float_scales_are_not_interchangeable() {
let expected = LengthScale::try_new(1.0).unwrap();
let adjacent = LengthScale::try_new(f32::from_bits(1.0_f32.to_bits() + 1)).unwrap();
assert_eq!(
expected.check_definition("test", adjacent),
Err(Error::LengthScaleMismatch {
operation: "test",
expected: expected.bits(),
actual: adjacent.bits(),
})
);
}
#[test]
fn invalid_global_scales_cannot_be_represented() {
for value in [0.0, -1.0, f32::INFINITY, f32::NEG_INFINITY, f32::NAN] {
assert_eq!(LengthScale::try_new(value), None);
}
}
#[test]
fn scales_that_break_native_derived_calculations_cannot_be_represented() {
let below_ray_limit = 0.5 / B2_HUGE_FACTOR;
let above_volume_limit = 1.0e13_f32;
let above_squared_slop_limit = 1.0e22_f32;
for value in [
f32::from_bits(1),
below_ray_limit,
above_volume_limit,
above_squared_slop_limit,
f32::MAX,
] {
assert_eq!(LengthScale::try_new(value), None);
}
assert!(LengthScale::try_new(1.0).is_some());
}
}