use std::cmp::min;
use crate::ser::ToSerde;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Position(f64);
impl Position {
#[cfg(test)]
pub fn from_int(position: usize) -> Self {
Self(position as f64)
}
pub fn new(value: f64) -> Self {
Self(value)
}
pub fn between(first: Option<Position>, second: Option<Position>) -> Option<Self> {
let (first, second) = match (first, second) {
(Some(first), Some(second)) => (first.0, second.0),
(Some(first), None) => (first.0, first.0 + 2f64),
(None, Some(second)) => (second.0 - 2f64, second.0),
(None, None) => return Some(Position(0f64)),
};
between(first, second).map(Self)
}
#[inline]
pub fn get(&self) -> f64 {
self.0
}
}
impl ToSerde for Position {
type Output = f64;
fn to_serde(&self) -> Self::Output {
self.get()
}
}
fn ensure_has_integer_part(value: f64) -> (f64, i32) {
debug_assert_ne!(value, 0.0);
let exponent = -min(value.log10().floor() as i32, 0);
(value * 10f64.powi(exponent), exponent)
}
fn between(first: f64, second: f64) -> Option<f64> {
fn approximate_between(first: f64, second: f64) -> Option<f64> {
let (mut first, mut second, mut exponent) = if second == 0f64 || second > 1f64 {
(first, second, 0i32)
} else {
let (second, exponent) = ensure_has_integer_part(second);
(first * 10f64.powi(exponent), second, exponent)
};
let floor = second.floor();
let value = if first < floor && floor < second {
floor
} else {
'outer: loop {
let value = ((second + first) / 2f64).floor();
if first < value && value < second {
break 'outer value
}
let new_first = first * 10f64;
let new_second = second * 10f64;
if new_first == first && new_second == second {
return None
}
exponent = exponent.checked_add(1)?;
first = new_first;
second = new_second;
if second.is_infinite() {
return None
}
}
};
let value = if exponent != 0 {
value / 10f64.powi(exponent)
} else {
value
};
Some(value)
}
let (first, second) = if first == second {
return None
} else if first < second {
(first, second)
} else {
(second, first)
};
let result = approximate_between(first, second)?;
if first < result && result < second {
Some(result)
} else {
None
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn integer_part_ensurance() {
assert_eq!(ensure_has_integer_part(0.000000001).0, 1.0);
assert_eq!(ensure_has_integer_part(0.1).0, 1.0);
assert_eq!(ensure_has_integer_part(1.0).0, 1.0);
assert_eq!(ensure_has_integer_part(1.2).0, 1.2);
assert_eq!(ensure_has_integer_part(1.8).0, 1.8);
assert_eq!(ensure_has_integer_part(5.0).0, 5.0);
assert_eq!(ensure_has_integer_part(9.9).0, 9.9);
assert_eq!(ensure_has_integer_part(10.0).0, 10.0);
assert_eq!(ensure_has_integer_part(15.0).0, 15.0);
assert_eq!(ensure_has_integer_part(227.0).0, 227.0);
}
#[test]
fn approximation_between() {
assert_eq!(between(-1.0, 0.0).unwrap(), -0.5);
assert_eq!(between(0.001, 0.01).unwrap(), 0.005);
assert_eq!(between(0.0, 10.0).unwrap(), 5.0);
assert_eq!(between(10.0, 0.0).unwrap(), 5.0);
assert_eq!(between(0.9999, 1.1).unwrap(), 1.0);
assert_eq!(between(1.0, 3.0).unwrap(), 2.0);
assert_eq!(between(1.0, 1.00002).unwrap(), 1.00001);
assert_eq!(between(1.5, 2.5).unwrap(), 2.0);
assert_eq!(between(1.1, 2.9).unwrap(), 2.0);
assert_eq!(between(1.1, 2.8).unwrap(), 2.0);
assert_eq!(between(1.0, 10.0).unwrap(), 5.0);
assert_eq!(between(2.0, 10.0).unwrap(), 6.0);
assert_eq!(between(3.0, 10.0).unwrap(), 6.0);
assert_eq!(between(200.0, 200.3).unwrap(), 200.1);
assert_eq!(between(200.0, 201.0).unwrap(), 200.5);
assert_eq!(between(200.0, 202.0).unwrap(), 201.0);
assert_eq!(between(0.0, 0.0), None);
assert_eq!(between(1.0, 1.0), None);
assert_eq!(between(200.0, 200.0), None);
}
#[test]
fn position_creation() {
let first = Position::from_int(1);
let second = Position::from_int(2);
let between = Position::between(Some(first), Some(second)).unwrap();
assert_eq!(between.get(), 1.5);
let between = Position::between(None, Some(second)).unwrap();
assert_eq!(between.get(), 1.0);
let between = Position::between(Some(first), None).unwrap();
assert_eq!(between.get(), 2.0);
let between = Position::between(None, None).unwrap();
assert_eq!(between.get(), 0.0);
}
}