use super::{
Distance, DistanceUnit, Energy, EnergyRate, EnergyRateUnit, EnergyUnit, Speed, SpeedUnit, Time,
TimeUnit, UnitError,
};
pub const BASE_DISTANCE_UNIT: DistanceUnit = DistanceUnit::Meters;
pub const BASE_TIME_UNIT: TimeUnit = TimeUnit::Seconds;
pub const BASE_SPEED_UNIT: SpeedUnit = SpeedUnit::MetersPerSecond;
pub fn create_time(
speed: Speed,
speed_unit: SpeedUnit,
distance: Distance,
distance_unit: DistanceUnit,
time_unit: TimeUnit,
) -> Result<Time, UnitError> {
let d = distance_unit.convert(distance, BASE_DISTANCE_UNIT);
let s = speed_unit.convert(speed, BASE_SPEED_UNIT);
if s <= Speed::ZERO || d <= Distance::ZERO {
Err(UnitError::TimeFromSpeedAndDistanceError(
speed,
speed_unit,
distance,
distance_unit,
))
} else {
let time = (d, s).into();
let result = BASE_TIME_UNIT.convert(time, time_unit);
Ok(result)
}
}
pub fn create_speed(
time: Time,
time_unit: TimeUnit,
distance: Distance,
distance_unit: DistanceUnit,
speed_unit: SpeedUnit,
) -> Result<Speed, UnitError> {
let d = distance_unit.convert(distance, BASE_DISTANCE_UNIT);
let t = time_unit.convert(time, BASE_TIME_UNIT);
if t <= Time::ZERO {
Err(UnitError::SpeedFromTimeAndDistanceError(time, distance))
} else {
let speed = (d, t).into();
let result = BASE_SPEED_UNIT.convert(speed, speed_unit);
Ok(result)
}
}
pub fn create_energy(
energy_rate: EnergyRate,
energy_rate_unit: EnergyRateUnit,
distance: Distance,
distance_unit: DistanceUnit,
) -> Result<(Energy, EnergyUnit), UnitError> {
let rate_distance_unit = energy_rate_unit.associated_distance_unit();
let energy_unit = energy_rate_unit.associated_energy_unit();
let calc_distance = distance_unit.convert(distance, rate_distance_unit);
let energy = (energy_rate, calc_distance).into();
Ok((energy, energy_unit))
}
#[cfg(test)]
mod test {
use super::*;
use crate::model::unit::as_f64::AsF64;
fn approx_eq_time(a: Time, b: Time, error: f64) {
let result = match (a, b) {
(c, d) if c < d => (d - c).as_f64() < error,
(c, d) if c > d => (c - d).as_f64() < error,
(_, _) => true,
};
assert!(
result,
"{} ~= {} is not true within an error of {}",
a, b, error
)
}
fn approx_eq_speed(a: Speed, b: Speed, error: f64) {
let result = match (a, b) {
(c, d) if c < d => (d - c).as_f64() < error,
(c, d) if c > d => (c - d).as_f64() < error,
(_, _) => true,
};
assert!(
result,
"{} ~= {} is not true within an error of {}",
a, b, error
)
}
fn approx_eq_energy(a: Energy, b: Energy, error: f64) {
let result = match (a, b) {
(c, d) if c < d => (d - c).as_f64() < error,
(c, d) if c > d => (c - d).as_f64() < error,
(_, _) => true,
};
assert!(
result,
"{} ~= {} is not true within an error of {}",
a, b, error
)
}
#[test]
fn test_speed_calculate_fails() {
let failure = create_speed(
Time::ZERO,
TimeUnit::Seconds,
Distance::ONE,
DistanceUnit::Meters,
SpeedUnit::MetersPerSecond,
);
assert!(failure.is_err());
}
#[test]
fn test_speed_calculate_idempotent() {
let one_mps = create_speed(
Time::ONE,
TimeUnit::Seconds,
Distance::ONE,
DistanceUnit::Meters,
SpeedUnit::MetersPerSecond,
)
.unwrap();
assert_eq!(Speed::ONE, one_mps);
}
#[test]
fn test_speed_calculate_imperial_to_si() {
let speed_kph = create_speed(
Time::ONE,
TimeUnit::Hours,
Distance::ONE,
DistanceUnit::Miles,
SpeedUnit::KilometersPerHour,
)
.unwrap();
approx_eq_speed(Speed::new(1.60934), speed_kph, 0.001);
}
#[test]
fn test_speed_calculate_kph_to_base() {
let speed_kph = create_speed(
Time::ONE,
TimeUnit::Hours,
Distance::ONE,
DistanceUnit::Kilometers,
BASE_SPEED_UNIT,
)
.unwrap();
let expected = SpeedUnit::KilometersPerHour.convert(Speed::ONE, BASE_SPEED_UNIT);
approx_eq_speed(speed_kph, expected, 0.001);
}
#[test]
fn test_speed_calculate_base_to_kph() {
let speed_kph = create_speed(
Time::ONE,
BASE_TIME_UNIT,
Distance::ONE,
BASE_DISTANCE_UNIT,
SpeedUnit::KilometersPerHour,
)
.unwrap();
let expected = SpeedUnit::MetersPerSecond.convert(Speed::ONE, SpeedUnit::KilometersPerHour);
approx_eq_speed(speed_kph, expected, 0.001);
}
#[test]
fn test_speed_calculate_mph_to_base() {
let speed_kph = create_speed(
Time::ONE,
TimeUnit::Hours,
Distance::ONE,
DistanceUnit::Miles,
BASE_SPEED_UNIT,
)
.unwrap();
let expected = SpeedUnit::MilesPerHour.convert(Speed::ONE, BASE_SPEED_UNIT);
approx_eq_speed(speed_kph, expected, 0.001);
}
#[test]
fn test_speed_calculate_base_to_mph() {
let speed_kph = create_speed(
Time::ONE,
BASE_TIME_UNIT,
Distance::ONE,
BASE_DISTANCE_UNIT,
SpeedUnit::MilesPerHour,
)
.unwrap();
let expected = SpeedUnit::MetersPerSecond.convert(Speed::ONE, SpeedUnit::MilesPerHour);
approx_eq_speed(speed_kph, expected, 0.001);
}
#[test]
fn test_time_calculate_fails() {
let failure = create_time(
Speed::ZERO,
SpeedUnit::KilometersPerHour,
Distance::ONE,
DistanceUnit::Meters,
BASE_TIME_UNIT,
);
assert!(failure.is_err());
}
#[test]
fn test_time_calculate_idempotent() {
let one_sec = create_time(
Speed::ONE,
SpeedUnit::MetersPerSecond,
Distance::ONE,
DistanceUnit::Meters,
BASE_TIME_UNIT,
)
.unwrap();
assert_eq!(Time::ONE, one_sec);
}
#[test]
fn test_time_calculate_kph_to_base() {
let time = create_time(
Speed::ONE,
SpeedUnit::KilometersPerHour,
Distance::ONE,
DistanceUnit::Kilometers,
BASE_TIME_UNIT,
)
.unwrap();
let expected = TimeUnit::Hours.convert(Time::ONE, BASE_TIME_UNIT);
approx_eq_time(time, expected, 0.001);
}
#[test]
fn test_time_calculate_base_to_kph() {
let time = create_time(
Speed::ONE,
BASE_SPEED_UNIT,
Distance::ONE,
BASE_DISTANCE_UNIT,
TimeUnit::Hours,
)
.unwrap();
let expected = BASE_TIME_UNIT.convert(Time::ONE, TimeUnit::Hours);
approx_eq_time(time, expected, 0.001);
}
#[test]
fn test_time_calculate_mph_to_base() {
let time = create_time(
Speed::ONE,
SpeedUnit::MilesPerHour,
Distance::ONE,
DistanceUnit::Miles,
BASE_TIME_UNIT,
)
.unwrap();
let expected = TimeUnit::Hours.convert(Time::ONE, BASE_TIME_UNIT);
approx_eq_time(time, expected, 0.01);
}
#[test]
fn test_time_calculate_base_to_mph() {
let time = create_time(
Speed::ONE,
BASE_SPEED_UNIT,
Distance::ONE,
BASE_DISTANCE_UNIT,
TimeUnit::Hours,
)
.unwrap();
let expected = BASE_TIME_UNIT.convert(Time::ONE, TimeUnit::Hours);
approx_eq_time(time, expected, 0.001);
}
#[test]
fn test_energy_ggpm_meters() {
let ten_mpg_rate = 1.0 / 10.0;
let (energy, energy_unit) = create_energy(
EnergyRate::new(ten_mpg_rate),
EnergyRateUnit::GallonsGasolinePerMile,
Distance::new(1609.0),
DistanceUnit::Meters,
)
.unwrap();
approx_eq_energy(energy, Energy::new(ten_mpg_rate), 0.00001);
assert_eq!(energy_unit, EnergyUnit::GallonsGasoline);
}
#[test]
fn test_energy_ggpm_miles() {
let ten_mpg_rate = 1.0 / 10.0;
let (energy, energy_unit) = create_energy(
EnergyRate::new(ten_mpg_rate),
EnergyRateUnit::GallonsGasolinePerMile,
Distance::new(1.0),
DistanceUnit::Miles,
)
.unwrap();
approx_eq_energy(energy, Energy::new(ten_mpg_rate), 0.00001);
assert_eq!(energy_unit, EnergyUnit::GallonsGasoline);
}
}