From 36bd412578cb930f67d484f4b24a462c42e257be Mon Sep 17 00:00:00 2001 From: Chris de Claverie Date: Tue, 4 Jan 2022 12:39:44 +0100 Subject: [PATCH 01/17] Redefine duration constants --- src/duration.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 23f2dff1..3c41d0b0 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -14,11 +14,17 @@ use std::fmt; use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use std::str::FromStr; -const DAYS_PER_CENTURY_U: u128 = 36_525; -const SECONDS_PER_MINUTE_U: u128 = 60; -const SECONDS_PER_HOUR_U: u128 = 3_600; -const SECONDS_PER_DAY_U: u128 = 86_400; -const ONE: u128 = 1_u128; + + +const SECONDS_PER_MINUTE_U: u64 = 60; +const MINUTES_PER_HOUR_U: u64 = 60; +const HOURS_PER_DAY_U: u64 = 24; +const SECONDS_PER_HOUR_U: u64 = SECONDS_PER_MINUTE_U * MINUTES_PER_HOUR_U; +const SECONDS_PER_DAY_U: u64 = SECONDS_PER_HOUR_U * HOURS_PER_DAY_U; +const DAYS_PER_CENTURY_U: u64 = 36_525; +const NS_PER_DAY_U: u64 = 1e9 as u64 * SECONDS_PER_DAY_U; +const NS_PER_CENTURY_U: u64 = DAYS_PER_CENTURY_U * NS_PER_DAY_U; +const ONE: u64 = 1_u64; /// Defines generally usable durations for high precision math with Epoch (all data is stored in seconds) #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] From b66ebab2ff7ceee59d9f049bd1f5ac8b6451293b Mon Sep 17 00:00:00 2001 From: Chris de Claverie Date: Tue, 4 Jan 2022 12:44:41 +0100 Subject: [PATCH 02/17] Switch from twofloat to integer-based Duration --- src/duration.rs | 85 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 3c41d0b0..9a05fea2 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -1,12 +1,11 @@ extern crate regex; extern crate serde; extern crate serde_derive; -extern crate twofloat; +extern crate divrem; -// use self::qd::Quad; use self::regex::Regex; use self::serde::{de, Deserialize, Deserializer}; -use self::twofloat::TwoFloat; +use self::divrem::{DivEuclid, DivRemEuclid}; use crate::{Errors, SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE}; use std::cmp::Ordering; use std::convert::TryFrom; @@ -26,9 +25,13 @@ const NS_PER_DAY_U: u64 = 1e9 as u64 * SECONDS_PER_DAY_U; const NS_PER_CENTURY_U: u64 = DAYS_PER_CENTURY_U * NS_PER_DAY_U; const ONE: u64 = 1_u64; -/// Defines generally usable durations for high precision math with Epoch (all data is stored in seconds) +/// Defines generally usable durations for high precision math with Epoch #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] -pub struct Duration(TwoFloat); +pub struct Duration { + ns : u64, // 1 century is about 3.1e18 ns, and max value of u64 is about 1e19.26 + centuries : i64 // +- 9.22e18 centuries is the possible range for a Duration + // Reducing the range could be a good tradeoff for a lowerm memory footprint +} impl<'de> Deserialize<'de> for Duration { fn deserialize(deserializer: D) -> Result @@ -152,27 +155,24 @@ impl Duration { /// Returns this duration in f64 in the provided unit. /// For high fidelity comparisons, it is recommended to keep using the Duration structure. pub fn in_unit_f64(&self, unit: TimeUnit) -> f64 { - f64::from(self.in_unit(unit)) + self.in_unit(unit) } /// Returns this duration in seconds f64. /// For high fidelity comparisons, it is recommended to keep using the Duration structure. pub fn in_seconds(&self) -> f64 { - f64::from(self.0) + (self.ns as f64 / 1e9) + (self.centuries * DAYS_PER_CENTURY_U as i64 * SECONDS_PER_DAY_U as i64) as f64 } /// Returns the value of this duration in the requested unit. - pub fn in_unit(&self, unit: TimeUnit) -> TwoFloat { - self.0 * unit.from_seconds() + pub fn in_unit(&self, unit: TimeUnit) -> f64 { + self.in_seconds() * unit.from_seconds() } /// Returns the absolute value of this duration + #[must_use] pub fn abs(&self) -> Self { - if self.0 < TwoFloat::from(0) { - Self { 0: -self.0 } - } else { - *self - } + if self.centuries < 0 { -*self } else { *self } } /// Builds a new duration from the hi and lo two-float values @@ -358,7 +358,7 @@ impl fmt::Display for Duration { impl fmt::LowerExp for Duration { // Prints the duration with appropriate units fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let seconds_f64 = f64::from(self.0); + let seconds_f64 = self.in_seconds(); let seconds_f64_abs = seconds_f64.abs(); if seconds_f64_abs < 1e-5 { fmt::Display::fmt(&(seconds_f64 * 1e9), f)?; @@ -386,7 +386,23 @@ impl Add for Duration { type Output = Duration; fn add(self, rhs: Self) -> Duration { - Self { 0: self.0 + rhs.0 } + + // Usage of u128 is to avoid possibility of overflow and type-system carry from u64 + // may switch approach when carrying_add is stabilized + let mut total_ns: u128 = self.ns as u128 + rhs.ns as u128; + let mut century_carry = 0; + if total_ns > NS_PER_CENTURY_U as u128 { + century_carry = total_ns / NS_PER_CENTURY_U as u128; + total_ns %= NS_PER_CENTURY_U as u128; + // total_ns is now guaranteed to be less than u64_max + } + + let total_centuries = + self.centuries + .saturating_add(rhs.centuries) + .saturating_add(century_carry as i64); + + Self { ns : total_ns as u64, centuries : total_centuries } } } @@ -400,7 +416,16 @@ impl Sub for Duration { type Output = Duration; fn sub(self, rhs: Self) -> Duration { - Self { 0: self.0 - rhs.0 } + let mut total_ns: i128 = self.ns as i128 - rhs.ns as i128; + let mut century_borrow = 0; + if total_ns < 0 { + century_borrow = (-total_ns / NS_PER_CENTURY_U as i128)+1; + total_ns += century_borrow * NS_PER_CENTURY_U as i128; + } + + + let total_centuries = self.centuries.saturating_sub(rhs.centuries).saturating_sub(century_borrow as i64); + Self { ns : total_ns as u64, centuries : total_centuries } } } @@ -469,7 +494,7 @@ impl Neg for Duration { type Output = Duration; fn neg(self) -> Self::Output { - Self { 0: -self.0 } + Self { ns: NS_PER_CENTURY_U - self.ns, centuries : -self.centuries-1 } } } @@ -598,26 +623,26 @@ impl Sub for TimeUnit { } impl TimeUnit { - pub fn in_seconds(self) -> TwoFloat { + pub fn in_seconds(self) -> f64 { match self { - TimeUnit::Century => TwoFloat::from(DAYS_PER_CENTURY_U * SECONDS_PER_DAY_U), - TimeUnit::Day => TwoFloat::from(SECONDS_PER_DAY_U), - TimeUnit::Hour => TwoFloat::from(SECONDS_PER_HOUR_U), - TimeUnit::Minute => TwoFloat::from(SECONDS_PER_MINUTE_U), - TimeUnit::Second => TwoFloat::from(ONE), - TimeUnit::Millisecond => TwoFloat::from(1e-3), - TimeUnit::Microsecond => TwoFloat::from(1e-6), - TimeUnit::Nanosecond => TwoFloat::from(1e-9), + TimeUnit::Century => DAYS_PER_CENTURY_U as f64 * SECONDS_PER_DAY_U as f64, + TimeUnit::Day => SECONDS_PER_DAY_U as f64, + TimeUnit::Hour => SECONDS_PER_HOUR_U as f64, + TimeUnit::Minute => SECONDS_PER_MINUTE_U as f64, + TimeUnit::Second => ONE as f64, + TimeUnit::Millisecond => 1e-3, + TimeUnit::Microsecond => 1e-6, + TimeUnit::Nanosecond => 1e-9, } } pub fn in_seconds_f64(self) -> f64 { - f64::from(self.in_seconds()) + self.in_seconds() } #[allow(clippy::wrong_self_convention)] - pub fn from_seconds(self) -> TwoFloat { - TwoFloat::from(1) / self.in_seconds() + pub fn from_seconds(self) -> f64 { + 1.0 / self.in_seconds() } } From 492f69c2c60566fb77bae0b44d9dc6f8b7734f91 Mon Sep 17 00:00:00 2001 From: Chris de Claverie Date: Tue, 4 Jan 2022 12:51:28 +0100 Subject: [PATCH 03/17] remove TryFrom from two floats and refactor Display for Duration --- src/duration.rs | 227 ++++++++++++++---------------------------------- src/epoch.rs | 16 ---- 2 files changed, 67 insertions(+), 176 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 9a05fea2..09f4b9bb 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -8,7 +8,6 @@ use self::serde::{de, Deserialize, Deserializer}; use self::divrem::{DivEuclid, DivRemEuclid}; use crate::{Errors, SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE}; use std::cmp::Ordering; -use std::convert::TryFrom; use std::fmt; use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use std::str::FromStr; @@ -175,183 +174,91 @@ impl Duration { if self.centuries < 0 { -*self } else { *self } } - /// Builds a new duration from the hi and lo two-float values - pub fn try_from_hi_lo(hi: f64, lo: f64) -> Result { - match TwoFloat::try_from((hi, lo)) { - Ok(t) => Ok(Self(t)), - Err(_) => Err(Errors::ConversionOverlapError(hi, lo)), - } - } -} -impl TryFrom<(f64, f64)> for Duration { - type Error = Errors; + pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64, u64, u64) { - fn try_from(value: (f64, f64)) -> Result { - Self::try_from_hi_lo(value.0, value.1) - } -} + let total_ns : i128 = self.centuries as i128 * NS_PER_CENTURY_U as i128 + self.ns as i128; + + let sign = total_ns.signum() as i8; + let mut ns_left = total_ns.abs() as u64; + + + let centuries = ns_left / NS_PER_CENTURY_U; + + + let years = ns_left / (1e9 as u64 * SECONDS_PER_DAY_U as u64 * 365); + ns_left %= 1e9 as u64 * SECONDS_PER_DAY_U as u64 * 365; + + + let days = ns_left / (1e9 as u64 * SECONDS_PER_DAY_U as u64); + ns_left %= 1e9 as u64 * SECONDS_PER_DAY_U as u64; + + let hours = ns_left / (1e9 as u64 * SECONDS_PER_HOUR_U as u64); + ns_left %= 1e9 as u64 * SECONDS_PER_HOUR_U as u64; + + let minutes = ns_left / (1e9 as u64 * SECONDS_PER_MINUTE_U as u64); + ns_left %= 1e9 as u64 * SECONDS_PER_MINUTE_U as u64; + + let seconds = ns_left / (1e9 as u64); + ns_left %= 1e9 as u64; + + let ms = ns_left / (1e6 as u64); + ns_left %= 1e6 as u64; + + let us = ns_left / (1e3 as u64); + ns_left %= 1e3 as u64; + + let ns = dbg!(ns_left); + + (sign, centuries, years, days, hours, minutes, seconds, ms, us, ns) + } + } impl fmt::Display for Duration { // Prints this duration with automatic selection of the highest and sub-second unit fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let eps = 1e-1; - let is_neg = self.0.is_sign_negative(); - - let days_tf = self.in_unit(TimeUnit::Day); - let mut days = days_tf.hi() + days_tf.lo(); - if days.abs() < eps { - days = 0.0 - } else if is_neg { - days = days.ceil() - } else { - days = days.floor() - }; - let hours_tf = self.in_unit(TimeUnit::Hour) - days * TwoFloat::from(24.0); - let mut hours = hours_tf.hi() + hours_tf.lo(); - if hours.abs() < eps { - hours = 0.0 - } else if is_neg { - hours = hours.ceil() - } else { - hours = hours.floor() - }; - - let minutes_tf = self.in_unit(TimeUnit::Minute) - - hours * TwoFloat::from(60.0) - - days * TwoFloat::from(24.0 * 60.0); - let mut minutes = minutes_tf.hi() + minutes_tf.lo(); - if minutes.abs() < eps { - minutes = 0.0 - } else if is_neg { - minutes = minutes.ceil() - } else { - minutes = minutes.floor() - }; - - let seconds_tf = self.in_unit(TimeUnit::Second) - - minutes * TwoFloat::from(60.0) - - hours * TwoFloat::from(3600.0) - - days * TwoFloat::from(24.0 * 3600.0); - let mut seconds = seconds_tf.hi() + seconds_tf.lo(); - if seconds.abs() < eps { - seconds = 0.0 - } else if is_neg { - seconds = seconds.ceil() - } else { - seconds = seconds.floor() - }; - - let milli_tf = self.in_unit(TimeUnit::Millisecond) - - seconds * TwoFloat::from(1e3) - - minutes * TwoFloat::from(60.0 * 1e3) - - hours * TwoFloat::from(3600.0 * 1e3) - - days * TwoFloat::from(24.0 * 3600.0 * 1e3); - let mut milli = milli_tf.hi() + milli_tf.lo(); - if milli.abs() < eps { - milli = 0.0 - } else if is_neg { - milli = milli.ceil() - } else { - milli = milli.floor() - }; - - // Compute the microseconds for precise nanosecond printing, but we don't actually print the microseconds - let micro_tf = self.in_unit(TimeUnit::Microsecond) - - milli * TwoFloat::from(1e3) - - seconds * TwoFloat::from(1e6) - - minutes * TwoFloat::from(60.0 * 1e6) - - hours * TwoFloat::from(3600.0 * 1e6) - - days * TwoFloat::from(24.0 * 3600.0 * 1e6); - let micro = micro_tf.hi() + micro_tf.lo(); - - let mut nano = 1e3 * micro; - - if nano.abs() < eps || (nano < 0.0 && !is_neg) { - nano = 0.0 - } else { - nano = format!("{:.3}", nano).parse().unwrap(); - } - let mut print_all = false; - let nil = TwoFloat::try_from((std::f64::EPSILON, 0.0)).unwrap(); + let (sign, centuries, years, days, hours, minutes, seconds, milli, us, nano) = self.decompose(); + + let values = [centuries, years, days, hours, minutes, seconds, milli, us, nano]; + let names = ["centuries", "years", "days", "h", "min", "s", "ms", "us", "ns"]; + + let print_all = false; - let neg_one = TwoFloat::from(-1); + let mut interval_start = None; + let mut interval_end = None; - if days.abs() > nil { - fmt::Display::fmt(&days, f)?; - write!(f, " days ")?; - print_all = true; - } - if hours.abs() > nil || print_all { - if is_neg && print_all { - // We have already printed the negative sign - // So let's oppose this number - fmt::Display::fmt(&(hours * neg_one), f)?; - } else { - fmt::Display::fmt(&hours, f)?; - } - write!(f, " h ")?; - print_all = true; - } - if minutes.abs() > nil || print_all { - if is_neg && print_all { - let neg_minutes = minutes * neg_one; - fmt::Display::fmt(&(neg_minutes.hi() + neg_minutes.lo()), f)?; - } else { - fmt::Display::fmt(&minutes, f)?; - } - write!(f, " min ")?; - print_all = true; - } - // If the milliseconds and nanoseconds are nil, then we stop at the second level - if milli.abs() < nil && nano.abs() < nil { - if is_neg && print_all { - let neg_seconds = seconds * neg_one; - fmt::Display::fmt(&(neg_seconds.hi() + neg_seconds.lo()), f)?; + if print_all { + interval_start = Some(0); + interval_end = Some(values.len()-1); } else { - fmt::Display::fmt(&seconds, f)?; + for index in 0..values.len() { + if interval_start.is_none() { + if values[index] > 0 { + interval_start = Some(index); + interval_end = Some(index); } - write!(f, " s") - } else { - if seconds.abs() > nil || print_all { - if is_neg && print_all { - let neg_seconds = seconds * neg_one; - fmt::Display::fmt(&(neg_seconds.hi() + neg_seconds.lo()), f)?; - } else { - fmt::Display::fmt(&seconds, f)?; - } - write!(f, " s ")?; - print_all = true; - } - if nano.abs() < nil || (is_neg && nano * neg_one <= nil) { - // Only stop at the millisecond level - if is_neg && print_all { - let neg_milli = milli * neg_one; - fmt::Display::fmt(&(neg_milli.hi() + neg_milli.lo()), f)?; } else { - fmt::Display::fmt(&milli, f)?; + if values[index] > 0 { + interval_end = Some(index); } - write!(f, " ms") - } else { - if milli.abs() > nil || print_all { - if is_neg && print_all { - let neg_milli = milli * neg_one; - fmt::Display::fmt(&(neg_milli.hi() + neg_milli.lo()), f)?; - } else { - fmt::Display::fmt(&milli, f)?; } - write!(f, " ms ")?; } - if is_neg && print_all { - let neg_nano = nano * neg_one; - fmt::Display::fmt(&(neg_nano.hi() + neg_nano.lo()), f)?; - } else { - fmt::Display::fmt(&nano, f)?; } - write!(f, " ns") + assert!(interval_start.is_some()); + assert!(interval_end.is_some()); + + if sign == -1 { + write!(f, "-")?; } + + for i in interval_start.unwrap()..=interval_end.unwrap() { + write!(f, "{} {} ", values[i], names[i])?; } + + + Ok(()) + } } diff --git a/src/epoch.rs b/src/epoch.rs index 1bd37dbf..99707a0a 100644 --- a/src/epoch.rs +++ b/src/epoch.rs @@ -9,7 +9,6 @@ use crate::{ Errors, TimeSystem, DAYS_PER_CENTURY, ET_EPOCH_S, J1900_OFFSET, J2000_OFFSET, MJD_OFFSET, SECONDS_PER_DAY, }; -use std::convert::TryFrom; use std::fmt; use std::ops::{Add, AddAssign, Sub, SubAssign}; use std::str::FromStr; @@ -258,14 +257,6 @@ impl Epoch { Self::from_jde_tai(days) - TimeUnit::Second * ET_OFFSET_S } - /// Builds a new Epoch from the hi and lo two-float values - pub fn try_from_hi_lo(hi: f64, lo: f64) -> Result { - match Duration::try_from((hi, lo)) { - Ok(t) => Ok(Self(t)), - Err(_) => Err(Errors::ConversionOverlapError(hi, lo)), - } - } - /// Attempts to build an Epoch from the provided Gregorian date and time in TAI. pub fn maybe_from_gregorian_tai( year: i32, @@ -931,13 +922,6 @@ impl Epoch { } } -impl TryFrom<(f64, f64)> for Epoch { - type Error = Errors; - - fn try_from(value: (f64, f64)) -> Result { - Self::try_from_hi_lo(value.0, value.1) - } -} impl FromStr for Epoch { type Err = Errors; From 1529409d942ee6a5cd6d1ad08290b9335db7bf8f Mon Sep 17 00:00:00 2001 From: Chris de Claverie Date: Tue, 4 Jan 2022 12:53:49 +0100 Subject: [PATCH 04/17] fix tests and specialize impl_ops to get better precision for integers --- Cargo.toml | 4 +- src/duration.rs | 463 +++++++++++++++++++++++++++++++++++++----------- src/epoch.rs | 7 +- 3 files changed, 372 insertions(+), 102 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 92fe8a45..054c6185 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,12 @@ readme = "README.md" license = "Apache-2.0" [dependencies] +divrem = "1.0.0" rand = "0.8" rand_distr = "0.4" regex = "1.3" serde = "1.0" serde_derive = "1.0" -twofloat = "0.4.1" [dev-dependencies] serde_derive = "1.0" @@ -25,4 +25,4 @@ criterion = "0.3" [[bench]] name = "bench_epoch" -harness = false \ No newline at end of file +harness = false diff --git a/src/duration.rs b/src/duration.rs index 09f4b9bb..dc8f4638 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -42,22 +42,24 @@ impl<'de> Deserialize<'de> for Duration { } } -macro_rules! impl_ops_for_type { + + +macro_rules! impl_ops_f { ($type:ident) => { impl Mul<$type> for TimeUnit { type Output = Duration; fn mul(self, q: $type) -> Duration { match self { TimeUnit::Century => { - Duration::from_days(TwoFloat::from(q) * TwoFloat::from(DAYS_PER_CENTURY_U)) + Duration::from_days_f(q as f64 * DAYS_PER_CENTURY_U as f64) } - TimeUnit::Day => Duration::from_days(TwoFloat::from(q)), - TimeUnit::Hour => Duration::from_hours(TwoFloat::from(q)), - TimeUnit::Minute => Duration::from_minutes(TwoFloat::from(q)), - TimeUnit::Second => Duration::from_seconds(TwoFloat::from(q)), - TimeUnit::Millisecond => Duration::from_milliseconds(TwoFloat::from(q)), - TimeUnit::Microsecond => Duration::from_microseconds(TwoFloat::from(q)), - TimeUnit::Nanosecond => Duration::from_nanoseconds(TwoFloat::from(q)), + TimeUnit::Day => Duration::from_days_f(q as f64), + TimeUnit::Hour => Duration::from_hours_f(q as f64), + TimeUnit::Minute => Duration::from_minutes_f(q as f64), + TimeUnit::Second => Duration::from_seconds_f(q as f64), + TimeUnit::Millisecond => Duration::from_milliseconds_f(q as f64), + TimeUnit::Microsecond => Duration::from_microseconds_f(q as f64), + TimeUnit::Nanosecond => Duration::from_nanoseconds_f(q as f64), } } } @@ -66,16 +68,16 @@ macro_rules! impl_ops_for_type { type Output = Duration; fn mul(self, q: TimeUnit) -> Duration { match q { - TimeUnit::Century => Duration::from_days( - TwoFloat::from(self) * TwoFloat::from(DAYS_PER_CENTURY_U), + TimeUnit::Century => Duration::from_days_f( + self as f64 * DAYS_PER_CENTURY_U as f64, ), - TimeUnit::Day => Duration::from_days(TwoFloat::from(self)), - TimeUnit::Hour => Duration::from_hours(TwoFloat::from(self)), - TimeUnit::Minute => Duration::from_minutes(TwoFloat::from(self)), - TimeUnit::Second => Duration::from_seconds(TwoFloat::from(self)), - TimeUnit::Millisecond => Duration::from_milliseconds(TwoFloat::from(self)), - TimeUnit::Microsecond => Duration::from_microseconds(TwoFloat::from(self)), - TimeUnit::Nanosecond => Duration::from_nanoseconds(TwoFloat::from(self)), + TimeUnit::Day => Duration::from_days_f(self as f64), + TimeUnit::Hour => Duration::from_hours_f(self as f64), + TimeUnit::Minute => Duration::from_minutes_f(self as f64), + TimeUnit::Second => Duration::from_seconds_f(self as f64), + TimeUnit::Millisecond => Duration::from_milliseconds_f(self as f64), + TimeUnit::Microsecond => Duration::from_microseconds_f(self as f64), + TimeUnit::Nanosecond => Duration::from_nanoseconds_f(self as f64), } } } @@ -83,68 +85,327 @@ macro_rules! impl_ops_for_type { impl Mul<$type> for Duration { type Output = Duration; fn mul(self, q: $type) -> Duration { - Self { - 0: self.0 * TwoFloat::from(q), - } + Duration::from_seconds_f(self.in_seconds() * q as f64) + } } impl Div<$type> for Duration { type Output = Duration; fn div(self, q: $type) -> Duration { - Self { - 0: self.0 / TwoFloat::from(q), + Duration::from_seconds_f(self.in_seconds() / q as f64) + + } + } + + impl Mul for $type { + type Output = Duration; + fn mul(self, q: Duration) -> Duration { + Duration::from_seconds_f(self as f64 * q.in_seconds()) + + } + } + + impl TimeUnitHelper for $type {} + }; +} + +macro_rules! impl_ops_u { + ($type:ident) => { + impl Mul<$type> for TimeUnit { + type Output = Duration; + fn mul(self, q: $type) -> Duration { + match self { + TimeUnit::Century => { + Duration::from_days_u(q as u128 * DAYS_PER_CENTURY_U as u128) + } + TimeUnit::Day => Duration::from_days_u(q as u128), + TimeUnit::Hour => Duration::from_hours_u(q as u128), + TimeUnit::Minute => Duration::from_minutes_u(q as u128), + TimeUnit::Second => Duration::from_seconds_u(q as u128), + TimeUnit::Millisecond => Duration::from_milliseconds_u(q as u128), + TimeUnit::Microsecond => Duration::from_microseconds_u(q as u128), + TimeUnit::Nanosecond => Duration::from_nanoseconds_u(q as u128), + } + } + } + + impl Mul for $type { + type Output = Duration; + fn mul(self, q: TimeUnit) -> Duration { + match q { + TimeUnit::Century => Duration::from_days_u( + self as u128 * DAYS_PER_CENTURY_U as u128, + ), + TimeUnit::Day => Duration::from_days_u(self as u128), + TimeUnit::Hour => Duration::from_hours_u(self as u128), + TimeUnit::Minute => Duration::from_minutes_u(self as u128), + TimeUnit::Second => Duration::from_seconds_u(self as u128), + TimeUnit::Millisecond => Duration::from_milliseconds_u(self as u128), + TimeUnit::Microsecond => Duration::from_microseconds_u(self as u128), + TimeUnit::Nanosecond => Duration::from_nanoseconds_u(self as u128), } } } + impl Mul<$type> for Duration { + type Output = Duration; + fn mul(self, q: $type) -> Duration { + Self::from_nanoseconds_i(self.total_ns() * q as i128) + + } + } + + impl Div<$type> for Duration { + type Output = Duration; + fn div(self, q: $type) -> Duration { + Self::from_nanoseconds_i(self.total_ns() / q as i128) + + } + } + impl Mul for $type { type Output = Duration; fn mul(self, q: Duration) -> Duration { - Duration { - 0: q.0 * TwoFloat::from(self), + Duration::from_nanoseconds_i(q.total_ns() * self as i128) + + } + } + + impl TimeUnitHelper for $type {} + }; +} + +macro_rules! impl_ops_i { + ($type:ident) => { + impl Mul<$type> for TimeUnit { + type Output = Duration; + fn mul(self, q: $type) -> Duration { + match self { + TimeUnit::Century => { + Duration::from_days_i(q as i128 * DAYS_PER_CENTURY_U as i128) + } + TimeUnit::Day => Duration::from_days_i(q as i128), + TimeUnit::Hour => Duration::from_hours_i(q as i128), + TimeUnit::Minute => Duration::from_minutes_i(q as i128), + TimeUnit::Second => Duration::from_seconds_i(q as i128), + TimeUnit::Millisecond => Duration::from_milliseconds_i(q as i128), + TimeUnit::Microsecond => Duration::from_microseconds_i(q as i128), + TimeUnit::Nanosecond => Duration::from_nanoseconds_i(q as i128), } } } + impl Mul for $type { + type Output = Duration; + fn mul(self, q: TimeUnit) -> Duration { + match q { + TimeUnit::Century => Duration::from_days_i( + self as i128 * DAYS_PER_CENTURY_U as i128, + ), + TimeUnit::Day => Duration::from_days_i(self as i128), + TimeUnit::Hour => Duration::from_hours_i(self as i128), + TimeUnit::Minute => Duration::from_minutes_i(self as i128), + TimeUnit::Second => Duration::from_seconds_i(self as i128), + TimeUnit::Millisecond => Duration::from_milliseconds_i(self as i128), + TimeUnit::Microsecond => Duration::from_microseconds_i(self as i128), + TimeUnit::Nanosecond => Duration::from_nanoseconds_i(self as i128), + } + } + } + + impl Mul<$type> for Duration { + type Output = Duration; + fn mul(self, q: $type) -> Duration { + Self::from_nanoseconds_i(self.total_ns() * q as i128) + + } + } + + impl Div<$type> for Duration { + type Output = Duration; + fn div(self, q: $type) -> Duration { + Self::from_nanoseconds_i(self.total_ns() / q as i128) + + } + } + + impl Mul for $type { + type Output = Duration; + fn mul(self, q: Duration) -> Duration { + Duration::from_nanoseconds_i(q.total_ns() * self as i128) + } + } + impl TimeUnitHelper for $type {} }; } impl Duration { - pub fn from_days(days: TwoFloat) -> Self { - Self { - 0: days * TwoFloat::from(SECONDS_PER_DAY_U), - } + pub fn new(ns : u64, centuries : i64) -> Self { + let mut out = Duration { ns, centuries }; + out.normalize(); + out } - pub fn from_hours(hours: TwoFloat) -> Self { - Self { - 0: hours * TwoFloat::from(SECONDS_PER_HOUR_U), + + pub fn total_ns(self) -> i128 { + self.centuries as i128 * NS_PER_CENTURY_U as i128 + self.ns as i128 + } + + fn normalize(&mut self) { + if self.ns > NS_PER_CENTURY_U as u64 { + let carry = self.ns / NS_PER_CENTURY_U as u64; + self.centuries += carry as i64; + self.ns %= NS_PER_CENTURY_U as u64; } } - pub fn from_minutes(minutes: TwoFloat) -> Self { + + pub fn from_value_f(mut value : f64, century_divider : u64, ns_multiplier : u64) -> Self { + let centuries = (value.div_euclid(century_divider as f64)) as i64; + value = value.rem_euclid(century_divider as f64); + + // Risks : Overflow, loss of precision, unexpected roundings + let ns = dbg!(dbg!(dbg!(value) * ns_multiplier as f64).round() as u64); Self { - 0: minutes * TwoFloat::from(SECONDS_PER_MINUTE_U), + ns, centuries } } - pub fn from_seconds(seconds: TwoFloat) -> Self { - Self { 0: seconds } + pub fn from_days_f(days: f64) -> Self { + let century_divider = DAYS_PER_CENTURY_U; + let ns_multiplier = SECONDS_PER_DAY_U * 1e9 as u64; + Self::from_value_f(days, century_divider, ns_multiplier) } - pub fn from_milliseconds(ms: TwoFloat) -> Self { - Self { - 0: ms * TwoFloat::from(1e-3), - } + pub fn from_hours_f(hours: f64) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U; + let ns_multiplier = SECONDS_PER_HOUR_U * 1e9 as u64; + Self::from_value_f(hours, century_divider, ns_multiplier) + } + pub fn from_minutes_f(minutes: f64) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U; + let ns_multiplier = SECONDS_PER_MINUTE_U * 1e9 as u64; + Self::from_value_f(minutes, century_divider, ns_multiplier) + } + pub fn from_seconds_f(seconds: f64) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U; + let ns_multiplier = 1e9 as u64; + Self::from_value_f(seconds, century_divider, ns_multiplier) } - pub fn from_microseconds(us: TwoFloat) -> Self { + pub fn from_milliseconds_f(ms: f64) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e3 as u64; + let ns_multiplier = 1e6 as u64; + Self::from_value_f(ms, century_divider, ns_multiplier) + } + pub fn from_microseconds_f(us: f64) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e6 as u64; + let ns_multiplier = 1e3 as u64; + Self::from_value_f(us, century_divider, ns_multiplier) + } + pub fn from_nanoseconds_f(ns: f64) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e9 as u64; + let ns_multiplier = 1; + Self::from_value_f(ns, century_divider, ns_multiplier) + } + + + + pub fn from_value_u(mut value : u128, century_divider : u64, ns_multiplier : u64) -> Self { + let centuries = (value.div_euclid(century_divider as u128)) as i64; + value = value.rem_euclid(century_divider as u128); + + // Risks : Overflow, loss of precision, unexpected roundings + let ns = (value * ns_multiplier as u128) as u64; Self { - 0: us * TwoFloat::from(1e-6), + ns, centuries } } - pub fn from_nanoseconds(ns: TwoFloat) -> Self { + pub fn from_days_u(days: u128) -> Self { + let century_divider = DAYS_PER_CENTURY_U; + let ns_multiplier = SECONDS_PER_DAY_U * 1e9 as u64; + Self::from_value_u(days, century_divider, ns_multiplier) + } + pub fn from_hours_u(hours: u128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U; + let ns_multiplier = SECONDS_PER_HOUR_U * 1e9 as u64; + Self::from_value_u(hours, century_divider, ns_multiplier) + } + pub fn from_minutes_u(minutes: u128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U; + let ns_multiplier = SECONDS_PER_MINUTE_U * 1e9 as u64; + Self::from_value_u(minutes, century_divider, ns_multiplier) + } + pub fn from_seconds_u(seconds: u128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U; + let ns_multiplier = 1e9 as u64; + Self::from_value_u(seconds, century_divider, ns_multiplier) + } + pub fn from_milliseconds_u(ms: u128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e3 as u64; + let ns_multiplier = 1e6 as u64; + Self::from_value_u(ms, century_divider, ns_multiplier) + } + pub fn from_microseconds_u(us: u128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e6 as u64; + let ns_multiplier = 1e3 as u64; + Self::from_value_u(us, century_divider, ns_multiplier) + } + pub fn from_nanoseconds_u(ns: u128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e9 as u64; + let ns_multiplier = 1 as u64; + Self::from_value_u(ns, century_divider, ns_multiplier) + } + + + + + pub fn from_value_i(mut value : i128, century_divider : u64, ns_multiplier : u64) -> Self { + let centuries = (value.div_euclid(century_divider as i128)) as i64; + value = value.rem_euclid(century_divider as i128); + + // Risks : Overflow, loss of precision, unexpected roundings + let ns = (value * ns_multiplier as i128) as u64; Self { - 0: ns * TwoFloat::from(1e-9), + ns, centuries } } + pub fn from_days_i(days: i128) -> Self { + let century_divider = DAYS_PER_CENTURY_U; + let ns_multiplier = SECONDS_PER_DAY_U * 1e9 as u64; + Self::from_value_i(days, century_divider, ns_multiplier) + } + pub fn from_hours_i(hours: i128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U; + let ns_multiplier = SECONDS_PER_HOUR_U * 1e9 as u64; + Self::from_value_i(hours, century_divider, ns_multiplier) + } + pub fn from_minutes_i(minutes: i128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U; + let ns_multiplier = SECONDS_PER_MINUTE_U * 1e9 as u64; + Self::from_value_i(minutes, century_divider, ns_multiplier) + } + pub fn from_seconds_i(seconds: i128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U; + let ns_multiplier = 1e9 as u64; + Self::from_value_i(seconds, century_divider, ns_multiplier) + } + pub fn from_milliseconds_i(ms: i128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e3 as u64; + let ns_multiplier = 1e6 as u64; + Self::from_value_i(ms, century_divider, ns_multiplier) + } + pub fn from_microseconds_i(us: i128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e6 as u64; + let ns_multiplier = 1e3 as u64; + Self::from_value_i(us, century_divider, ns_multiplier) + } + pub fn from_nanoseconds_i(ns: i128) -> Self { + let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e9 as u64; + let ns_multiplier = 1 as u64; + Self::from_value_i(ns, century_divider, ns_multiplier) + } + + + + /// Creates a new duration from the provided unit pub fn from_f64(value: f64, unit: TimeUnit) -> Self { @@ -174,6 +435,7 @@ impl Duration { if self.centuries < 0 { -*self } else { *self } } + pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64, u64, u64) { @@ -185,7 +447,7 @@ impl Duration { let centuries = ns_left / NS_PER_CENTURY_U; - + let years = ns_left / (1e9 as u64 * SECONDS_PER_DAY_U as u64 * 365); ns_left %= 1e9 as u64 * SECONDS_PER_DAY_U as u64 * 365; @@ -211,8 +473,8 @@ impl Duration { let ns = dbg!(ns_left); (sign, centuries, years, days, hours, minutes, seconds, ms, us, ns) - } - } + } +} impl fmt::Display for Duration { // Prints this duration with automatic selection of the highest and sub-second unit @@ -231,26 +493,26 @@ impl fmt::Display for Duration { if print_all { interval_start = Some(0); interval_end = Some(values.len()-1); - } else { + } else { for index in 0..values.len() { if interval_start.is_none() { if values[index] > 0 { interval_start = Some(index); interval_end = Some(index); - } + } } else { if values[index] > 0 { interval_end = Some(index); - } } } - } + } + } assert!(interval_start.is_some()); assert!(interval_end.is_some()); if sign == -1 { write!(f, "-")?; - } + } for i in interval_start.unwrap()..=interval_end.unwrap() { write!(f, "{} {} ", values[i], names[i])?; @@ -498,6 +760,7 @@ pub trait TimeUnitHelper: Copy + Mul { } } + #[derive(Copy, Clone, Debug, PartialEq)] pub enum TimeUnit { /// 36525 days, it the number of days per century in the Julian calendar @@ -553,17 +816,27 @@ impl TimeUnit { } } -impl_ops_for_type!(f32); -impl_ops_for_type!(f64); -impl_ops_for_type!(u8); -impl_ops_for_type!(i8); -impl_ops_for_type!(u16); -impl_ops_for_type!(i16); -impl_ops_for_type!(u32); -impl_ops_for_type!(i32); -impl_ops_for_type!(u64); -impl_ops_for_type!(i64); -impl_ops_for_type!(u128); + + +impl_ops_u!(u8); +impl_ops_i!(i8); + +impl_ops_u!(u16); +impl_ops_i!(i16); + +impl_ops_u!(u32); +impl_ops_i!(i32); + +impl_ops_u!(u64); +impl_ops_i!(i64); + +impl_ops_u!(u128); +impl_ops_i!(i128); + +impl_ops_f!(f32); +impl_ops_f!(f64); + + #[test] fn time_unit() { @@ -596,8 +869,8 @@ fn time_unit() { let sum: Duration = seven_hours + six_minutes + five_seconds; assert!((sum.in_seconds() - 25565.0).abs() < EPSILON); - let neg_sum = -sum; - assert!((neg_sum.in_seconds() + 25565.0).abs() < EPSILON); + let neg_sum = dbg!(-dbg!(sum)); + assert!(dbg!((dbg!(neg_sum.in_seconds()) + dbg!(25565.0)).abs()) < EPSILON); assert_eq!(neg_sum.abs(), sum, "abs failed"); @@ -619,8 +892,8 @@ fn time_unit() { let third_hour: Duration = -1 * TimeUnit::Hour / 3; let sum: Duration = quarter_hour + third_hour; let delta = sum.in_unit(TimeUnit::Millisecond).floor() - - sum.in_unit(TimeUnit::Second).floor() * TwoFloat::from(1000.0); - println!("{:?}", delta * TwoFloat::from(-1) == TwoFloat::from(0)); + - sum.in_unit(TimeUnit::Second).floor() * 1000.0; + println!("{:?}", delta * -1.0 == 0.0); assert!((sum.in_unit_f64(TimeUnit::Minute) + 35.0).abs() < EPSILON); } @@ -628,12 +901,12 @@ fn time_unit() { fn duration_print() { // Check printing adds precision assert_eq!( - format!("{}", TimeUnit::Day * 10.0 + TimeUnit::Hour * 5), - "10 days 5 h 0 min 0 s" + format!("{}", TimeUnit::Day * 10.0 + TimeUnit::Hour * 5).trim(), + "10 days 5 h" ); assert_eq!( - format!("{}", TimeUnit::Hour * 5 + TimeUnit::Millisecond * 256), + format!("{}", TimeUnit::Hour * 5 + TimeUnit::Millisecond * 256).trim(), "5 h 0 min 0 s 256 ms" ); @@ -641,38 +914,30 @@ fn duration_print() { format!( "{}", TimeUnit::Hour * 5 + TimeUnit::Millisecond * 256 + TimeUnit::Nanosecond - ), - "5 h 0 min 0 s 256 ms 1 ns" + ).trim(), + "5 h 0 min 0 s 256 ms 0 us 1 ns" ); assert_eq!( - format!("{}", TimeUnit::Hour + TimeUnit::Second), + format!("{}", TimeUnit::Hour + TimeUnit::Second).trim(), "1 h 0 min 1 s" ); - assert_eq!( - format!( - "{}", - TimeUnit::Hour * 5 - + TimeUnit::Millisecond * 256 - + TimeUnit::Microsecond - + TimeUnit::Nanosecond * 3.5 - ), - "5 h 0 min 0 s 256 ms 1003.5 ns" - ); - // Check printing negative durations only shows one negative sign assert_eq!( - format!("{}", TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256), + format!("{}", TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256).trim(), "-5 h 0 min 0 s 256 ms" ); + let d : Duration = TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256 + TimeUnit::Nanosecond * -3; + dbg!(d.in_seconds()); + assert_eq!( format!( "{}", - TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256 + TimeUnit::Nanosecond * -3.5 - ), - "-5 h 0 min 0 s 256 ms 3.5 ns" + TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256 + dbg!(TimeUnit::Nanosecond * -3) + ).trim(), + "-5 h 0 min 0 s 256 ms 0 us 3 ns" ); assert_eq!( @@ -680,20 +945,20 @@ fn duration_print() { "{}", (TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256) - (TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256 + TimeUnit::Nanosecond * 2) - ), + ).trim(), "-2 ns" ); assert_eq!( - format!("{}", Duration::from_nanoseconds(TwoFloat::from(2))), + format!("{}", Duration::from_nanoseconds_f(2.0)).trim(), "2 ns" ); // Check that we support nanoseconds pas GPS time - let now = TimeUnit::Nanosecond * 1286495254000000123_u128; + let now = TimeUnit::Nanosecond * 1286495254000000203_u128; assert_eq!( - format!("{}", now), - "14889 days 23 h 47 min 34 s 0 ms 203.125 ns" + format!("{}", now).trim(), + "40 years 289 days 23 h 47 min 34 s 0 ms 0 us 203 ns" ); let arbitrary = 14889.days() @@ -703,8 +968,8 @@ fn duration_print() { + 0.milliseconds() + 123.nanoseconds(); assert_eq!( - format!("{}", arbitrary), - "14889 days 23 h 47 min 34 s 0 ms 123 ns" + format!("{}", arbitrary).trim(), + "40 years 289 days 23 h 47 min 34 s 0 ms 0 us 123 ns" ); // Test fractional @@ -716,15 +981,15 @@ fn duration_print() { sum.in_unit_f64(TimeUnit::Minute), (1.0 / 4.0 + 1.0 / 3.0) * 60.0 ); - assert_eq!(format!("{}", sum), "35 min 0 s"); // Note the automatic unit selection + assert_eq!(format!("{}", sum).trim(), "35 min"); // Note the automatic unit selection let quarter_hour = -0.25 * TimeUnit::Hour; let third_hour: Duration = -1 * TimeUnit::Hour / 3; let sum: Duration = quarter_hour + third_hour; let delta = sum.in_unit(TimeUnit::Millisecond).floor() - - sum.in_unit(TimeUnit::Second).floor() * TwoFloat::from(1000.0); - println!("{:?}", delta * TwoFloat::from(-1) == TwoFloat::from(0)); - assert_eq!(format!("{}", sum), "-35 min 0 s"); // Note the automatic unit selection + - sum.in_unit(TimeUnit::Second).floor() * 1000.0; + println!("{:?}", delta * -1.0 == 0.0); // This floating-point comparison looks wrong + assert_eq!(format!("{}", sum).trim(), "-35 min"); // Note the automatic unit selection } #[test] diff --git a/src/epoch.rs b/src/epoch.rs index 99707a0a..fe66d76b 100644 --- a/src/epoch.rs +++ b/src/epoch.rs @@ -9,6 +9,7 @@ use crate::{ Errors, TimeSystem, DAYS_PER_CENTURY, ET_EPOCH_S, J1900_OFFSET, J2000_OFFSET, MJD_OFFSET, SECONDS_PER_DAY, }; +use std::convert::TryFrom; use std::fmt; use std::ops::{Add, AddAssign, Sub, SubAssign}; use std::str::FromStr; @@ -1367,6 +1368,10 @@ fn gpst() { #[test] fn spice_et_tdb() { use crate::J2000_NAIF; + let round_prec = |val : f64, prec : i32| -> f64 { + let power = (10.0 as f64).powi(prec); + ((val * power).round()) / power + }; /* >>> sp.str2et("2012-02-07 11:22:33 UTC") 381885819.18493587 @@ -1433,7 +1438,7 @@ fn spice_et_tdb() { let sp_ex = Epoch::from_et_seconds(381_885_753.003_859_5); assert!(dbg!(2455964.9739931 - sp_ex.as_jde_tdb_days()).abs() < 4.7e-10); - assert!((2455964.9739931 - sp_ex.as_jde_et_days()).abs() < std::f64::EPSILON); + assert!((2455964.9739931 - dbg!(round_prec(sp_ex.as_jde_et_days(), 7))).abs() < std::f64::EPSILON); let sp_ex = Epoch::from_et_seconds(0.0); assert!(sp_ex.as_et_seconds() < std::f64::EPSILON); From 696db8bd76fb665b8adefcfce3051298eb574d98 Mon Sep 17 00:00:00 2001 From: Chris de Claverie Date: Thu, 6 Jan 2022 11:56:58 +0100 Subject: [PATCH 05/17] switch to i16 for centuries of Duration --- src/duration.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index dc8f4638..0e6dea82 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -28,7 +28,7 @@ const ONE: u64 = 1_u64; #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] pub struct Duration { ns : u64, // 1 century is about 3.1e18 ns, and max value of u64 is about 1e19.26 - centuries : i64 // +- 9.22e18 centuries is the possible range for a Duration + centuries : i16 // +- 9.22e18 centuries is the possible range for a Duration // Reducing the range could be a good tradeoff for a lowerm memory footprint } @@ -242,7 +242,7 @@ macro_rules! impl_ops_i { } impl Duration { - pub fn new(ns : u64, centuries : i64) -> Self { + pub fn new(ns : u64, centuries : i16) -> Self { let mut out = Duration { ns, centuries }; out.normalize(); out @@ -255,17 +255,17 @@ impl Duration { fn normalize(&mut self) { if self.ns > NS_PER_CENTURY_U as u64 { let carry = self.ns / NS_PER_CENTURY_U as u64; - self.centuries += carry as i64; + self.centuries = self.centuries.saturating_add(carry as i16); self.ns %= NS_PER_CENTURY_U as u64; } } pub fn from_value_f(mut value : f64, century_divider : u64, ns_multiplier : u64) -> Self { - let centuries = (value.div_euclid(century_divider as f64)) as i64; + let centuries = (value.div_euclid(century_divider as f64)) as i16; value = value.rem_euclid(century_divider as f64); // Risks : Overflow, loss of precision, unexpected roundings - let ns = dbg!(dbg!(dbg!(value) * ns_multiplier as f64).round() as u64); + let ns = (value * ns_multiplier as f64).round() as u64; Self { ns, centuries } @@ -309,7 +309,7 @@ impl Duration { pub fn from_value_u(mut value : u128, century_divider : u64, ns_multiplier : u64) -> Self { - let centuries = (value.div_euclid(century_divider as u128)) as i64; + let centuries = (value.div_euclid(century_divider as u128)) as i16; value = value.rem_euclid(century_divider as u128); // Risks : Overflow, loss of precision, unexpected roundings @@ -358,7 +358,7 @@ impl Duration { pub fn from_value_i(mut value : i128, century_divider : u64, ns_multiplier : u64) -> Self { - let centuries = (value.div_euclid(century_divider as i128)) as i64; + let centuries = (value.div_euclid(century_divider as i128)) as i16; value = value.rem_euclid(century_divider as i128); // Risks : Overflow, loss of precision, unexpected roundings @@ -421,7 +421,7 @@ impl Duration { /// Returns this duration in seconds f64. /// For high fidelity comparisons, it is recommended to keep using the Duration structure. pub fn in_seconds(&self) -> f64 { - (self.ns as f64 / 1e9) + (self.centuries * DAYS_PER_CENTURY_U as i64 * SECONDS_PER_DAY_U as i64) as f64 + (self.ns as f64 / 1e9) + (self.centuries as i64 * DAYS_PER_CENTURY_U as i64 * SECONDS_PER_DAY_U as i64) as f64 } /// Returns the value of this duration in the requested unit. @@ -569,7 +569,7 @@ impl Add for Duration { let total_centuries = self.centuries .saturating_add(rhs.centuries) - .saturating_add(century_carry as i64); + .saturating_add(century_carry as i16); Self { ns : total_ns as u64, centuries : total_centuries } } @@ -593,7 +593,7 @@ impl Sub for Duration { } - let total_centuries = self.centuries.saturating_sub(rhs.centuries).saturating_sub(century_borrow as i64); + let total_centuries = self.centuries.saturating_sub(rhs.centuries).saturating_sub(century_borrow as i16); Self { ns : total_ns as u64, centuries : total_centuries } } } From 1efbdca1418161be32558962007a1a0168580e74 Mon Sep 17 00:00:00 2001 From: Chris de Claverie Date: Mon, 10 Jan 2022 18:14:10 +0100 Subject: [PATCH 06/17] remove unchecked as casts, remove 128-bit operators to simplify the code --- src/duration.rs | 355 +++++++++++++++++------------------------------- 1 file changed, 126 insertions(+), 229 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 0e6dea82..285c9b12 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -14,16 +14,27 @@ use std::str::FromStr; -const SECONDS_PER_MINUTE_U: u64 = 60; -const MINUTES_PER_HOUR_U: u64 = 60; -const HOURS_PER_DAY_U: u64 = 24; -const SECONDS_PER_HOUR_U: u64 = SECONDS_PER_MINUTE_U * MINUTES_PER_HOUR_U; -const SECONDS_PER_DAY_U: u64 = SECONDS_PER_HOUR_U * HOURS_PER_DAY_U; -const DAYS_PER_CENTURY_U: u64 = 36_525; -const NS_PER_DAY_U: u64 = 1e9 as u64 * SECONDS_PER_DAY_U; -const NS_PER_CENTURY_U: u64 = DAYS_PER_CENTURY_U * NS_PER_DAY_U; +const SECONDS_PER_MINUTE_U: u16 = 60; +const MINUTES_PER_HOUR_U: u16 = 60; +const HOURS_PER_DAY_U: u16 = 24; +const SECONDS_PER_HOUR_U: u16 = SECONDS_PER_MINUTE_U * MINUTES_PER_HOUR_U; +const SECONDS_PER_DAY_U: u32 = SECONDS_PER_HOUR_U as u32 * HOURS_PER_DAY_U as u32; +const DAYS_PER_CENTURY_U: u16 = 36_525; +const NS_PER_DAY_U: u64 = 10u64.pow(9) * SECONDS_PER_DAY_U as u64; +const NS_PER_CENTURY_U: u64 = DAYS_PER_CENTURY_U as u64 * NS_PER_DAY_U as u64; const ONE: u64 = 1_u64; +const SECONDS_PER_MINUTE_F: f64 = 60.0; +const MINUTES_PER_HOUR_F: f64 = 60.0; +const HOURS_PER_DAY_F: f64 = 24.0; +const SECONDS_PER_HOUR_F: f64 = SECONDS_PER_MINUTE_F * MINUTES_PER_HOUR_F; +const SECONDS_PER_DAY_F: f64 = SECONDS_PER_HOUR_F * HOURS_PER_DAY_F; +const DAYS_PER_CENTURY_F: f64 = 36_525.0; +const NS_PER_DAY_F: f64 = 1e9 * SECONDS_PER_DAY_F; +const NS_PER_CENTURY_F: f64 = DAYS_PER_CENTURY_F * NS_PER_DAY_F; +const ONE_F: f64 = 1.0; + + /// Defines generally usable durations for high precision math with Epoch #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] pub struct Duration { @@ -51,15 +62,15 @@ macro_rules! impl_ops_f { fn mul(self, q: $type) -> Duration { match self { TimeUnit::Century => { - Duration::from_days_f(q as f64 * DAYS_PER_CENTURY_U as f64) + Duration::from_days_f(f64::from(q) * DAYS_PER_CENTURY_F) } - TimeUnit::Day => Duration::from_days_f(q as f64), - TimeUnit::Hour => Duration::from_hours_f(q as f64), - TimeUnit::Minute => Duration::from_minutes_f(q as f64), - TimeUnit::Second => Duration::from_seconds_f(q as f64), - TimeUnit::Millisecond => Duration::from_milliseconds_f(q as f64), - TimeUnit::Microsecond => Duration::from_microseconds_f(q as f64), - TimeUnit::Nanosecond => Duration::from_nanoseconds_f(q as f64), + TimeUnit::Day => Duration::from_days_f(f64::from(q)), + TimeUnit::Hour => Duration::from_hours_f(f64::from(q)), + TimeUnit::Minute => Duration::from_minutes_f(f64::from(q)), + TimeUnit::Second => Duration::from_seconds_f(f64::from(q)), + TimeUnit::Millisecond => Duration::from_milliseconds_f(f64::from(q)), + TimeUnit::Microsecond => Duration::from_microseconds_f(f64::from(q)), + TimeUnit::Nanosecond => Duration::from_nanoseconds_f(f64::from(q)), } } } @@ -69,15 +80,15 @@ macro_rules! impl_ops_f { fn mul(self, q: TimeUnit) -> Duration { match q { TimeUnit::Century => Duration::from_days_f( - self as f64 * DAYS_PER_CENTURY_U as f64, + f64::from(self) * DAYS_PER_CENTURY_F, ), - TimeUnit::Day => Duration::from_days_f(self as f64), - TimeUnit::Hour => Duration::from_hours_f(self as f64), - TimeUnit::Minute => Duration::from_minutes_f(self as f64), - TimeUnit::Second => Duration::from_seconds_f(self as f64), - TimeUnit::Millisecond => Duration::from_milliseconds_f(self as f64), - TimeUnit::Microsecond => Duration::from_microseconds_f(self as f64), - TimeUnit::Nanosecond => Duration::from_nanoseconds_f(self as f64), + TimeUnit::Day => Duration::from_days_f(f64::from(self)), + TimeUnit::Hour => Duration::from_hours_f(f64::from(self)), + TimeUnit::Minute => Duration::from_minutes_f(f64::from(self)), + TimeUnit::Second => Duration::from_seconds_f(f64::from(self)), + TimeUnit::Millisecond => Duration::from_milliseconds_f(f64::from(self)), + TimeUnit::Microsecond => Duration::from_microseconds_f(f64::from(self)), + TimeUnit::Nanosecond => Duration::from_nanoseconds_f(f64::from(self)), } } } @@ -110,72 +121,6 @@ macro_rules! impl_ops_f { }; } -macro_rules! impl_ops_u { - ($type:ident) => { - impl Mul<$type> for TimeUnit { - type Output = Duration; - fn mul(self, q: $type) -> Duration { - match self { - TimeUnit::Century => { - Duration::from_days_u(q as u128 * DAYS_PER_CENTURY_U as u128) - } - TimeUnit::Day => Duration::from_days_u(q as u128), - TimeUnit::Hour => Duration::from_hours_u(q as u128), - TimeUnit::Minute => Duration::from_minutes_u(q as u128), - TimeUnit::Second => Duration::from_seconds_u(q as u128), - TimeUnit::Millisecond => Duration::from_milliseconds_u(q as u128), - TimeUnit::Microsecond => Duration::from_microseconds_u(q as u128), - TimeUnit::Nanosecond => Duration::from_nanoseconds_u(q as u128), - } - } - } - - impl Mul for $type { - type Output = Duration; - fn mul(self, q: TimeUnit) -> Duration { - match q { - TimeUnit::Century => Duration::from_days_u( - self as u128 * DAYS_PER_CENTURY_U as u128, - ), - TimeUnit::Day => Duration::from_days_u(self as u128), - TimeUnit::Hour => Duration::from_hours_u(self as u128), - TimeUnit::Minute => Duration::from_minutes_u(self as u128), - TimeUnit::Second => Duration::from_seconds_u(self as u128), - TimeUnit::Millisecond => Duration::from_milliseconds_u(self as u128), - TimeUnit::Microsecond => Duration::from_microseconds_u(self as u128), - TimeUnit::Nanosecond => Duration::from_nanoseconds_u(self as u128), - } - } - } - - impl Mul<$type> for Duration { - type Output = Duration; - fn mul(self, q: $type) -> Duration { - Self::from_nanoseconds_i(self.total_ns() * q as i128) - - } - } - - impl Div<$type> for Duration { - type Output = Duration; - fn div(self, q: $type) -> Duration { - Self::from_nanoseconds_i(self.total_ns() / q as i128) - - } - } - - impl Mul for $type { - type Output = Duration; - fn mul(self, q: Duration) -> Duration { - Duration::from_nanoseconds_i(q.total_ns() * self as i128) - - } - } - - impl TimeUnitHelper for $type {} - }; -} - macro_rules! impl_ops_i { ($type:ident) => { impl Mul<$type> for TimeUnit { @@ -183,15 +128,15 @@ macro_rules! impl_ops_i { fn mul(self, q: $type) -> Duration { match self { TimeUnit::Century => { - Duration::from_days_i(q as i128 * DAYS_PER_CENTURY_U as i128) + Duration::from_days_i(i128::from(q) * DAYS_PER_CENTURY_U as i128) } - TimeUnit::Day => Duration::from_days_i(q as i128), - TimeUnit::Hour => Duration::from_hours_i(q as i128), - TimeUnit::Minute => Duration::from_minutes_i(q as i128), - TimeUnit::Second => Duration::from_seconds_i(q as i128), - TimeUnit::Millisecond => Duration::from_milliseconds_i(q as i128), - TimeUnit::Microsecond => Duration::from_microseconds_i(q as i128), - TimeUnit::Nanosecond => Duration::from_nanoseconds_i(q as i128), + TimeUnit::Day => Duration::from_days_i(i128::from(q)), + TimeUnit::Hour => Duration::from_hours_i(i128::from(q)), + TimeUnit::Minute => Duration::from_minutes_i(i128::from(q)), + TimeUnit::Second => Duration::from_seconds_i(i128::from(q)), + TimeUnit::Millisecond => Duration::from_milliseconds_i(i128::from(q)), + TimeUnit::Microsecond => Duration::from_microseconds_i(i128::from(q)), + TimeUnit::Nanosecond => Duration::from_nanoseconds_i(i128::from(q)), } } } @@ -201,15 +146,15 @@ macro_rules! impl_ops_i { fn mul(self, q: TimeUnit) -> Duration { match q { TimeUnit::Century => Duration::from_days_i( - self as i128 * DAYS_PER_CENTURY_U as i128, + i128::from(self) * i128::from(DAYS_PER_CENTURY_U), ), - TimeUnit::Day => Duration::from_days_i(self as i128), - TimeUnit::Hour => Duration::from_hours_i(self as i128), - TimeUnit::Minute => Duration::from_minutes_i(self as i128), - TimeUnit::Second => Duration::from_seconds_i(self as i128), - TimeUnit::Millisecond => Duration::from_milliseconds_i(self as i128), - TimeUnit::Microsecond => Duration::from_microseconds_i(self as i128), - TimeUnit::Nanosecond => Duration::from_nanoseconds_i(self as i128), + TimeUnit::Day => Duration::from_days_i(i128::from(self)), + TimeUnit::Hour => Duration::from_hours_i(i128::from(self)), + TimeUnit::Minute => Duration::from_minutes_i(i128::from(self)), + TimeUnit::Second => Duration::from_seconds_i(i128::from(self)), + TimeUnit::Millisecond => Duration::from_milliseconds_i(i128::from(self)), + TimeUnit::Microsecond => Duration::from_microseconds_i(i128::from(self)), + TimeUnit::Nanosecond => Duration::from_nanoseconds_i(i128::from(self)), } } } @@ -217,7 +162,7 @@ macro_rules! impl_ops_i { impl Mul<$type> for Duration { type Output = Duration; fn mul(self, q: $type) -> Duration { - Self::from_nanoseconds_i(self.total_ns() * q as i128) + Self::from_nanoseconds_i(self.total_ns() * i128::from(q)) } } @@ -225,7 +170,7 @@ macro_rules! impl_ops_i { impl Div<$type> for Duration { type Output = Duration; fn div(self, q: $type) -> Duration { - Self::from_nanoseconds_i(self.total_ns() / q as i128) + Self::from_nanoseconds_i(self.total_ns() / i128::from(q)) } } @@ -233,7 +178,7 @@ macro_rules! impl_ops_i { impl Mul for $type { type Output = Duration; fn mul(self, q: Duration) -> Duration { - Duration::from_nanoseconds_i(q.total_ns() * self as i128) + Duration::from_nanoseconds_i(q.total_ns() * i128::from(self)) } } @@ -242,26 +187,26 @@ macro_rules! impl_ops_i { } impl Duration { - pub fn new(ns : u64, centuries : i16) -> Self { + pub fn new(centuries : i16, ns : u64) -> Self { let mut out = Duration { ns, centuries }; out.normalize(); out } pub fn total_ns(self) -> i128 { - self.centuries as i128 * NS_PER_CENTURY_U as i128 + self.ns as i128 + i128::from(self.centuries) * i128::from(NS_PER_CENTURY_U) + i128::from(self.ns) } fn normalize(&mut self) { - if self.ns > NS_PER_CENTURY_U as u64 { - let carry = self.ns / NS_PER_CENTURY_U as u64; + if self.ns > NS_PER_CENTURY_U { + let carry = self.ns / NS_PER_CENTURY_U ; self.centuries = self.centuries.saturating_add(carry as i16); - self.ns %= NS_PER_CENTURY_U as u64; + self.ns %= NS_PER_CENTURY_U; } } pub fn from_value_f(mut value : f64, century_divider : u64, ns_multiplier : u64) -> Self { - let centuries = (value.div_euclid(century_divider as f64)) as i16; + let centuries = value.div_euclid(century_divider as f64) as i16; value = value.rem_euclid(century_divider as f64); // Risks : Overflow, loss of precision, unexpected roundings @@ -271,135 +216,86 @@ impl Duration { } } pub fn from_days_f(days: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_U; - let ns_multiplier = SECONDS_PER_DAY_U * 1e9 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U); + let ns_multiplier = u64::from(SECONDS_PER_DAY_U) * 10u64.pow(9); Self::from_value_f(days, century_divider, ns_multiplier) } pub fn from_hours_f(hours: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U; - let ns_multiplier = SECONDS_PER_HOUR_U * 1e9 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U); + let ns_multiplier = u64::from(SECONDS_PER_HOUR_U) * 10u64.pow(9); Self::from_value_f(hours, century_divider, ns_multiplier) } pub fn from_minutes_f(minutes: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U; - let ns_multiplier = SECONDS_PER_MINUTE_U * 1e9 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U); + let ns_multiplier = u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(9); Self::from_value_f(minutes, century_divider, ns_multiplier) } pub fn from_seconds_f(seconds: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U; - let ns_multiplier = 1e9 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U); + let ns_multiplier = 10u64.pow(9); Self::from_value_f(seconds, century_divider, ns_multiplier) } pub fn from_milliseconds_f(ms: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e3 as u64; - let ns_multiplier = 1e6 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(3); + let ns_multiplier = 10u64.pow(6); Self::from_value_f(ms, century_divider, ns_multiplier) } pub fn from_microseconds_f(us: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e6 as u64; - let ns_multiplier = 1e3 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(6); + let ns_multiplier = 10u64.pow(3); Self::from_value_f(us, century_divider, ns_multiplier) } pub fn from_nanoseconds_f(ns: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e9 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(9); let ns_multiplier = 1; Self::from_value_f(ns, century_divider, ns_multiplier) } - - pub fn from_value_u(mut value : u128, century_divider : u64, ns_multiplier : u64) -> Self { - let centuries = (value.div_euclid(century_divider as u128)) as i16; - value = value.rem_euclid(century_divider as u128); - - // Risks : Overflow, loss of precision, unexpected roundings - let ns = (value * ns_multiplier as u128) as u64; - Self { - ns, centuries - } - } - pub fn from_days_u(days: u128) -> Self { - let century_divider = DAYS_PER_CENTURY_U; - let ns_multiplier = SECONDS_PER_DAY_U * 1e9 as u64; - Self::from_value_u(days, century_divider, ns_multiplier) - } - pub fn from_hours_u(hours: u128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U; - let ns_multiplier = SECONDS_PER_HOUR_U * 1e9 as u64; - Self::from_value_u(hours, century_divider, ns_multiplier) - } - pub fn from_minutes_u(minutes: u128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U; - let ns_multiplier = SECONDS_PER_MINUTE_U * 1e9 as u64; - Self::from_value_u(minutes, century_divider, ns_multiplier) - } - pub fn from_seconds_u(seconds: u128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U; - let ns_multiplier = 1e9 as u64; - Self::from_value_u(seconds, century_divider, ns_multiplier) - } - pub fn from_milliseconds_u(ms: u128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e3 as u64; - let ns_multiplier = 1e6 as u64; - Self::from_value_u(ms, century_divider, ns_multiplier) - } - pub fn from_microseconds_u(us: u128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e6 as u64; - let ns_multiplier = 1e3 as u64; - Self::from_value_u(us, century_divider, ns_multiplier) - } - pub fn from_nanoseconds_u(ns: u128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e9 as u64; - let ns_multiplier = 1 as u64; - Self::from_value_u(ns, century_divider, ns_multiplier) - } - - - pub fn from_value_i(mut value : i128, century_divider : u64, ns_multiplier : u64) -> Self { - let centuries = (value.div_euclid(century_divider as i128)) as i16; - value = value.rem_euclid(century_divider as i128); + let centuries = (value.div_euclid(i128::from(century_divider))) as i16; + value = value.rem_euclid(i128::from(century_divider)); // Risks : Overflow, loss of precision, unexpected roundings - let ns = (value * ns_multiplier as i128) as u64; + let ns = (value * i128::from(ns_multiplier)) as u64; Self { ns, centuries } } pub fn from_days_i(days: i128) -> Self { - let century_divider = DAYS_PER_CENTURY_U; - let ns_multiplier = SECONDS_PER_DAY_U * 1e9 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U); + let ns_multiplier = u64::from(SECONDS_PER_DAY_U) * 10u64.pow(9); Self::from_value_i(days, century_divider, ns_multiplier) } pub fn from_hours_i(hours: i128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U; - let ns_multiplier = SECONDS_PER_HOUR_U * 1e9 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U); + let ns_multiplier = u64::from(SECONDS_PER_HOUR_U) * 10u64.pow(9); Self::from_value_i(hours, century_divider, ns_multiplier) } pub fn from_minutes_i(minutes: i128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U; - let ns_multiplier = SECONDS_PER_MINUTE_U * 1e9 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U); + let ns_multiplier = u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(9); Self::from_value_i(minutes, century_divider, ns_multiplier) } pub fn from_seconds_i(seconds: i128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U; - let ns_multiplier = 1e9 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U); + let ns_multiplier = 10u64.pow(9); Self::from_value_i(seconds, century_divider, ns_multiplier) } pub fn from_milliseconds_i(ms: i128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e3 as u64; - let ns_multiplier = 1e6 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(3); + let ns_multiplier = 10u64.pow(6); Self::from_value_i(ms, century_divider, ns_multiplier) } pub fn from_microseconds_i(us: i128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e6 as u64; - let ns_multiplier = 1e3 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(6); + let ns_multiplier = 10u64.pow(3); Self::from_value_i(us, century_divider, ns_multiplier) } pub fn from_nanoseconds_i(ns: i128) -> Self { - let century_divider = DAYS_PER_CENTURY_U * HOURS_PER_DAY_U * MINUTES_PER_HOUR_U * SECONDS_PER_MINUTE_U * 1e9 as u64; - let ns_multiplier = 1 as u64; + let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(9); + let ns_multiplier = 1; Self::from_value_i(ns, century_divider, ns_multiplier) } @@ -421,7 +317,7 @@ impl Duration { /// Returns this duration in seconds f64. /// For high fidelity comparisons, it is recommended to keep using the Duration structure. pub fn in_seconds(&self) -> f64 { - (self.ns as f64 / 1e9) + (self.centuries as i64 * DAYS_PER_CENTURY_U as i64 * SECONDS_PER_DAY_U as i64) as f64 + (self.ns as f64 / 1e9) + (i128::from(self.centuries) * i128::from(DAYS_PER_CENTURY_U) * i128::from(SECONDS_PER_DAY_U)) as f64 } /// Returns the value of this duration in the requested unit. @@ -439,7 +335,7 @@ impl Duration { pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64, u64, u64) { - let total_ns : i128 = self.centuries as i128 * NS_PER_CENTURY_U as i128 + self.ns as i128; + let total_ns : i128 = i128::from(self.centuries) * i128::from(NS_PER_CENTURY_U) + i128::from(self.ns); let sign = total_ns.signum() as i8; let mut ns_left = total_ns.abs() as u64; @@ -448,29 +344,29 @@ impl Duration { let centuries = ns_left / NS_PER_CENTURY_U; - let years = ns_left / (1e9 as u64 * SECONDS_PER_DAY_U as u64 * 365); - ns_left %= 1e9 as u64 * SECONDS_PER_DAY_U as u64 * 365; + let years = ns_left / (10u64.pow(9) * u64::from(SECONDS_PER_DAY_U) * 365); + ns_left %= 10u64.pow(9) * u64::from(SECONDS_PER_DAY_U) * 365; - let days = ns_left / (1e9 as u64 * SECONDS_PER_DAY_U as u64); - ns_left %= 1e9 as u64 * SECONDS_PER_DAY_U as u64; + let days = ns_left / (10u64.pow(9) * u64::from(SECONDS_PER_DAY_U)); + ns_left %= 10u64.pow(9) * u64::from(SECONDS_PER_DAY_U) ; - let hours = ns_left / (1e9 as u64 * SECONDS_PER_HOUR_U as u64); - ns_left %= 1e9 as u64 * SECONDS_PER_HOUR_U as u64; + let hours = ns_left / (10u64.pow(9) * u64::from(SECONDS_PER_HOUR_U)); + ns_left %= 10u64.pow(9) * u64::from(SECONDS_PER_HOUR_U); - let minutes = ns_left / (1e9 as u64 * SECONDS_PER_MINUTE_U as u64); - ns_left %= 1e9 as u64 * SECONDS_PER_MINUTE_U as u64; + let minutes = ns_left / (10u64.pow(9) * u64::from(SECONDS_PER_MINUTE_U)); + ns_left %= 10u64.pow(9) * u64::from(SECONDS_PER_MINUTE_U); - let seconds = ns_left / (1e9 as u64); - ns_left %= 1e9 as u64; + let seconds = ns_left / 10u64.pow(9); + ns_left %= 10u64.pow(9); - let ms = ns_left / (1e6 as u64); - ns_left %= 1e6 as u64; + let ms = ns_left / 10u64.pow(6); + ns_left %= 10u64.pow(6); - let us = ns_left / (1e3 as u64); - ns_left %= 1e3 as u64; + let us = ns_left / 10u64.pow(3); + ns_left %= 10u64.pow(3); - let ns = dbg!(ns_left); + let ns = ns_left; (sign, centuries, years, days, hours, minutes, seconds, ms, us, ns) } @@ -558,11 +454,11 @@ impl Add for Duration { // Usage of u128 is to avoid possibility of overflow and type-system carry from u64 // may switch approach when carrying_add is stabilized - let mut total_ns: u128 = self.ns as u128 + rhs.ns as u128; + let mut total_ns: u128 = u128::from(self.ns) + u128::from(rhs.ns); let mut century_carry = 0; - if total_ns > NS_PER_CENTURY_U as u128 { - century_carry = total_ns / NS_PER_CENTURY_U as u128; - total_ns %= NS_PER_CENTURY_U as u128; + if total_ns > u128::from(NS_PER_CENTURY_U) { + century_carry = total_ns / u128::from(NS_PER_CENTURY_U); + total_ns %= u128::from(NS_PER_CENTURY_U); // total_ns is now guaranteed to be less than u64_max } @@ -585,11 +481,11 @@ impl Sub for Duration { type Output = Duration; fn sub(self, rhs: Self) -> Duration { - let mut total_ns: i128 = self.ns as i128 - rhs.ns as i128; + let mut total_ns: i128 = i128::from(self.ns) - i128::from(rhs.ns); let mut century_borrow = 0; if total_ns < 0 { - century_borrow = (-total_ns / NS_PER_CENTURY_U as i128)+1; - total_ns += century_borrow * NS_PER_CENTURY_U as i128; + century_borrow = (-total_ns / i128::from(NS_PER_CENTURY_U))+1; + total_ns += century_borrow * i128::from(NS_PER_CENTURY_U); } @@ -795,11 +691,11 @@ impl Sub for TimeUnit { impl TimeUnit { pub fn in_seconds(self) -> f64 { match self { - TimeUnit::Century => DAYS_PER_CENTURY_U as f64 * SECONDS_PER_DAY_U as f64, - TimeUnit::Day => SECONDS_PER_DAY_U as f64, - TimeUnit::Hour => SECONDS_PER_HOUR_U as f64, - TimeUnit::Minute => SECONDS_PER_MINUTE_U as f64, - TimeUnit::Second => ONE as f64, + TimeUnit::Century => DAYS_PER_CENTURY_F * SECONDS_PER_DAY_F, + TimeUnit::Day => SECONDS_PER_DAY_F, + TimeUnit::Hour => SECONDS_PER_HOUR_F, + TimeUnit::Minute => SECONDS_PER_MINUTE_F, + TimeUnit::Second => ONE_F, TimeUnit::Millisecond => 1e-3, TimeUnit::Microsecond => 1e-6, TimeUnit::Nanosecond => 1e-9, @@ -818,20 +714,21 @@ impl TimeUnit { -impl_ops_u!(u8); +impl_ops_i!(u8); impl_ops_i!(i8); -impl_ops_u!(u16); +impl_ops_i!(u16); impl_ops_i!(i16); -impl_ops_u!(u32); +impl_ops_i!(u32); impl_ops_i!(i32); -impl_ops_u!(u64); +impl_ops_i!(u64); impl_ops_i!(i64); -impl_ops_u!(u128); -impl_ops_i!(i128); +// Removed u128 and i128 as some operators couldn’t be made safe and there is no real need for them +//impl_ops_u!(u128); +//impl_ops_i!(i128); impl_ops_f!(f32); impl_ops_f!(f64); From 1dd04844766c458ea959643df21bbb31720fb404 Mon Sep 17 00:00:00 2001 From: Chris de Claverie Date: Mon, 10 Jan 2022 18:19:23 +0100 Subject: [PATCH 07/17] simplify float operators --- src/duration.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 285c9b12..58f4d9b4 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -205,49 +205,49 @@ impl Duration { } } - pub fn from_value_f(mut value : f64, century_divider : u64, ns_multiplier : u64) -> Self { - let centuries = value.div_euclid(century_divider as f64) as i16; - value = value.rem_euclid(century_divider as f64); + pub fn from_value_f(mut value : f64, century_divider : f64, ns_multiplier : f64) -> Self { + let centuries = value.div_euclid(century_divider) as i16; + value = value.rem_euclid(century_divider); // Risks : Overflow, loss of precision, unexpected roundings - let ns = (value * ns_multiplier as f64).round() as u64; + let ns = (value * ns_multiplier).round() as u64; Self { ns, centuries } } pub fn from_days_f(days: f64) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U); - let ns_multiplier = u64::from(SECONDS_PER_DAY_U) * 10u64.pow(9); + let century_divider = DAYS_PER_CENTURY_F; + let ns_multiplier = SECONDS_PER_DAY_F * 1e9; Self::from_value_f(days, century_divider, ns_multiplier) } pub fn from_hours_f(hours: f64) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U); - let ns_multiplier = u64::from(SECONDS_PER_HOUR_U) * 10u64.pow(9); + let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F; + let ns_multiplier = SECONDS_PER_HOUR_F * 1e9; Self::from_value_f(hours, century_divider, ns_multiplier) } pub fn from_minutes_f(minutes: f64) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U); - let ns_multiplier = u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(9); + let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F * MINUTES_PER_HOUR_F; + let ns_multiplier = SECONDS_PER_MINUTE_F * 1e9; Self::from_value_f(minutes, century_divider, ns_multiplier) } pub fn from_seconds_f(seconds: f64) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U); - let ns_multiplier = 10u64.pow(9); + let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F * MINUTES_PER_HOUR_F * SECONDS_PER_MINUTE_F; + let ns_multiplier = 1e9; Self::from_value_f(seconds, century_divider, ns_multiplier) } pub fn from_milliseconds_f(ms: f64) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(3); - let ns_multiplier = 10u64.pow(6); + let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F * MINUTES_PER_HOUR_F * SECONDS_PER_MINUTE_F * 1e3; + let ns_multiplier = 1e6; Self::from_value_f(ms, century_divider, ns_multiplier) } pub fn from_microseconds_f(us: f64) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(6); - let ns_multiplier = 10u64.pow(3); + let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F * MINUTES_PER_HOUR_F * SECONDS_PER_MINUTE_F * 1e6; + let ns_multiplier = 1e3; Self::from_value_f(us, century_divider, ns_multiplier) } pub fn from_nanoseconds_f(ns: f64) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(9); - let ns_multiplier = 1; + let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F * MINUTES_PER_HOUR_F * SECONDS_PER_MINUTE_F * 1e9; + let ns_multiplier = 1.0; Self::from_value_f(ns, century_divider, ns_multiplier) } From 4ca4de8a788567be8761af4bc8634398ce47ac7b Mon Sep 17 00:00:00 2001 From: Chris de Claverie Date: Tue, 11 Jan 2022 14:03:55 +0100 Subject: [PATCH 08/17] remove centuries and years from Duration decomposition and Display impl --- src/duration.rs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 58f4d9b4..a740e447 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -333,21 +333,13 @@ impl Duration { - pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64, u64, u64) { + pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64) { let total_ns : i128 = i128::from(self.centuries) * i128::from(NS_PER_CENTURY_U) + i128::from(self.ns); let sign = total_ns.signum() as i8; let mut ns_left = total_ns.abs() as u64; - - let centuries = ns_left / NS_PER_CENTURY_U; - - - let years = ns_left / (10u64.pow(9) * u64::from(SECONDS_PER_DAY_U) * 365); - ns_left %= 10u64.pow(9) * u64::from(SECONDS_PER_DAY_U) * 365; - - let days = ns_left / (10u64.pow(9) * u64::from(SECONDS_PER_DAY_U)); ns_left %= 10u64.pow(9) * u64::from(SECONDS_PER_DAY_U) ; @@ -368,7 +360,7 @@ impl Duration { let ns = ns_left; - (sign, centuries, years, days, hours, minutes, seconds, ms, us, ns) + (sign, days, hours, minutes, seconds, ms, us, ns) } } @@ -376,10 +368,10 @@ impl fmt::Display for Duration { // Prints this duration with automatic selection of the highest and sub-second unit fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let (sign, centuries, years, days, hours, minutes, seconds, milli, us, nano) = self.decompose(); + let (sign, days, hours, minutes, seconds, milli, us, nano) = self.decompose(); - let values = [centuries, years, days, hours, minutes, seconds, milli, us, nano]; - let names = ["centuries", "years", "days", "h", "min", "s", "ms", "us", "ns"]; + let values = [days, hours, minutes, seconds, milli, us, nano]; + let names = ["days", "h", "min", "s", "ms", "us", "ns"]; let print_all = false; @@ -855,7 +847,7 @@ fn duration_print() { let now = TimeUnit::Nanosecond * 1286495254000000203_u128; assert_eq!( format!("{}", now).trim(), - "40 years 289 days 23 h 47 min 34 s 0 ms 0 us 203 ns" + "14889 days 23 h 47 min 34 s 0 ms 0 us 203 ns" ); let arbitrary = 14889.days() @@ -866,7 +858,7 @@ fn duration_print() { + 123.nanoseconds(); assert_eq!( format!("{}", arbitrary).trim(), - "40 years 289 days 23 h 47 min 34 s 0 ms 0 us 123 ns" + "14889 days 23 h 47 min 34 s 0 ms 0 us 123 ns" ); // Test fractional From a4f0c8e97d742fe74e32510abd0b54508e13357c Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Sun, 13 Feb 2022 09:35:41 -0700 Subject: [PATCH 09/17] Operations are still broken Signed-off-by: Christopher Rabotin --- Cargo.toml | 3 +- src/duration.rs | 948 ++++++++++++++++++++++++++---------------------- src/epoch.rs | 135 ++++--- src/lib.rs | 2 + 4 files changed, 590 insertions(+), 498 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 054c6185..2be51461 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ rand_distr = "0.4" regex = "1.3" serde = "1.0" serde_derive = "1.0" +twofloat = "0.4.1" [dev-dependencies] serde_derive = "1.0" @@ -25,4 +26,4 @@ criterion = "0.3" [[bench]] name = "bench_epoch" -harness = false +harness = false \ No newline at end of file diff --git a/src/duration.rs b/src/duration.rs index a740e447..cdb12387 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -1,46 +1,207 @@ +extern crate divrem; extern crate regex; extern crate serde; extern crate serde_derive; -extern crate divrem; +extern crate twofloat; +use self::divrem::DivRemEuclid; use self::regex::Regex; use self::serde::{de, Deserialize, Deserializer}; -use self::divrem::{DivEuclid, DivRemEuclid}; -use crate::{Errors, SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE}; +#[allow(unused_imports)] +use self::twofloat::TwoFloat; +use crate::{ + Errors, DAYS_PER_CENTURY, SECONDS_PER_CENTURY, SECONDS_PER_DAY, SECONDS_PER_HOUR, + SECONDS_PER_MINUTE, +}; use std::cmp::Ordering; +use std::convert::{TryFrom, TryInto}; use std::fmt; use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use std::str::FromStr; +const DAYS_PER_CENTURY_U: u128 = 36_525; +const DAYS_PER_CENTURY_U64: u64 = 36_525; +const HOURS_PER_CENTURY: f64 = 24.0 * DAYS_PER_CENTURY; +const MINUTES_PER_CENTURY: f64 = 60.0 * HOURS_PER_CENTURY; +const NANOSECONDS_PER_MICROSECOND: u64 = 1_000; +const NANOSECONDS_PER_MILLISECOND: u64 = 1_000 * NANOSECONDS_PER_MICROSECOND; +const NANOSECONDS_PER_SECOND: u64 = 1_000 * NANOSECONDS_PER_MILLISECOND; +const NANOSECONDS_PER_MINUTE: u64 = 60 * NANOSECONDS_PER_SECOND; +const NANOSECONDS_PER_HOUR: u64 = 60 * NANOSECONDS_PER_MINUTE; +const NANOSECONDS_PER_DAY: u64 = 24 * NANOSECONDS_PER_HOUR; +const NANOSECONDS_PER_CENTURY: u64 = DAYS_PER_CENTURY_U64 * NANOSECONDS_PER_DAY; + +/// Defines generally usable durations for nanosecond precision valid for 32,768 centuries in either direction, and only on 80 bits / 10 octets. +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub struct Duration { + pub centuries: i16, + pub nanoseconds: u64, +} +impl Duration { + fn normalize(&mut self) { + if self.nanoseconds > NANOSECONDS_PER_CENTURY { + let (centuries, nanoseconds) = self.nanoseconds.div_rem_euclid(NANOSECONDS_PER_CENTURY); + self.nanoseconds = nanoseconds; + // If the quotient is larger than an i16, set the centuries to the max. + // Similarly, we will ensure with a saturating add that the centuries always fits in an i16 + match i16::try_from(centuries) { + Ok(centuries) => self.centuries = self.centuries.saturating_add(centuries), + Err(_) => self.centuries = i16::MAX, + } + } + } + + /// Converts the total nanoseconds as i128 into this Duration (saving 48 bits) + pub fn from_total_nanoseconds(nanos: i128) -> Self { + if nanos < 0 { + let mut centuries = -1; + let mut nanoseconds: u64; + let truncated; + let usable_nanos = if nanos == i128::MIN { + truncated = true; + nanos + 1 + } else { + truncated = false; + nanos + }; + if usable_nanos.abs() >= u64::MAX.into() { + centuries -= 1; + nanoseconds = (usable_nanos.abs() - i128::from(NANOSECONDS_PER_CENTURY)) as u64; + } else { + // We know it fits! + nanoseconds = usable_nanos.abs() as u64; + } + if truncated { + nanoseconds -= 1; + } + let mut me = Self { + centuries, + nanoseconds, + }; + me.normalize(); + me + } else { + let mut centuries = 0; + let nanoseconds: u64; + // We must check that we fit in a u64, or the normalize function cannot work! + if nanos >= u64::MAX.into() { + centuries += 1; + nanoseconds = (nanos - i128::from(NANOSECONDS_PER_CENTURY)) as u64; + } else { + // We know it fits! + nanoseconds = nanos as u64; + } + let mut me = Self { + centuries, + nanoseconds, + }; + me.normalize(); + me + } + } -const SECONDS_PER_MINUTE_U: u16 = 60; -const MINUTES_PER_HOUR_U: u16 = 60; -const HOURS_PER_DAY_U: u16 = 24; -const SECONDS_PER_HOUR_U: u16 = SECONDS_PER_MINUTE_U * MINUTES_PER_HOUR_U; -const SECONDS_PER_DAY_U: u32 = SECONDS_PER_HOUR_U as u32 * HOURS_PER_DAY_U as u32; -const DAYS_PER_CENTURY_U: u16 = 36_525; -const NS_PER_DAY_U: u64 = 10u64.pow(9) * SECONDS_PER_DAY_U as u64; -const NS_PER_CENTURY_U: u64 = DAYS_PER_CENTURY_U as u64 * NS_PER_DAY_U as u64; -const ONE: u64 = 1_u64; + /// Returns the total nanoseconds in a signed 128 bit integer + pub fn total_nanoseconds(self) -> i128 { + i128::from(self.centuries) * i128::from(NANOSECONDS_PER_CENTURY) + + i128::from(self.nanoseconds) + } -const SECONDS_PER_MINUTE_F: f64 = 60.0; -const MINUTES_PER_HOUR_F: f64 = 60.0; -const HOURS_PER_DAY_F: f64 = 24.0; -const SECONDS_PER_HOUR_F: f64 = SECONDS_PER_MINUTE_F * MINUTES_PER_HOUR_F; -const SECONDS_PER_DAY_F: f64 = SECONDS_PER_HOUR_F * HOURS_PER_DAY_F; -const DAYS_PER_CENTURY_F: f64 = 36_525.0; -const NS_PER_DAY_F: f64 = 1e9 * SECONDS_PER_DAY_F; -const NS_PER_CENTURY_F: f64 = DAYS_PER_CENTURY_F * NS_PER_DAY_F; -const ONE_F: f64 = 1.0; + /// Creates a new duration from the provided unit + #[must_use] + pub fn from_f64(value: f64, unit: TimeUnit) -> Self { + unit * value + } + /// Returns this duration in f64 in the provided unit. + /// For high fidelity comparisons, it is recommended to keep using the Duration structure. + pub fn in_unit_f64(&self, unit: TimeUnit) -> f64 { + f64::from(self.in_unit(unit)) + } -/// Defines generally usable durations for high precision math with Epoch -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] -pub struct Duration { - ns : u64, // 1 century is about 3.1e18 ns, and max value of u64 is about 1e19.26 - centuries : i16 // +- 9.22e18 centuries is the possible range for a Duration - // Reducing the range could be a good tradeoff for a lowerm memory footprint + /// Returns this duration in seconds f64. + /// For high fidelity comparisons, it is recommended to keep using the Duration structure. + pub fn in_seconds(&self) -> f64 { + // Compute the seconds and nanoseconds that we know this fits on a 64bit float + let (seconds_u64, nanoseconds_u64) = + self.nanoseconds.div_rem_euclid(NANOSECONDS_PER_CENTURY); + f64::from(self.centuries) * SECONDS_PER_DAY * DAYS_PER_CENTURY + + (seconds_u64 as f64) + + (nanoseconds_u64 as f64) * 1e-9 + } + + /// Returns the value of this duration in the requested unit. + #[must_use] + pub fn in_unit(&self, unit: TimeUnit) -> f64 { + self.in_seconds() * unit.from_seconds() + } + + /// Returns the absolute value of this duration + #[must_use] + pub fn abs(&self) -> Self { + if self.centuries.is_negative() { + -*self + } else { + *self + } + } + + /// Builds a new duration from the number of centuries and the number of nanoseconds + #[must_use] + pub fn new(centuries: i16, nanoseconds: u64) -> Self { + let mut out = Self { + centuries, + nanoseconds, + }; + out.normalize(); + out + } + + pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64) { + let total_ns = self.total_nanoseconds(); + + let sign = total_ns.signum() as i8; + let ns_left = total_ns.abs(); + + let (days, ns_left) = ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_DAY)); + let (hours, ns_left) = ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_HOUR)); + let (minutes, ns_left) = ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_MINUTE)); + let (seconds, ns_left) = ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_SECOND)); + let (milliseconds, ns_left) = + ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_MILLISECOND)); + let (microseconds, ns_left) = + ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_MICROSECOND)); + + // Everything should fit in the expected types now + ( + sign, + days.try_into().unwrap(), + hours.try_into().unwrap(), + minutes.try_into().unwrap(), + seconds.try_into().unwrap(), + milliseconds.try_into().unwrap(), + microseconds.try_into().unwrap(), + ns_left.try_into().unwrap(), + ) + } + + /// Maximum duration that can be represented + pub const MAX: Self = Self { + centuries: i16::MAX, + nanoseconds: u64::MAX, + }; + + /// Minimum duration that can be represented + pub const MIN: Self = Self { + centuries: i16::MIN, + nanoseconds: u64::MAX, + }; + + /// Smallest duration that can be represented + pub const EPSILON: Self = Self { + centuries: 0, + nanoseconds: 1, + }; } impl<'de> Deserialize<'de> for Duration { @@ -53,24 +214,146 @@ impl<'de> Deserialize<'de> for Duration { } } - - -macro_rules! impl_ops_f { +macro_rules! impl_ops_for_type { ($type:ident) => { impl Mul<$type> for TimeUnit { type Output = Duration; + fn mul(self, q: $type) -> Duration { match self { - TimeUnit::Century => { - Duration::from_days_f(f64::from(q) * DAYS_PER_CENTURY_F) + TimeUnit::Century => self * q * DAYS_PER_CENTURY_U, + TimeUnit::Day => { + // The centuries will be a round number here so the `as` conversion should work. + let centuries_typed = q.div_euclid(DAYS_PER_CENTURY as $type); + let centuries = if centuries_typed > (i16::MAX as $type) { + return Duration::MAX; + } else if centuries_typed < (i16::MIN as $type) { + return Duration::MIN; + } else { + centuries_typed as i16 + }; + + // rem_euclid returns the nonnegative number, so we can cast that directly into u64 + let nanoseconds = + q.rem_euclid(DAYS_PER_CENTURY as $type) as u64 * NANOSECONDS_PER_DAY; + Duration { + centuries, + nanoseconds, + } + } + TimeUnit::Hour => { + // The centuries will be a round number here so the `as` conversion should work. + let centuries_typed = q.div_euclid(HOURS_PER_CENTURY as $type); + let centuries = if centuries_typed > (i16::MAX as $type) { + return Duration::MAX; + } else if centuries_typed < (i16::MIN as $type) { + return Duration::MIN; + } else { + centuries_typed as i16 + }; + + // rem_euclid returns the nonnegative number, so we can cast that directly into u64 + let nanoseconds = + q.rem_euclid(HOURS_PER_CENTURY as $type) as u64 * NANOSECONDS_PER_HOUR; + Duration { + centuries, + nanoseconds, + } + } + TimeUnit::Minute => { + // The centuries will be a round number here so the `as` conversion should work. + let centuries_typed = q.div_euclid(MINUTES_PER_CENTURY as $type); + let centuries = if centuries_typed > (i16::MAX as $type) { + return Duration::MAX; + } else if centuries_typed < (i16::MIN as $type) { + return Duration::MIN; + } else { + centuries_typed as i16 + }; + + // rem_euclid returns the nonnegative number, so we can cast that directly into u64 + let nanoseconds = q.rem_euclid(MINUTES_PER_CENTURY as $type) as u64 + * NANOSECONDS_PER_MINUTE; + Duration { + centuries, + nanoseconds, + } + } + TimeUnit::Second => { + // The centuries will be a round number here so the `as` conversion should work. + let centuries_typed = q.div_euclid(SECONDS_PER_CENTURY as $type); + let centuries = if centuries_typed > (i16::MAX as $type) { + return Duration::MAX; + } else if centuries_typed < (i16::MIN as $type) { + return Duration::MIN; + } else { + centuries_typed as i16 + }; + + // rem_euclid returns the nonnegative number, so we can cast that directly into u64 + let nanoseconds = q.rem_euclid(SECONDS_PER_CENTURY as $type) as u64 + * NANOSECONDS_PER_SECOND; + Duration { + centuries, + nanoseconds, + } + } + TimeUnit::Millisecond => { + // The centuries will be a round number here so the `as` conversion should work. + let centuries_typed = q.div_euclid((SECONDS_PER_CENTURY * 1e-3) as $type); + let centuries = if centuries_typed > (i16::MAX as $type) { + return Duration::MAX; + } else if centuries_typed < (i16::MIN as $type) { + return Duration::MIN; + } else { + centuries_typed as i16 + }; + + // rem_euclid returns the nonnegative number, so we can cast that directly into u64 + let nanoseconds = + q.rem_euclid(DAYS_PER_CENTURY as $type) as u64 * NANOSECONDS_PER_SECOND; + Duration { + centuries, + nanoseconds, + } + } + TimeUnit::Microsecond => { + // The centuries will be a round number here so the `as` conversion should work. + let centuries_typed = q.div_euclid((SECONDS_PER_CENTURY * 1e-6) as $type); + let centuries = if centuries_typed > (i16::MAX as $type) { + return Duration::MAX; + } else if centuries_typed < (i16::MIN as $type) { + return Duration::MIN; + } else { + centuries_typed as i16 + }; + + // rem_euclid returns the nonnegative number, so we can cast that directly into u64 + let nanoseconds = q.rem_euclid(DAYS_PER_CENTURY as $type) as u64 + * NANOSECONDS_PER_MICROSECOND; + Duration { + centuries, + nanoseconds, + } + } + TimeUnit::Nanosecond => { + // The centuries will be a round number here so the `as` conversion should work. + let centuries_typed = q.div_euclid((SECONDS_PER_CENTURY * 1e-9) as $type); + let centuries = if centuries_typed > (i16::MAX as $type) { + return Duration::MAX; + } else if centuries_typed < (i16::MIN as $type) { + return Duration::MIN; + } else { + centuries_typed as i16 + }; + + // rem_euclid returns the nonnegative number, so we can cast that directly into u64 + let nanoseconds = q.rem_euclid(DAYS_PER_CENTURY as $type) as u64; + Duration { + centuries, + nanoseconds, + } } - TimeUnit::Day => Duration::from_days_f(f64::from(q)), - TimeUnit::Hour => Duration::from_hours_f(f64::from(q)), - TimeUnit::Minute => Duration::from_minutes_f(f64::from(q)), - TimeUnit::Second => Duration::from_seconds_f(f64::from(q)), - TimeUnit::Millisecond => Duration::from_milliseconds_f(f64::from(q)), - TimeUnit::Microsecond => Duration::from_microseconds_f(f64::from(q)), - TimeUnit::Nanosecond => Duration::from_nanoseconds_f(f64::from(q)), } } } @@ -78,107 +361,80 @@ macro_rules! impl_ops_f { impl Mul for $type { type Output = Duration; fn mul(self, q: TimeUnit) -> Duration { - match q { - TimeUnit::Century => Duration::from_days_f( - f64::from(self) * DAYS_PER_CENTURY_F, - ), - TimeUnit::Day => Duration::from_days_f(f64::from(self)), - TimeUnit::Hour => Duration::from_hours_f(f64::from(self)), - TimeUnit::Minute => Duration::from_minutes_f(f64::from(self)), - TimeUnit::Second => Duration::from_seconds_f(f64::from(self)), - TimeUnit::Millisecond => Duration::from_milliseconds_f(f64::from(self)), - TimeUnit::Microsecond => Duration::from_microseconds_f(f64::from(self)), - TimeUnit::Nanosecond => Duration::from_nanoseconds_f(f64::from(self)), - } + // Apply the reflexive property + q * self } } impl Mul<$type> for Duration { type Output = Duration; - fn mul(self, q: $type) -> Duration { - Duration::from_seconds_f(self.in_seconds() * q as f64) - + fn mul(self, q: $type) -> Self::Output { + // Compute as nanoseconds to align with Duration, and divide as needed. + let mut as_duration = q * TimeUnit::Nanosecond; + let mut me = self; + me.centuries = me.centuries.saturating_mul(as_duration.centuries); + if me.centuries == i16::MAX { + return Self::Output::MAX; + } else if me.centuries == i16::MIN { + return Self::Output::MIN; + } + me.nanoseconds = me.nanoseconds.saturating_mul(as_duration.nanoseconds); + if me.nanoseconds == u64::MAX { + // Increment the centuries and decrease nanoseconds + as_duration.centuries += 1; + as_duration.nanoseconds -= NANOSECONDS_PER_CENTURY; + // And repeat + me.centuries = me.centuries.saturating_mul(as_duration.centuries); + if me.centuries == i16::MAX { + return Self::Output::MAX; + } else if me.centuries == i16::MIN { + return Self::Output::MIN; + } + me.nanoseconds = me.nanoseconds.saturating_mul(as_duration.nanoseconds); + } + me } } impl Div<$type> for Duration { type Output = Duration; - fn div(self, q: $type) -> Duration { - Duration::from_seconds_f(self.in_seconds() / q as f64) - - } - } - - impl Mul for $type { - type Output = Duration; - fn mul(self, q: Duration) -> Duration { - Duration::from_seconds_f(self as f64 * q.in_seconds()) - - } - } - - impl TimeUnitHelper for $type {} - }; -} - -macro_rules! impl_ops_i { - ($type:ident) => { - impl Mul<$type> for TimeUnit { - type Output = Duration; - fn mul(self, q: $type) -> Duration { - match self { - TimeUnit::Century => { - Duration::from_days_i(i128::from(q) * DAYS_PER_CENTURY_U as i128) + fn div(self, q: $type) -> Self::Output { + // Compute as nanoseconds to align with Duration, and divide as needed. + let mut as_duration = q * TimeUnit::Nanosecond; + let mut me = self; + if as_duration.centuries > 0 { + me.centuries = me.centuries.saturating_div(as_duration.centuries); + if me.centuries == i16::MAX { + return Self::Output::MAX; + } else if me.centuries == i16::MIN { + return Self::Output::MIN; } - TimeUnit::Day => Duration::from_days_i(i128::from(q)), - TimeUnit::Hour => Duration::from_hours_i(i128::from(q)), - TimeUnit::Minute => Duration::from_minutes_i(i128::from(q)), - TimeUnit::Second => Duration::from_seconds_i(i128::from(q)), - TimeUnit::Millisecond => Duration::from_milliseconds_i(i128::from(q)), - TimeUnit::Microsecond => Duration::from_microseconds_i(i128::from(q)), - TimeUnit::Nanosecond => Duration::from_nanoseconds_i(i128::from(q)), } - } - } - - impl Mul for $type { - type Output = Duration; - fn mul(self, q: TimeUnit) -> Duration { - match q { - TimeUnit::Century => Duration::from_days_i( - i128::from(self) * i128::from(DAYS_PER_CENTURY_U), - ), - TimeUnit::Day => Duration::from_days_i(i128::from(self)), - TimeUnit::Hour => Duration::from_hours_i(i128::from(self)), - TimeUnit::Minute => Duration::from_minutes_i(i128::from(self)), - TimeUnit::Second => Duration::from_seconds_i(i128::from(self)), - TimeUnit::Millisecond => Duration::from_milliseconds_i(i128::from(self)), - TimeUnit::Microsecond => Duration::from_microseconds_i(i128::from(self)), - TimeUnit::Nanosecond => Duration::from_nanoseconds_i(i128::from(self)), + if as_duration.nanoseconds > 0 { + me.nanoseconds = me.nanoseconds.saturating_div(as_duration.nanoseconds); + if me.nanoseconds == u64::MAX { + // Increment the centuries and decrease nanoseconds + as_duration.centuries += 1; + as_duration.nanoseconds -= NANOSECONDS_PER_CENTURY; + // And repeat + me.centuries = me.centuries.saturating_mul(as_duration.centuries); + if me.centuries == i16::MAX { + return Self::Output::MAX; + } else if me.centuries == i16::MIN { + return Self::Output::MIN; + } + me.nanoseconds = me.nanoseconds.saturating_mul(as_duration.nanoseconds); + } } - } - } - - impl Mul<$type> for Duration { - type Output = Duration; - fn mul(self, q: $type) -> Duration { - Self::from_nanoseconds_i(self.total_ns() * i128::from(q)) - - } - } - - impl Div<$type> for Duration { - type Output = Duration; - fn div(self, q: $type) -> Duration { - Self::from_nanoseconds_i(self.total_ns() / i128::from(q)) - + me } } impl Mul for $type { type Output = Duration; - fn mul(self, q: Duration) -> Duration { - Duration::from_nanoseconds_i(q.total_ns() * i128::from(self)) + fn mul(self, q: Self::Output) -> Self::Output { + // Apply the reflexive property + q * self } } @@ -186,229 +442,34 @@ macro_rules! impl_ops_i { }; } -impl Duration { - pub fn new(centuries : i16, ns : u64) -> Self { - let mut out = Duration { ns, centuries }; - out.normalize(); - out - } - - pub fn total_ns(self) -> i128 { - i128::from(self.centuries) * i128::from(NS_PER_CENTURY_U) + i128::from(self.ns) - } - - fn normalize(&mut self) { - if self.ns > NS_PER_CENTURY_U { - let carry = self.ns / NS_PER_CENTURY_U ; - self.centuries = self.centuries.saturating_add(carry as i16); - self.ns %= NS_PER_CENTURY_U; - } - } - - pub fn from_value_f(mut value : f64, century_divider : f64, ns_multiplier : f64) -> Self { - let centuries = value.div_euclid(century_divider) as i16; - value = value.rem_euclid(century_divider); - - // Risks : Overflow, loss of precision, unexpected roundings - let ns = (value * ns_multiplier).round() as u64; - Self { - ns, centuries - } - } - pub fn from_days_f(days: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_F; - let ns_multiplier = SECONDS_PER_DAY_F * 1e9; - Self::from_value_f(days, century_divider, ns_multiplier) - } - pub fn from_hours_f(hours: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F; - let ns_multiplier = SECONDS_PER_HOUR_F * 1e9; - Self::from_value_f(hours, century_divider, ns_multiplier) - } - pub fn from_minutes_f(minutes: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F * MINUTES_PER_HOUR_F; - let ns_multiplier = SECONDS_PER_MINUTE_F * 1e9; - Self::from_value_f(minutes, century_divider, ns_multiplier) - } - pub fn from_seconds_f(seconds: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F * MINUTES_PER_HOUR_F * SECONDS_PER_MINUTE_F; - let ns_multiplier = 1e9; - Self::from_value_f(seconds, century_divider, ns_multiplier) - } - pub fn from_milliseconds_f(ms: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F * MINUTES_PER_HOUR_F * SECONDS_PER_MINUTE_F * 1e3; - let ns_multiplier = 1e6; - Self::from_value_f(ms, century_divider, ns_multiplier) - } - pub fn from_microseconds_f(us: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F * MINUTES_PER_HOUR_F * SECONDS_PER_MINUTE_F * 1e6; - let ns_multiplier = 1e3; - Self::from_value_f(us, century_divider, ns_multiplier) - } - pub fn from_nanoseconds_f(ns: f64) -> Self { - let century_divider = DAYS_PER_CENTURY_F * HOURS_PER_DAY_F * MINUTES_PER_HOUR_F * SECONDS_PER_MINUTE_F * 1e9; - let ns_multiplier = 1.0; - Self::from_value_f(ns, century_divider, ns_multiplier) - } - - - - pub fn from_value_i(mut value : i128, century_divider : u64, ns_multiplier : u64) -> Self { - let centuries = (value.div_euclid(i128::from(century_divider))) as i16; - value = value.rem_euclid(i128::from(century_divider)); - - // Risks : Overflow, loss of precision, unexpected roundings - let ns = (value * i128::from(ns_multiplier)) as u64; - Self { - ns, centuries - } - } - pub fn from_days_i(days: i128) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U); - let ns_multiplier = u64::from(SECONDS_PER_DAY_U) * 10u64.pow(9); - Self::from_value_i(days, century_divider, ns_multiplier) - } - pub fn from_hours_i(hours: i128) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U); - let ns_multiplier = u64::from(SECONDS_PER_HOUR_U) * 10u64.pow(9); - Self::from_value_i(hours, century_divider, ns_multiplier) - } - pub fn from_minutes_i(minutes: i128) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U); - let ns_multiplier = u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(9); - Self::from_value_i(minutes, century_divider, ns_multiplier) - } - pub fn from_seconds_i(seconds: i128) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U); - let ns_multiplier = 10u64.pow(9); - Self::from_value_i(seconds, century_divider, ns_multiplier) - } - pub fn from_milliseconds_i(ms: i128) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(3); - let ns_multiplier = 10u64.pow(6); - Self::from_value_i(ms, century_divider, ns_multiplier) - } - pub fn from_microseconds_i(us: i128) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(6); - let ns_multiplier = 10u64.pow(3); - Self::from_value_i(us, century_divider, ns_multiplier) - } - pub fn from_nanoseconds_i(ns: i128) -> Self { - let century_divider = u64::from(DAYS_PER_CENTURY_U) * u64::from(HOURS_PER_DAY_U) * u64::from(MINUTES_PER_HOUR_U) * u64::from(SECONDS_PER_MINUTE_U) * 10u64.pow(9); - let ns_multiplier = 1; - Self::from_value_i(ns, century_divider, ns_multiplier) - } - - - - - - /// Creates a new duration from the provided unit - pub fn from_f64(value: f64, unit: TimeUnit) -> Self { - unit * value - } - - /// Returns this duration in f64 in the provided unit. - /// For high fidelity comparisons, it is recommended to keep using the Duration structure. - pub fn in_unit_f64(&self, unit: TimeUnit) -> f64 { - self.in_unit(unit) - } - - /// Returns this duration in seconds f64. - /// For high fidelity comparisons, it is recommended to keep using the Duration structure. - pub fn in_seconds(&self) -> f64 { - (self.ns as f64 / 1e9) + (i128::from(self.centuries) * i128::from(DAYS_PER_CENTURY_U) * i128::from(SECONDS_PER_DAY_U)) as f64 - } - - /// Returns the value of this duration in the requested unit. - pub fn in_unit(&self, unit: TimeUnit) -> f64 { - self.in_seconds() * unit.from_seconds() - } - - /// Returns the absolute value of this duration - #[must_use] - pub fn abs(&self) -> Self { - if self.centuries < 0 { -*self } else { *self } - } - - - - pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64) { - - let total_ns : i128 = i128::from(self.centuries) * i128::from(NS_PER_CENTURY_U) + i128::from(self.ns); - - let sign = total_ns.signum() as i8; - let mut ns_left = total_ns.abs() as u64; - - let days = ns_left / (10u64.pow(9) * u64::from(SECONDS_PER_DAY_U)); - ns_left %= 10u64.pow(9) * u64::from(SECONDS_PER_DAY_U) ; - - let hours = ns_left / (10u64.pow(9) * u64::from(SECONDS_PER_HOUR_U)); - ns_left %= 10u64.pow(9) * u64::from(SECONDS_PER_HOUR_U); - - let minutes = ns_left / (10u64.pow(9) * u64::from(SECONDS_PER_MINUTE_U)); - ns_left %= 10u64.pow(9) * u64::from(SECONDS_PER_MINUTE_U); - - let seconds = ns_left / 10u64.pow(9); - ns_left %= 10u64.pow(9); - - let ms = ns_left / 10u64.pow(6); - ns_left %= 10u64.pow(6); - - let us = ns_left / 10u64.pow(3); - ns_left %= 10u64.pow(3); - - let ns = ns_left; - - (sign, days, hours, minutes, seconds, ms, us, ns) - } -} - impl fmt::Display for Duration { - // Prints this duration with automatic selection of the highest and sub-second unit + // Prints this duration with automatic selection of the units, i.e. everything that isn't zero is ignored fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - - let (sign, days, hours, minutes, seconds, milli, us, nano) = self.decompose(); - - let values = [days, hours, minutes, seconds, milli, us, nano]; - let names = ["days", "h", "min", "s", "ms", "us", "ns"]; - - let print_all = false; - - let mut interval_start = None; - let mut interval_end = None; - - if print_all { - interval_start = Some(0); - interval_end = Some(values.len()-1); + if self.total_nanoseconds() == 0 { + write!(f, "0 ns") } else { - for index in 0..values.len() { - if interval_start.is_none() { - if values[index] > 0 { - interval_start = Some(index); - interval_end = Some(index); + let (sign, days, hours, minutes, seconds, milli, us, nano) = self.decompose(); + if sign == -1 { + write!(f, "-")?; + } + + let values = [days, hours, minutes, seconds, milli, us, nano]; + let units = ["days", "h", "min", "s", "ms", "us", "ns"]; + let mut prev_ignored = true; + for (val, unit) in values.iter().zip(units.iter()) { + if *val > 0 { + if !prev_ignored { + // Add space + write!(f, " ")?; } + write!(f, "{} {}", val, unit)?; + prev_ignored = false; } else { - if values[index] > 0 { - interval_end = Some(index); - } + prev_ignored = true; } } + Ok(()) } - assert!(interval_start.is_some()); - assert!(interval_end.is_some()); - - if sign == -1 { - write!(f, "-")?; - } - - for i in interval_start.unwrap()..=interval_end.unwrap() { - write!(f, "{} {} ", values[i], names[i])?; - } - - - Ok(()) - } } @@ -443,23 +504,27 @@ impl Add for Duration { type Output = Duration; fn add(self, rhs: Self) -> Duration { + let mut me = self; + me.centuries = me.centuries.saturating_add(rhs.centuries); + if me.centuries == i16::MAX { + return Duration::MAX; + } + me.nanoseconds = me.nanoseconds.saturating_add(rhs.nanoseconds); - // Usage of u128 is to avoid possibility of overflow and type-system carry from u64 - // may switch approach when carrying_add is stabilized - let mut total_ns: u128 = u128::from(self.ns) + u128::from(rhs.ns); - let mut century_carry = 0; - if total_ns > u128::from(NS_PER_CENTURY_U) { - century_carry = total_ns / u128::from(NS_PER_CENTURY_U); - total_ns %= u128::from(NS_PER_CENTURY_U); - // total_ns is now guaranteed to be less than u64_max + if me.nanoseconds == u64::MAX { + // Increment the centuries and decrease nanoseconds + me.centuries += 1; + if me.centuries == i16::MAX { + return Self::Output::MAX; + } + + me.nanoseconds = me + .nanoseconds + .saturating_add(rhs.nanoseconds - NANOSECONDS_PER_CENTURY); } - - let total_centuries = - self.centuries - .saturating_add(rhs.centuries) - .saturating_add(century_carry as i16); - - Self { ns : total_ns as u64, centuries : total_centuries } + + me.normalize(); + me } } @@ -473,16 +538,32 @@ impl Sub for Duration { type Output = Duration; fn sub(self, rhs: Self) -> Duration { - let mut total_ns: i128 = i128::from(self.ns) - i128::from(rhs.ns); - let mut century_borrow = 0; - if total_ns < 0 { - century_borrow = (-total_ns / i128::from(NS_PER_CENTURY_U))+1; - total_ns += century_borrow * i128::from(NS_PER_CENTURY_U); + let mut me = self; + + me.centuries = me.centuries.saturating_sub(rhs.centuries); + if me.centuries == i16::MIN { + return Duration::MIN; } + if rhs.nanoseconds == me.nanoseconds { + // Special case to avoid getting confused with the saturating sub call + me.nanoseconds = 0 + } else { + me.nanoseconds = me.nanoseconds.saturating_sub(rhs.nanoseconds); + if me.nanoseconds == 0 { + // Oh, we might over underflowed + me.centuries += 1; + if me.centuries == i16::MAX { + return Self::Output::MAX; + } - let total_centuries = self.centuries.saturating_sub(rhs.centuries).saturating_sub(century_borrow as i16); - Self { ns : total_ns as u64, centuries : total_centuries } + me.nanoseconds = me + .nanoseconds + .saturating_add(rhs.nanoseconds + NANOSECONDS_PER_CENTURY); + } + } + me.normalize(); + me } } @@ -548,10 +629,14 @@ impl PartialOrd for Duration { } impl Neg for Duration { - type Output = Duration; + type Output = Self; + #[must_use] fn neg(self) -> Self::Output { - Self { ns: NS_PER_CENTURY_U - self.ns, centuries : -self.centuries-1 } + Self { + centuries: -self.centuries - 1, + nanoseconds: NANOSECONDS_PER_CENTURY - self.nanoseconds, + } } } @@ -622,6 +707,7 @@ impl FromStr for Duration { /// assert_eq!(Duration::from_str("10.598 nanosecond").unwrap(), 10.598_f64.nanoseconds()); /// ``` pub trait TimeUnitHelper: Copy + Mul { + // TODO: Find a better name for this, it's a pain to import and use fn centuries(self) -> Duration { self * TimeUnit::Century } @@ -648,7 +734,6 @@ pub trait TimeUnitHelper: Copy + Mul { } } - #[derive(Copy, Clone, Debug, PartialEq)] pub enum TimeUnit { /// 36525 days, it the number of days per century in the Julian calendar @@ -681,51 +766,38 @@ impl Sub for TimeUnit { } impl TimeUnit { + #[must_use] pub fn in_seconds(self) -> f64 { match self { - TimeUnit::Century => DAYS_PER_CENTURY_F * SECONDS_PER_DAY_F, - TimeUnit::Day => SECONDS_PER_DAY_F, - TimeUnit::Hour => SECONDS_PER_HOUR_F, - TimeUnit::Minute => SECONDS_PER_MINUTE_F, - TimeUnit::Second => ONE_F, + TimeUnit::Century => DAYS_PER_CENTURY * SECONDS_PER_DAY, + TimeUnit::Day => SECONDS_PER_DAY, + TimeUnit::Hour => SECONDS_PER_HOUR, + TimeUnit::Minute => SECONDS_PER_MINUTE, + TimeUnit::Second => 1.0, TimeUnit::Millisecond => 1e-3, TimeUnit::Microsecond => 1e-6, TimeUnit::Nanosecond => 1e-9, } } - pub fn in_seconds_f64(self) -> f64 { - self.in_seconds() - } - - #[allow(clippy::wrong_self_convention)] + // #[allow(clippy::wrong_self_convention)] + #[must_use] pub fn from_seconds(self) -> f64 { 1.0 / self.in_seconds() } } - - -impl_ops_i!(u8); -impl_ops_i!(i8); - -impl_ops_i!(u16); -impl_ops_i!(i16); - -impl_ops_i!(u32); -impl_ops_i!(i32); - -impl_ops_i!(u64); -impl_ops_i!(i64); - -// Removed u128 and i128 as some operators couldn’t be made safe and there is no real need for them -//impl_ops_u!(u128); -//impl_ops_i!(i128); - -impl_ops_f!(f32); -impl_ops_f!(f64); - - +impl_ops_for_type!(f32); +impl_ops_for_type!(f64); +// impl_ops_for_type!(u8); +// impl_ops_for_type!(i8); +// impl_ops_for_type!(u16); +// impl_ops_for_type!(i16); +impl_ops_for_type!(u32); +impl_ops_for_type!(i32); +impl_ops_for_type!(u64); +impl_ops_for_type!(i64); +impl_ops_for_type!(u128); #[test] fn time_unit() { @@ -758,8 +830,8 @@ fn time_unit() { let sum: Duration = seven_hours + six_minutes + five_seconds; assert!((sum.in_seconds() - 25565.0).abs() < EPSILON); - let neg_sum = dbg!(-dbg!(sum)); - assert!(dbg!((dbg!(neg_sum.in_seconds()) + dbg!(25565.0)).abs()) < EPSILON); + let neg_sum = -sum; + assert!((neg_sum.in_seconds() + 25565.0).abs() < EPSILON); assert_eq!(neg_sum.abs(), sum, "abs failed"); @@ -770,6 +842,7 @@ fn time_unit() { let quarter_hour = 0.25 * TimeUnit::Hour; let third_hour = (1.0 / 3.0) * TimeUnit::Hour; let sum: Duration = quarter_hour + third_hour; + dbg!(quarter_hour, third_hour, sum); assert!((sum.in_unit_f64(TimeUnit::Minute) - 35.0).abs() < EPSILON); println!( "Duration: {}\nFloating: {}", @@ -781,8 +854,8 @@ fn time_unit() { let third_hour: Duration = -1 * TimeUnit::Hour / 3; let sum: Duration = quarter_hour + third_hour; let delta = sum.in_unit(TimeUnit::Millisecond).floor() - - sum.in_unit(TimeUnit::Second).floor() * 1000.0; - println!("{:?}", delta * -1.0 == 0.0); + - sum.in_unit(TimeUnit::Second).floor() * TwoFloat::from(1000.0); + println!("{:?}", delta * TwoFloat::from(-1) == TwoFloat::from(0)); assert!((sum.in_unit_f64(TimeUnit::Minute) + 35.0).abs() < EPSILON); } @@ -790,43 +863,51 @@ fn time_unit() { fn duration_print() { // Check printing adds precision assert_eq!( - format!("{}", TimeUnit::Day * 10.0 + TimeUnit::Hour * 5).trim(), + format!("{}", TimeUnit::Day * 10.0 + TimeUnit::Hour * 5), "10 days 5 h" ); assert_eq!( - format!("{}", TimeUnit::Hour * 5 + TimeUnit::Millisecond * 256).trim(), - "5 h 0 min 0 s 256 ms" + format!("{}", TimeUnit::Hour * 5 + TimeUnit::Millisecond * 256), + "5 h 256 ms" ); assert_eq!( format!( "{}", TimeUnit::Hour * 5 + TimeUnit::Millisecond * 256 + TimeUnit::Nanosecond - ).trim(), - "5 h 0 min 0 s 256 ms 0 us 1 ns" + ), + "5 h 256 ms 1 ns" ); assert_eq!( - format!("{}", TimeUnit::Hour + TimeUnit::Second).trim(), + format!("{}", TimeUnit::Hour + TimeUnit::Second), "1 h 0 min 1 s" ); - // Check printing negative durations only shows one negative sign assert_eq!( - format!("{}", TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256).trim(), - "-5 h 0 min 0 s 256 ms" + format!( + "{}", + TimeUnit::Hour * 5 + + TimeUnit::Millisecond * 256 + + TimeUnit::Microsecond + + TimeUnit::Nanosecond * 3.5 + ), + "5 h 256 ms 1003.5 ns" ); - let d : Duration = TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256 + TimeUnit::Nanosecond * -3; - dbg!(d.in_seconds()); + // Check printing negative durations only shows one negative sign + assert_eq!( + format!("{}", TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256), + "-5 h 256 ms" + ); assert_eq!( format!( "{}", - TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256 + dbg!(TimeUnit::Nanosecond * -3) - ).trim(), - "-5 h 0 min 0 s 256 ms 0 us 3 ns" + TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256 + TimeUnit::Nanosecond * -3.5 + ), + "-5 h 256 ms 3.5 ns" ); assert_eq!( @@ -834,20 +915,17 @@ fn duration_print() { "{}", (TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256) - (TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256 + TimeUnit::Nanosecond * 2) - ).trim(), + ), "-2 ns" ); - assert_eq!( - format!("{}", Duration::from_nanoseconds_f(2.0)).trim(), - "2 ns" - ); + assert_eq!(format!("{}", TimeUnit::Nanosecond * 2), "2 ns"); // Check that we support nanoseconds pas GPS time - let now = TimeUnit::Nanosecond * 1286495254000000203_u128; + let now = TimeUnit::Nanosecond * 1286495254000000123_u128; assert_eq!( - format!("{}", now).trim(), - "14889 days 23 h 47 min 34 s 0 ms 0 us 203 ns" + format!("{}", now), + "14889 days 23 h 47 min 34 s 0 ms 203.125 ns" ); let arbitrary = 14889.days() @@ -857,8 +935,8 @@ fn duration_print() { + 0.milliseconds() + 123.nanoseconds(); assert_eq!( - format!("{}", arbitrary).trim(), - "14889 days 23 h 47 min 34 s 0 ms 0 us 123 ns" + format!("{}", arbitrary), + "14889 days 23 h 47 min 34 s 0 ms 123 ns" ); // Test fractional @@ -870,15 +948,15 @@ fn duration_print() { sum.in_unit_f64(TimeUnit::Minute), (1.0 / 4.0 + 1.0 / 3.0) * 60.0 ); - assert_eq!(format!("{}", sum).trim(), "35 min"); // Note the automatic unit selection + assert_eq!(format!("{}", sum), "35 min 0 s"); // Note the automatic unit selection let quarter_hour = -0.25 * TimeUnit::Hour; let third_hour: Duration = -1 * TimeUnit::Hour / 3; let sum: Duration = quarter_hour + third_hour; - let delta = sum.in_unit(TimeUnit::Millisecond).floor() - - sum.in_unit(TimeUnit::Second).floor() * 1000.0; - println!("{:?}", delta * -1.0 == 0.0); // This floating-point comparison looks wrong - assert_eq!(format!("{}", sum).trim(), "-35 min"); // Note the automatic unit selection + let delta = + sum.in_unit(TimeUnit::Millisecond).floor() - sum.in_unit(TimeUnit::Second).floor() * 1000.0; + println!("{:?}", (delta * -1.0) == 0.0); + assert_eq!(format!("{}", sum), "-35 min 0 s"); // Note the automatic unit selection } #[test] @@ -889,3 +967,19 @@ fn deser_test() { pub _d: Duration, } } + +#[test] +fn test_i128_extremes() { + let d = Duration::from_total_nanoseconds(i128::MAX); + println!("{}", d); + assert_eq!(Duration::from_total_nanoseconds(d.total_nanoseconds()), d); + let d = Duration::from_total_nanoseconds(i128::MIN + 1); + println!("{}", d); + // Test truncation + let d_min = Duration::from_total_nanoseconds(i128::MIN); + assert_eq!(d - d_min, 1 * TimeUnit::Nanosecond); + assert_eq!(d_min - d, -1 * TimeUnit::Nanosecond); + println!("{}", d - d_min); + assert_eq!(Duration::from_total_nanoseconds(d.total_nanoseconds()), d); + println!("{}", Duration::MAX); +} diff --git a/src/epoch.rs b/src/epoch.rs index fe66d76b..65419427 100644 --- a/src/epoch.rs +++ b/src/epoch.rs @@ -9,7 +9,6 @@ use crate::{ Errors, TimeSystem, DAYS_PER_CENTURY, ET_EPOCH_S, J1900_OFFSET, J2000_OFFSET, MJD_OFFSET, SECONDS_PER_DAY, }; -use std::convert::TryFrom; use std::fmt; use std::ops::{Add, AddAssign, Sub, SubAssign}; use std::str::FromStr; @@ -58,7 +57,7 @@ const JULY_YEARS: [i32; 11] = [ 1972, 1981, 1982, 1983, 1985, 1992, 1993, 1994, 1997, 2012, 2015, ]; -const USUAL_DAYS_PER_MONTH: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; +const USUAL_DAYS_PER_MONTH: [u32; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; /// Defines an Epoch in TAI (temps atomique international) in seconds past 1900 January 01 at midnight (like the Network Time Protocol). /// @@ -261,11 +260,11 @@ impl Epoch { /// Attempts to build an Epoch from the provided Gregorian date and time in TAI. pub fn maybe_from_gregorian_tai( year: i32, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, nanos: u32, ) -> Result { Self::maybe_from_gregorian( @@ -284,11 +283,11 @@ impl Epoch { #[allow(clippy::too_many_arguments)] pub fn maybe_from_gregorian( year: i32, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, nanos: u32, ts: TimeSystem, ) -> Result { @@ -342,34 +341,34 @@ impl Epoch { /// Use maybe_from_gregorian_tai if unsure. pub fn from_gregorian_tai( year: i32, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, nanos: u32, ) -> Self { Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, nanos) .expect("invalid Gregorian date") } - pub fn from_gregorian_tai_at_midnight(year: i32, month: u8, day: u8) -> Self { + pub fn from_gregorian_tai_at_midnight(year: i32, month: u32, day: u32) -> Self { Self::maybe_from_gregorian_tai(year, month, day, 0, 0, 0, 0) .expect("invalid Gregorian date") } - pub fn from_gregorian_tai_at_noon(year: i32, month: u8, day: u8) -> Self { + pub fn from_gregorian_tai_at_noon(year: i32, month: u32, day: u32) -> Self { Self::maybe_from_gregorian_tai(year, month, day, 12, 0, 0, 0) .expect("invalid Gregorian date") } pub fn from_gregorian_tai_hms( year: i32, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, ) -> Self { Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, 0) .expect("invalid Gregorian date") @@ -378,11 +377,11 @@ impl Epoch { /// Attempts to build an Epoch from the provided Gregorian date and time in UTC. pub fn maybe_from_gregorian_utc( year: i32, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, nanos: u32, ) -> Result { let mut if_tai = @@ -411,34 +410,34 @@ impl Epoch { /// Use maybe_from_gregorian_tai if unsure. pub fn from_gregorian_utc( year: i32, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, nanos: u32, ) -> Self { Self::maybe_from_gregorian_utc(year, month, day, hour, minute, second, nanos) .expect("invalid Gregorian date") } - pub fn from_gregorian_utc_at_midnight(year: i32, month: u8, day: u8) -> Self { + pub fn from_gregorian_utc_at_midnight(year: i32, month: u32, day: u32) -> Self { Self::maybe_from_gregorian_utc(year, month, day, 0, 0, 0, 0) .expect("invalid Gregorian date") } - pub fn from_gregorian_utc_at_noon(year: i32, month: u8, day: u8) -> Self { + pub fn from_gregorian_utc_at_noon(year: i32, month: u32, day: u32) -> Self { Self::maybe_from_gregorian_utc(year, month, day, 12, 0, 0, 0) .expect("invalid Gregorian date") } pub fn from_gregorian_utc_hms( year: i32, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, ) -> Self { Self::maybe_from_gregorian_utc(year, month, day, hour, minute, second, 0) .expect("invalid Gregorian date") @@ -741,21 +740,21 @@ impl Epoch { if ts == TimeSystem::UTC { Self::maybe_from_gregorian_utc( cap[1].to_owned().parse::()?, - cap[2].to_owned().parse::()?, - cap[3].to_owned().parse::()?, - cap[4].to_owned().parse::()?, - cap[5].to_owned().parse::()?, - cap[6].to_owned().parse::()?, + cap[2].to_owned().parse::()?, + cap[3].to_owned().parse::()?, + cap[4].to_owned().parse::()?, + cap[5].to_owned().parse::()?, + cap[6].to_owned().parse::()?, nanos, ) } else { Self::maybe_from_gregorian( cap[1].to_owned().parse::()?, - cap[2].to_owned().parse::()?, - cap[3].to_owned().parse::()?, - cap[4].to_owned().parse::()?, - cap[5].to_owned().parse::()?, - cap[6].to_owned().parse::()?, + cap[2].to_owned().parse::()?, + cap[3].to_owned().parse::()?, + cap[4].to_owned().parse::()?, + cap[5].to_owned().parse::()?, + cap[6].to_owned().parse::()?, nanos, ts, ) @@ -765,11 +764,11 @@ impl Epoch { // Asumme UTC Self::maybe_from_gregorian_utc( cap[1].to_owned().parse::()?, - cap[2].to_owned().parse::()?, - cap[3].to_owned().parse::()?, - cap[4].to_owned().parse::()?, - cap[5].to_owned().parse::()?, - cap[6].to_owned().parse::()?, + cap[2].to_owned().parse::()?, + cap[3].to_owned().parse::()?, + cap[4].to_owned().parse::()?, + cap[5].to_owned().parse::()?, + cap[6].to_owned().parse::()?, nanos, ) } @@ -903,7 +902,7 @@ impl Epoch { month = 12; year -= 1; } - day = i32::from(USUAL_DAYS_PER_MONTH[(month - 1) as usize]); + day = USUAL_DAYS_PER_MONTH[(month - 1) as usize] as i32; } day += 1; // Otherwise the day count starts at 0 // Get the hours by the exact number of seconds in an hour @@ -923,7 +922,6 @@ impl Epoch { } } - impl FromStr for Epoch { type Err = Errors; @@ -1012,15 +1010,15 @@ impl fmt::Display for Epoch { /// Returns true if the provided Gregorian date is valid. Leap second days may have 60 seconds. pub fn is_gregorian_valid( year: i32, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, nanos: u32, ) -> bool { let max_seconds = if (month == 12 || month == 6) - && day == USUAL_DAYS_PER_MONTH[month as usize - 1] + && day == USUAL_DAYS_PER_MONTH[month as usize - 1].into() && hour == 23 && minute == 59 && ((month == 6 && JULY_YEARS.contains(&year)) @@ -1042,7 +1040,8 @@ pub fn is_gregorian_valid( { return false; } - if day > USUAL_DAYS_PER_MONTH[month as usize - 1] && (month != 2 || !is_leap_year(year)) { + if day > USUAL_DAYS_PER_MONTH[month as usize - 1].into() && (month != 2 || !is_leap_year(year)) + { // Not in February or not a leap year return false; } @@ -1368,10 +1367,6 @@ fn gpst() { #[test] fn spice_et_tdb() { use crate::J2000_NAIF; - let round_prec = |val : f64, prec : i32| -> f64 { - let power = (10.0 as f64).powi(prec); - ((val * power).round()) / power - }; /* >>> sp.str2et("2012-02-07 11:22:33 UTC") 381885819.18493587 @@ -1438,7 +1433,7 @@ fn spice_et_tdb() { let sp_ex = Epoch::from_et_seconds(381_885_753.003_859_5); assert!(dbg!(2455964.9739931 - sp_ex.as_jde_tdb_days()).abs() < 4.7e-10); - assert!((2455964.9739931 - dbg!(round_prec(sp_ex.as_jde_et_days(), 7))).abs() < std::f64::EPSILON); + assert!((2455964.9739931 - sp_ex.as_jde_et_days()).abs() < std::f64::EPSILON); let sp_ex = Epoch::from_et_seconds(0.0); assert!(sp_ex.as_et_seconds() < std::f64::EPSILON); diff --git a/src/lib.rs b/src/lib.rs index 10544723..f3a3a28d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,6 +154,8 @@ pub const SECONDS_PER_MINUTE: f64 = 60.0; pub const SECONDS_PER_HOUR: f64 = 3_600.0; /// `SECONDS_PER_DAY` defines the number of seconds per day. pub const SECONDS_PER_DAY: f64 = 86_400.0; +/// `SECONDS_PER_CENTURY` defines the number of seconds per century. +pub const SECONDS_PER_CENTURY: f64 = SECONDS_PER_DAY * DAYS_PER_CENTURY; /// `SECONDS_PER_YEAR` corresponds to the number of seconds per julian year from [NAIF SPICE](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/jyear_c.html). pub const SECONDS_PER_YEAR: f64 = 31_557_600.0; /// `SECONDS_PER_TROPICAL_YEAR` corresponds to the number of seconds per tropical year from [NAIF SPICE](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/tyear_c.html). From 87fdd9282ffc0cb503dabcac485372d0496f97d8 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Mon, 14 Feb 2022 12:38:31 -0700 Subject: [PATCH 10/17] Fixed Add, Sub Remove lots of impl_ops for Duration because rustc isn't happy Signed-off-by: Christopher Rabotin --- src/duration.rs | 270 ++++++++++++++++++++++++++++-------------------- src/epoch.rs | 134 ++++++++++++------------ 2 files changed, 225 insertions(+), 179 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index cdb12387..bb0b9045 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -14,12 +14,12 @@ use crate::{ SECONDS_PER_MINUTE, }; use std::cmp::Ordering; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryInto; use std::fmt; use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use std::str::FromStr; -const DAYS_PER_CENTURY_U: u128 = 36_525; +// const DAYS_PER_CENTURY_U: u128 = 36_525; const DAYS_PER_CENTURY_U64: u64 = 36_525; const HOURS_PER_CENTURY: f64 = 24.0 * DAYS_PER_CENTURY; const MINUTES_PER_CENTURY: f64 = 60.0 * HOURS_PER_CENTURY; @@ -31,77 +31,95 @@ const NANOSECONDS_PER_HOUR: u64 = 60 * NANOSECONDS_PER_MINUTE; const NANOSECONDS_PER_DAY: u64 = 24 * NANOSECONDS_PER_HOUR; const NANOSECONDS_PER_CENTURY: u64 = DAYS_PER_CENTURY_U64 * NANOSECONDS_PER_DAY; +const fn centuries_per_u64_nanoseconds() -> i16 { + u64::MAX.div_euclid(NANOSECONDS_PER_CENTURY) as i16 +} /// Defines generally usable durations for nanosecond precision valid for 32,768 centuries in either direction, and only on 80 bits / 10 octets. +/// +/// **Important conventions:** +/// Conventions had to be made to define the partial order of a duration. +/// It was decided that the nanoseconds corresponds to the nanoseconds _into_ the current century. In other words, +/// a durationn with centuries = -1 and nanoseconds = 0 is _a smaller duration_ than centuries = -1 and nanoseconds = 1. +/// That difference is exactly 1 nanoseconds, where the former duration is "closer to zero" than the latter. +/// As such, the largest negative duration that can be represented sets the centuries to i16::MAX and its nanoseconds to NANOSECONDS_PER_CENTURY. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] pub struct Duration { - pub centuries: i16, - pub nanoseconds: u64, + centuries: i16, + nanoseconds: u64, } impl Duration { fn normalize(&mut self) { - if self.nanoseconds > NANOSECONDS_PER_CENTURY { - let (centuries, nanoseconds) = self.nanoseconds.div_rem_euclid(NANOSECONDS_PER_CENTURY); - self.nanoseconds = nanoseconds; - // If the quotient is larger than an i16, set the centuries to the max. - // Similarly, we will ensure with a saturating add that the centuries always fits in an i16 - match i16::try_from(centuries) { - Ok(centuries) => self.centuries = self.centuries.saturating_add(centuries), - Err(_) => self.centuries = i16::MAX, + let extra_centuries = self.nanoseconds.div_euclid(NANOSECONDS_PER_CENTURY); + // We can skip this whole step if the div_euclid shows that we didn't overflow the number of nanoseconds per century + if extra_centuries > 0 { + let rem_nanos = self.nanoseconds.rem_euclid(NANOSECONDS_PER_CENTURY); + + if self.centuries == i16::MIN && rem_nanos > 0 { + // We're at the min number of centuries already, and we have extra nanos, so we're saturated the duration limit + *self = Self::MIN; + } else if self.centuries == i16::MAX && rem_nanos > 0 { + // Saturated max + *self = Self::MAX; + } else if self.centuries >= 0 { + // Check that we can safely cast because we have that room without overflowing + if (i16::MAX - self.centuries) as u64 >= extra_centuries { + // We can safely add without an overflow + self.centuries += extra_centuries as i16; + self.nanoseconds = rem_nanos; + } else { + // Saturated max again + *self = Self::MAX; + } + } else { + assert!(self.centuries < 0, "this shouldn't be possible"); + + // Check that we can safely cast because we have that room without overflowing + if (i16::MIN - self.centuries) as u64 >= extra_centuries { + // We can safely add without an overflow + self.centuries += extra_centuries as i16; + self.nanoseconds = rem_nanos; + } else { + // Saturated max again + *self = Self::MIN; + } } } } + /// Create a normalized duration from its parts + pub fn from_parts(centuries: i16, nanoseconds: u64) -> Self { + let mut me = Self { + centuries, + nanoseconds, + }; + me.normalize(); + me + } + /// Converts the total nanoseconds as i128 into this Duration (saving 48 bits) pub fn from_total_nanoseconds(nanos: i128) -> Self { - if nanos < 0 { - let mut centuries = -1; - let mut nanoseconds: u64; - let truncated; - let usable_nanos = if nanos == i128::MIN { - truncated = true; - nanos + 1 - } else { - truncated = false; - nanos - }; - if usable_nanos.abs() >= u64::MAX.into() { - centuries -= 1; - nanoseconds = (usable_nanos.abs() - i128::from(NANOSECONDS_PER_CENTURY)) as u64; - } else { - // We know it fits! - nanoseconds = usable_nanos.abs() as u64; - } - if truncated { - nanoseconds -= 1; - } - let mut me = Self { - centuries, - nanoseconds, - }; - me.normalize(); - me + // In this function, we simply check that the input data can be casted. The `normalize` function will check whether more work needs to be done. + if nanos == 0 { + Self::ZERO } else { - let mut centuries = 0; - let nanoseconds: u64; - // We must check that we fit in a u64, or the normalize function cannot work! - if nanos >= u64::MAX.into() { - centuries += 1; - nanoseconds = (nanos - i128::from(NANOSECONDS_PER_CENTURY)) as u64; + let centuries_i128 = nanos.div_euclid(NANOSECONDS_PER_CENTURY.into()); + let remaining_nanos_i128 = nanos.rem_euclid(NANOSECONDS_PER_CENTURY.into()); + if centuries_i128 > i16::MAX.into() { + Self::MAX + } else if centuries_i128 < i16::MIN.into() { + Self::MIN } else { - // We know it fits! - nanoseconds = nanos as u64; + // We know that the centuries fit, and we know that the nanos are less than the number + // of nanos per centuries, and rem_euclid guarantees that it's positive, so the + // casting will work fine every time. + Self::from_parts(centuries_i128 as i16, remaining_nanos_i128 as u64) } - let mut me = Self { - centuries, - nanoseconds, - }; - me.normalize(); - me } } /// Returns the total nanoseconds in a signed 128 bit integer + #[must_use] pub fn total_nanoseconds(self) -> i128 { i128::from(self.centuries) * i128::from(NANOSECONDS_PER_CENTURY) + i128::from(self.nanoseconds) @@ -115,12 +133,14 @@ impl Duration { /// Returns this duration in f64 in the provided unit. /// For high fidelity comparisons, it is recommended to keep using the Duration structure. + #[must_use] pub fn in_unit_f64(&self, unit: TimeUnit) -> f64 { f64::from(self.in_unit(unit)) } /// Returns this duration in seconds f64. /// For high fidelity comparisons, it is recommended to keep using the Duration structure. + #[must_use] pub fn in_seconds(&self) -> f64 { // Compute the seconds and nanoseconds that we know this fits on a 64bit float let (seconds_u64, nanoseconds_u64) = @@ -157,6 +177,8 @@ impl Duration { out } + /// Decomposes a Duration in its sign, days, + #[must_use] pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64) { let total_ns = self.total_nanoseconds(); @@ -185,16 +207,22 @@ impl Duration { ) } + /// A duration of exactly zero nanoseconds + const ZERO: Self = Self { + centuries: 0, + nanoseconds: 0, + }; + /// Maximum duration that can be represented pub const MAX: Self = Self { centuries: i16::MAX, - nanoseconds: u64::MAX, + nanoseconds: NANOSECONDS_PER_CENTURY, }; /// Minimum duration that can be represented pub const MIN: Self = Self { centuries: i16::MIN, - nanoseconds: u64::MAX, + nanoseconds: NANOSECONDS_PER_CENTURY, }; /// Smallest duration that can be represented @@ -202,6 +230,15 @@ impl Duration { centuries: 0, nanoseconds: 1, }; + + /// Minimum positive duration is one nanoseconds + pub const MIN_POSITIVE: Self = Self::EPSILON; + + /// Minimum negative duration is minus one nanosecond + pub const MIN_NEGATIVE: Self = Self { + centuries: -1, + nanoseconds: NANOSECONDS_PER_CENTURY - 1, + }; } impl<'de> Deserialize<'de> for Duration { @@ -221,7 +258,10 @@ macro_rules! impl_ops_for_type { fn mul(self, q: $type) -> Duration { match self { - TimeUnit::Century => self * q * DAYS_PER_CENTURY_U, + TimeUnit::Century => { + todo!(); + // self * qee * DAYS_PER_CENTURY_U64ee + } TimeUnit::Day => { // The centuries will be a round number here so the `as` conversion should work. let centuries_typed = q.div_euclid(DAYS_PER_CENTURY as $type); @@ -338,7 +378,7 @@ macro_rules! impl_ops_for_type { } TimeUnit::Nanosecond => { // The centuries will be a round number here so the `as` conversion should work. - let centuries_typed = q.div_euclid((SECONDS_PER_CENTURY * 1e-9) as $type); + let centuries_typed = q.div_euclid(NANOSECONDS_PER_CENTURY as $type); let centuries = if centuries_typed > (i16::MAX as $type) { return Duration::MAX; } else if centuries_typed < (i16::MIN as $type) { @@ -348,11 +388,12 @@ macro_rules! impl_ops_for_type { }; // rem_euclid returns the nonnegative number, so we can cast that directly into u64 - let nanoseconds = q.rem_euclid(DAYS_PER_CENTURY as $type) as u64; - Duration { - centuries, - nanoseconds, - } + let nanoseconds = if centuries >= 0 { + q.rem_euclid(NANOSECONDS_PER_CENTURY as $type) as u64 + } else { + ((NANOSECONDS_PER_CENTURY as $type) + q + (1 as $type)) as u64 + }; + Duration::from_parts(centuries, nanoseconds) } } } @@ -504,24 +545,20 @@ impl Add for Duration { type Output = Duration; fn add(self, rhs: Self) -> Duration { + // Check that the addition fits in an i16 let mut me = self; - me.centuries = me.centuries.saturating_add(rhs.centuries); - if me.centuries == i16::MAX { - return Duration::MAX; - } - me.nanoseconds = me.nanoseconds.saturating_add(rhs.nanoseconds); - - if me.nanoseconds == u64::MAX { - // Increment the centuries and decrease nanoseconds - me.centuries += 1; - if me.centuries == i16::MAX { - return Self::Output::MAX; + match me.centuries.checked_add(rhs.centuries) { + None => { + // Overflowed, so we've hit the max + return Self::MAX; + } + Some(centuries) => { + me.centuries = centuries; } - - me.nanoseconds = me - .nanoseconds - .saturating_add(rhs.nanoseconds - NANOSECONDS_PER_CENTURY); } + // We can safely add two nanoseconds together because we can fit five centuries in one u64 + // cf. https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b4011b1d5c06c38a72f28d0a9e6a5574 + me.nanoseconds += rhs.nanoseconds; me.normalize(); me @@ -538,30 +575,34 @@ impl Sub for Duration { type Output = Duration; fn sub(self, rhs: Self) -> Duration { + // Check that the subtraction fits in an i16 let mut me = self; - - me.centuries = me.centuries.saturating_sub(rhs.centuries); - if me.centuries == i16::MIN { - return Duration::MIN; + match me.centuries.checked_sub(rhs.centuries) { + None => { + // Underflowed, so we've hit the max + return Self::MIN; + } + Some(centuries) => { + me.centuries = centuries; + } } - if rhs.nanoseconds == me.nanoseconds { - // Special case to avoid getting confused with the saturating sub call - me.nanoseconds = 0 - } else { - me.nanoseconds = me.nanoseconds.saturating_sub(rhs.nanoseconds); - if me.nanoseconds == 0 { - // Oh, we might over underflowed - me.centuries += 1; - if me.centuries == i16::MAX { - return Self::Output::MAX; + match me.nanoseconds.checked_sub(rhs.nanoseconds) { + None => { + // Decrease the number of centuries, and realign + me.centuries -= 1; + me.nanoseconds = me.nanoseconds + NANOSECONDS_PER_CENTURY - rhs.nanoseconds; + } + Some(nanos) => { + if me.centuries >= 0 { + me.nanoseconds = nanos + } else { + // Account for the zero + me.nanoseconds = nanos + 1 } - - me.nanoseconds = me - .nanoseconds - .saturating_add(rhs.nanoseconds + NANOSECONDS_PER_CENTURY); } - } + }; + me.normalize(); me } @@ -787,17 +828,16 @@ impl TimeUnit { } } -impl_ops_for_type!(f32); impl_ops_for_type!(f64); // impl_ops_for_type!(u8); // impl_ops_for_type!(i8); // impl_ops_for_type!(u16); // impl_ops_for_type!(i16); -impl_ops_for_type!(u32); -impl_ops_for_type!(i32); -impl_ops_for_type!(u64); +// impl_ops_for_type!(u32); +// impl_ops_for_type!(i32); +// impl_ops_for_type!(u64); impl_ops_for_type!(i64); -impl_ops_for_type!(u128); +// impl_ops_for_type!(u128); #[test] fn time_unit() { @@ -922,7 +962,7 @@ fn duration_print() { assert_eq!(format!("{}", TimeUnit::Nanosecond * 2), "2 ns"); // Check that we support nanoseconds pas GPS time - let now = TimeUnit::Nanosecond * 1286495254000000123_u128; + let now = TimeUnit::Nanosecond * 1286495254000000123; assert_eq!( format!("{}", now), "14889 days 23 h 47 min 34 s 0 ms 203.125 ns" @@ -969,17 +1009,23 @@ fn deser_test() { } #[test] -fn test_i128_extremes() { +fn test_extremes() { let d = Duration::from_total_nanoseconds(i128::MAX); - println!("{}", d); + println!("d = {}", Duration::MIN_NEGATIVE - Duration::MIN_POSITIVE); assert_eq!(Duration::from_total_nanoseconds(d.total_nanoseconds()), d); let d = Duration::from_total_nanoseconds(i128::MIN + 1); - println!("{}", d); - // Test truncation - let d_min = Duration::from_total_nanoseconds(i128::MIN); - assert_eq!(d - d_min, 1 * TimeUnit::Nanosecond); - assert_eq!(d_min - d, -1 * TimeUnit::Nanosecond); - println!("{}", d - d_min); - assert_eq!(Duration::from_total_nanoseconds(d.total_nanoseconds()), d); - println!("{}", Duration::MAX); + assert_eq!(d, Duration::MIN); + // Test min positive + let d_min = Duration::from_total_nanoseconds(Duration::MIN_POSITIVE.total_nanoseconds()); + assert_eq!(d_min, Duration::MIN_POSITIVE); + // Test difference between min durations + dbg!(Duration::MIN_NEGATIVE, Duration::MIN_POSITIVE); + assert_eq!( + Duration::MIN_POSITIVE - Duration::MIN_NEGATIVE, + 2 * TimeUnit::Nanosecond + ); + assert_eq!( + Duration::MIN_NEGATIVE - Duration::MIN_POSITIVE, + -2 * TimeUnit::Nanosecond + ); } diff --git a/src/epoch.rs b/src/epoch.rs index 65419427..37780270 100644 --- a/src/epoch.rs +++ b/src/epoch.rs @@ -57,7 +57,7 @@ const JULY_YEARS: [i32; 11] = [ 1972, 1981, 1982, 1983, 1985, 1992, 1993, 1994, 1997, 2012, 2015, ]; -const USUAL_DAYS_PER_MONTH: [u32; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; +const USUAL_DAYS_PER_MONTH: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; /// Defines an Epoch in TAI (temps atomique international) in seconds past 1900 January 01 at midnight (like the Network Time Protocol). /// @@ -260,11 +260,11 @@ impl Epoch { /// Attempts to build an Epoch from the provided Gregorian date and time in TAI. pub fn maybe_from_gregorian_tai( year: i32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, nanos: u32, ) -> Result { Self::maybe_from_gregorian( @@ -283,11 +283,11 @@ impl Epoch { #[allow(clippy::too_many_arguments)] pub fn maybe_from_gregorian( year: i32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, nanos: u32, ts: TimeSystem, ) -> Result { @@ -295,7 +295,7 @@ impl Epoch { return Err(Errors::Carry); } - let mut seconds_wrt_1900 = TimeUnit::Day * (365 * (year - 1900).abs()); + let mut seconds_wrt_1900 = TimeUnit::Day * i64::from(365 * (year - 1900).abs()); // Now add the seconds for all the years prior to the current year for year in 1900..year { if is_leap_year(year) { @@ -304,18 +304,18 @@ impl Epoch { } // Add the seconds for the months prior to the current month for month in 0..month - 1 { - seconds_wrt_1900 += TimeUnit::Day * USUAL_DAYS_PER_MONTH[(month) as usize]; + seconds_wrt_1900 += TimeUnit::Day * i64::from(USUAL_DAYS_PER_MONTH[(month) as usize]); } if is_leap_year(year) && month > 2 { // NOTE: If on 29th of February, then the day is not finished yet, and therefore // the extra seconds are added below as per a normal day. seconds_wrt_1900 += TimeUnit::Day; } - seconds_wrt_1900 += TimeUnit::Day * (day - 1) - + TimeUnit::Hour * hour - + TimeUnit::Minute * minute - + TimeUnit::Second * second - + TimeUnit::Nanosecond * nanos; + seconds_wrt_1900 += TimeUnit::Day * i64::from(day - 1) + + TimeUnit::Hour * i64::from(hour) + + TimeUnit::Minute * i64::from(minute) + + TimeUnit::Second * i64::from(second) + + TimeUnit::Nanosecond * i64::from(nanos); if second == 60 { // Herein lies the whole ambiguity of leap seconds. Two different UTC dates exist at the // same number of second afters J1900.0. @@ -341,34 +341,34 @@ impl Epoch { /// Use maybe_from_gregorian_tai if unsure. pub fn from_gregorian_tai( year: i32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, nanos: u32, ) -> Self { Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, nanos) .expect("invalid Gregorian date") } - pub fn from_gregorian_tai_at_midnight(year: i32, month: u32, day: u32) -> Self { + pub fn from_gregorian_tai_at_midnight(year: i32, month: u8, day: u8) -> Self { Self::maybe_from_gregorian_tai(year, month, day, 0, 0, 0, 0) .expect("invalid Gregorian date") } - pub fn from_gregorian_tai_at_noon(year: i32, month: u32, day: u32) -> Self { + pub fn from_gregorian_tai_at_noon(year: i32, month: u8, day: u8) -> Self { Self::maybe_from_gregorian_tai(year, month, day, 12, 0, 0, 0) .expect("invalid Gregorian date") } pub fn from_gregorian_tai_hms( year: i32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, ) -> Self { Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, 0) .expect("invalid Gregorian date") @@ -377,11 +377,11 @@ impl Epoch { /// Attempts to build an Epoch from the provided Gregorian date and time in UTC. pub fn maybe_from_gregorian_utc( year: i32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, nanos: u32, ) -> Result { let mut if_tai = @@ -410,34 +410,34 @@ impl Epoch { /// Use maybe_from_gregorian_tai if unsure. pub fn from_gregorian_utc( year: i32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, nanos: u32, ) -> Self { Self::maybe_from_gregorian_utc(year, month, day, hour, minute, second, nanos) .expect("invalid Gregorian date") } - pub fn from_gregorian_utc_at_midnight(year: i32, month: u32, day: u32) -> Self { + pub fn from_gregorian_utc_at_midnight(year: i32, month: u8, day: u8) -> Self { Self::maybe_from_gregorian_utc(year, month, day, 0, 0, 0, 0) .expect("invalid Gregorian date") } - pub fn from_gregorian_utc_at_noon(year: i32, month: u32, day: u32) -> Self { + pub fn from_gregorian_utc_at_noon(year: i32, month: u8, day: u8) -> Self { Self::maybe_from_gregorian_utc(year, month, day, 12, 0, 0, 0) .expect("invalid Gregorian date") } pub fn from_gregorian_utc_hms( year: i32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, ) -> Self { Self::maybe_from_gregorian_utc(year, month, day, hour, minute, second, 0) .expect("invalid Gregorian date") @@ -740,21 +740,21 @@ impl Epoch { if ts == TimeSystem::UTC { Self::maybe_from_gregorian_utc( cap[1].to_owned().parse::()?, - cap[2].to_owned().parse::()?, - cap[3].to_owned().parse::()?, - cap[4].to_owned().parse::()?, - cap[5].to_owned().parse::()?, - cap[6].to_owned().parse::()?, + cap[2].to_owned().parse::()?, + cap[3].to_owned().parse::()?, + cap[4].to_owned().parse::()?, + cap[5].to_owned().parse::()?, + cap[6].to_owned().parse::()?, nanos, ) } else { Self::maybe_from_gregorian( cap[1].to_owned().parse::()?, - cap[2].to_owned().parse::()?, - cap[3].to_owned().parse::()?, - cap[4].to_owned().parse::()?, - cap[5].to_owned().parse::()?, - cap[6].to_owned().parse::()?, + cap[2].to_owned().parse::()?, + cap[3].to_owned().parse::()?, + cap[4].to_owned().parse::()?, + cap[5].to_owned().parse::()?, + cap[6].to_owned().parse::()?, nanos, ts, ) @@ -764,11 +764,11 @@ impl Epoch { // Asumme UTC Self::maybe_from_gregorian_utc( cap[1].to_owned().parse::()?, - cap[2].to_owned().parse::()?, - cap[3].to_owned().parse::()?, - cap[4].to_owned().parse::()?, - cap[5].to_owned().parse::()?, - cap[6].to_owned().parse::()?, + cap[2].to_owned().parse::()?, + cap[3].to_owned().parse::()?, + cap[4].to_owned().parse::()?, + cap[5].to_owned().parse::()?, + cap[6].to_owned().parse::()?, nanos, ) } @@ -1010,11 +1010,11 @@ impl fmt::Display for Epoch { /// Returns true if the provided Gregorian date is valid. Leap second days may have 60 seconds. pub fn is_gregorian_valid( year: i32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, nanos: u32, ) -> bool { let max_seconds = if (month == 12 || month == 6) From 7858a65049991e6b9057e1a9bb3a47c0cdfd7897 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Mon, 14 Feb 2022 14:14:41 -0700 Subject: [PATCH 11/17] Getting there... 19 tests pass out of 21 Signed-off-by: Christopher Rabotin --- src/duration.rs | 233 ++++++++++-------------------------------------- 1 file changed, 48 insertions(+), 185 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index bb0b9045..88573431 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -19,10 +19,7 @@ use std::fmt; use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use std::str::FromStr; -// const DAYS_PER_CENTURY_U: u128 = 36_525; const DAYS_PER_CENTURY_U64: u64 = 36_525; -const HOURS_PER_CENTURY: f64 = 24.0 * DAYS_PER_CENTURY; -const MINUTES_PER_CENTURY: f64 = 60.0 * HOURS_PER_CENTURY; const NANOSECONDS_PER_MICROSECOND: u64 = 1_000; const NANOSECONDS_PER_MILLISECOND: u64 = 1_000 * NANOSECONDS_PER_MICROSECOND; const NANOSECONDS_PER_SECOND: u64 = 1_000 * NANOSECONDS_PER_MILLISECOND; @@ -31,9 +28,6 @@ const NANOSECONDS_PER_HOUR: u64 = 60 * NANOSECONDS_PER_MINUTE; const NANOSECONDS_PER_DAY: u64 = 24 * NANOSECONDS_PER_HOUR; const NANOSECONDS_PER_CENTURY: u64 = DAYS_PER_CENTURY_U64 * NANOSECONDS_PER_DAY; -const fn centuries_per_u64_nanoseconds() -> i16 { - u64::MAX.div_euclid(NANOSECONDS_PER_CENTURY) as i16 -} /// Defines generally usable durations for nanosecond precision valid for 32,768 centuries in either direction, and only on 80 bits / 10 octets. /// /// **Important conventions:** @@ -143,11 +137,11 @@ impl Duration { #[must_use] pub fn in_seconds(&self) -> f64 { // Compute the seconds and nanoseconds that we know this fits on a 64bit float - let (seconds_u64, nanoseconds_u64) = - self.nanoseconds.div_rem_euclid(NANOSECONDS_PER_CENTURY); - f64::from(self.centuries) * SECONDS_PER_DAY * DAYS_PER_CENTURY - + (seconds_u64 as f64) - + (nanoseconds_u64 as f64) * 1e-9 + let seconds = self.nanoseconds.div_euclid(NANOSECONDS_PER_SECOND); + let subseconds = self.nanoseconds.rem_euclid(NANOSECONDS_PER_SECOND); + f64::from(self.centuries) * SECONDS_PER_CENTURY + + (seconds as f64) + + (subseconds as f64) * 1e-9 } /// Returns the value of this duration in the requested unit. @@ -256,146 +250,21 @@ macro_rules! impl_ops_for_type { impl Mul<$type> for TimeUnit { type Output = Duration; + /// Converts the input values to i128 and creates a duration from that + /// This method will necessarily ignore durations below nanoseconds fn mul(self, q: $type) -> Duration { - match self { - TimeUnit::Century => { - todo!(); - // self * qee * DAYS_PER_CENTURY_U64ee - } - TimeUnit::Day => { - // The centuries will be a round number here so the `as` conversion should work. - let centuries_typed = q.div_euclid(DAYS_PER_CENTURY as $type); - let centuries = if centuries_typed > (i16::MAX as $type) { - return Duration::MAX; - } else if centuries_typed < (i16::MIN as $type) { - return Duration::MIN; - } else { - centuries_typed as i16 - }; - - // rem_euclid returns the nonnegative number, so we can cast that directly into u64 - let nanoseconds = - q.rem_euclid(DAYS_PER_CENTURY as $type) as u64 * NANOSECONDS_PER_DAY; - Duration { - centuries, - nanoseconds, - } - } - TimeUnit::Hour => { - // The centuries will be a round number here so the `as` conversion should work. - let centuries_typed = q.div_euclid(HOURS_PER_CENTURY as $type); - let centuries = if centuries_typed > (i16::MAX as $type) { - return Duration::MAX; - } else if centuries_typed < (i16::MIN as $type) { - return Duration::MIN; - } else { - centuries_typed as i16 - }; - - // rem_euclid returns the nonnegative number, so we can cast that directly into u64 - let nanoseconds = - q.rem_euclid(HOURS_PER_CENTURY as $type) as u64 * NANOSECONDS_PER_HOUR; - Duration { - centuries, - nanoseconds, - } - } - TimeUnit::Minute => { - // The centuries will be a round number here so the `as` conversion should work. - let centuries_typed = q.div_euclid(MINUTES_PER_CENTURY as $type); - let centuries = if centuries_typed > (i16::MAX as $type) { - return Duration::MAX; - } else if centuries_typed < (i16::MIN as $type) { - return Duration::MIN; - } else { - centuries_typed as i16 - }; - - // rem_euclid returns the nonnegative number, so we can cast that directly into u64 - let nanoseconds = q.rem_euclid(MINUTES_PER_CENTURY as $type) as u64 - * NANOSECONDS_PER_MINUTE; - Duration { - centuries, - nanoseconds, - } - } - TimeUnit::Second => { - // The centuries will be a round number here so the `as` conversion should work. - let centuries_typed = q.div_euclid(SECONDS_PER_CENTURY as $type); - let centuries = if centuries_typed > (i16::MAX as $type) { - return Duration::MAX; - } else if centuries_typed < (i16::MIN as $type) { - return Duration::MIN; - } else { - centuries_typed as i16 - }; - - // rem_euclid returns the nonnegative number, so we can cast that directly into u64 - let nanoseconds = q.rem_euclid(SECONDS_PER_CENTURY as $type) as u64 - * NANOSECONDS_PER_SECOND; - Duration { - centuries, - nanoseconds, - } - } - TimeUnit::Millisecond => { - // The centuries will be a round number here so the `as` conversion should work. - let centuries_typed = q.div_euclid((SECONDS_PER_CENTURY * 1e-3) as $type); - let centuries = if centuries_typed > (i16::MAX as $type) { - return Duration::MAX; - } else if centuries_typed < (i16::MIN as $type) { - return Duration::MIN; - } else { - centuries_typed as i16 - }; - - // rem_euclid returns the nonnegative number, so we can cast that directly into u64 - let nanoseconds = - q.rem_euclid(DAYS_PER_CENTURY as $type) as u64 * NANOSECONDS_PER_SECOND; - Duration { - centuries, - nanoseconds, - } - } - TimeUnit::Microsecond => { - // The centuries will be a round number here so the `as` conversion should work. - let centuries_typed = q.div_euclid((SECONDS_PER_CENTURY * 1e-6) as $type); - let centuries = if centuries_typed > (i16::MAX as $type) { - return Duration::MAX; - } else if centuries_typed < (i16::MIN as $type) { - return Duration::MIN; - } else { - centuries_typed as i16 - }; - - // rem_euclid returns the nonnegative number, so we can cast that directly into u64 - let nanoseconds = q.rem_euclid(DAYS_PER_CENTURY as $type) as u64 - * NANOSECONDS_PER_MICROSECOND; - Duration { - centuries, - nanoseconds, - } - } - TimeUnit::Nanosecond => { - // The centuries will be a round number here so the `as` conversion should work. - let centuries_typed = q.div_euclid(NANOSECONDS_PER_CENTURY as $type); - let centuries = if centuries_typed > (i16::MAX as $type) { - return Duration::MAX; - } else if centuries_typed < (i16::MIN as $type) { - return Duration::MIN; - } else { - centuries_typed as i16 - }; - - // rem_euclid returns the nonnegative number, so we can cast that directly into u64 - let nanoseconds = if centuries >= 0 { - q.rem_euclid(NANOSECONDS_PER_CENTURY as $type) as u64 - } else { - ((NANOSECONDS_PER_CENTURY as $type) + q + (1 as $type)) as u64 - }; - Duration::from_parts(centuries, nanoseconds) - } - } + let total_ns = match self { + TimeUnit::Century => (q * (NANOSECONDS_PER_CENTURY as $type)) as i128, + TimeUnit::Day => (q * (NANOSECONDS_PER_DAY as $type)) as i128, + TimeUnit::Hour => (q * (NANOSECONDS_PER_HOUR as $type)) as i128, + TimeUnit::Minute => (q * (NANOSECONDS_PER_MINUTE as $type)) as i128, + TimeUnit::Second => (q * (NANOSECONDS_PER_SECOND as $type)) as i128, + TimeUnit::Millisecond => (q * (NANOSECONDS_PER_MILLISECOND as $type)) as i128, + TimeUnit::Microsecond => (q * (NANOSECONDS_PER_MICROSECOND as $type)) as i128, + TimeUnit::Nanosecond => q as i128, + }; + + Duration::from_total_nanoseconds(total_ns) } } @@ -495,18 +364,16 @@ impl fmt::Display for Duration { } let values = [days, hours, minutes, seconds, milli, us, nano]; - let units = ["days", "h", "min", "s", "ms", "us", "ns"]; - let mut prev_ignored = true; + let units = ["days", "h", "min", "s", "ms", "μs", "ns"]; + + let mut insert_space = false; for (val, unit) in values.iter().zip(units.iter()) { if *val > 0 { - if !prev_ignored { - // Add space + if insert_space { write!(f, " ")?; } write!(f, "{} {}", val, unit)?; - prev_ignored = false; - } else { - prev_ignored = true; + insert_space = true; } } Ok(()) @@ -594,11 +461,11 @@ impl Sub for Duration { me.nanoseconds = me.nanoseconds + NANOSECONDS_PER_CENTURY - rhs.nanoseconds; } Some(nanos) => { - if me.centuries >= 0 { - me.nanoseconds = nanos - } else { - // Account for the zero + if self.centuries >= 0 && rhs.centuries < 0 { + // Account for zero crossing me.nanoseconds = nanos + 1 + } else { + me.nanoseconds = nanos } } }; @@ -829,15 +696,7 @@ impl TimeUnit { } impl_ops_for_type!(f64); -// impl_ops_for_type!(u8); -// impl_ops_for_type!(i8); -// impl_ops_for_type!(u16); -// impl_ops_for_type!(i16); -// impl_ops_for_type!(u32); -// impl_ops_for_type!(i32); -// impl_ops_for_type!(u64); impl_ops_for_type!(i64); -// impl_ops_for_type!(u128); #[test] fn time_unit() { @@ -859,6 +718,7 @@ fn time_unit() { assert_eq!(5.0 * TimeUnit::Nanosecond, TimeUnit::Nanosecond * 5); let d: Duration = 1.0 * TimeUnit::Hour / 3 - 20 * TimeUnit::Minute; + dbg!(d); assert!(d.abs() < TimeUnit::Nanosecond); assert_eq!(3 * (20 * TimeUnit::Minute), TimeUnit::Hour); @@ -920,11 +780,10 @@ fn duration_print() { "5 h 256 ms 1 ns" ); - assert_eq!( - format!("{}", TimeUnit::Hour + TimeUnit::Second), - "1 h 0 min 1 s" - ); + assert_eq!(format!("{}", TimeUnit::Hour + TimeUnit::Second), "1 h 1 s"); + // NOTE: We _try_ to specify down to a half of a nanosecond, but duration is NOT + // more precise than that, so it only actually stores to that number. assert_eq!( format!( "{}", @@ -933,7 +792,7 @@ fn duration_print() { + TimeUnit::Microsecond + TimeUnit::Nanosecond * 3.5 ), - "5 h 256 ms 1003.5 ns" + "5 h 256 ms 1 μs 3 ns" ); // Check printing negative durations only shows one negative sign @@ -945,9 +804,9 @@ fn duration_print() { assert_eq!( format!( "{}", - TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256 + TimeUnit::Nanosecond * -3.5 + TimeUnit::Hour * -5 + TimeUnit::Millisecond * -256 + TimeUnit::Nanosecond * -3 ), - "-5 h 256 ms 3.5 ns" + "-5 h 256 ms 3 ns" ); assert_eq!( @@ -963,10 +822,7 @@ fn duration_print() { // Check that we support nanoseconds pas GPS time let now = TimeUnit::Nanosecond * 1286495254000000123; - assert_eq!( - format!("{}", now), - "14889 days 23 h 47 min 34 s 0 ms 203.125 ns" - ); + assert_eq!(format!("{}", now), "14889 days 23 h 47 min 34 s 123 ns"); let arbitrary = 14889.days() + 23.hours() @@ -976,7 +832,7 @@ fn duration_print() { + 123.nanoseconds(); assert_eq!( format!("{}", arbitrary), - "14889 days 23 h 47 min 34 s 0 ms 123 ns" + "14889 days 23 h 47 min 34 s 123 ns" ); // Test fractional @@ -984,11 +840,11 @@ fn duration_print() { let third_hour = (1.0 / 3.0) * TimeUnit::Hour; let sum: Duration = quarter_hour + third_hour; println!( - "Duration: {}\nFloating: {}", + "Proof that Duration is more precise than f64: {} vs {}", sum.in_unit_f64(TimeUnit::Minute), (1.0 / 4.0 + 1.0 / 3.0) * 60.0 ); - assert_eq!(format!("{}", sum), "35 min 0 s"); // Note the automatic unit selection + assert_eq!(format!("{}", sum), "35 min"); let quarter_hour = -0.25 * TimeUnit::Hour; let third_hour: Duration = -1 * TimeUnit::Hour / 3; @@ -996,7 +852,8 @@ fn duration_print() { let delta = sum.in_unit(TimeUnit::Millisecond).floor() - sum.in_unit(TimeUnit::Second).floor() * 1000.0; println!("{:?}", (delta * -1.0) == 0.0); - assert_eq!(format!("{}", sum), "-35 min 0 s"); // Note the automatic unit selection + dbg!(sum); + assert_eq!(format!("{}", sum), "-35 min"); // Note the automatic unit selection } #[test] @@ -1011,7 +868,7 @@ fn deser_test() { #[test] fn test_extremes() { let d = Duration::from_total_nanoseconds(i128::MAX); - println!("d = {}", Duration::MIN_NEGATIVE - Duration::MIN_POSITIVE); + assert_eq!(Duration::from_total_nanoseconds(d.total_nanoseconds()), d); let d = Duration::from_total_nanoseconds(i128::MIN + 1); assert_eq!(d, Duration::MIN); @@ -1028,4 +885,10 @@ fn test_extremes() { Duration::MIN_NEGATIVE - Duration::MIN_POSITIVE, -2 * TimeUnit::Nanosecond ); + assert_eq!( + Duration::from_total_nanoseconds(2), + 2 * TimeUnit::Nanosecond + ); + // Check that we do not support more precise than nanosecond + assert_eq!(TimeUnit::Nanosecond * 3.5, TimeUnit::Nanosecond * 3); } From eccdae77e6de9bbd126588ef4a9d5af64e276a93 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Mon, 14 Feb 2022 15:43:06 -0700 Subject: [PATCH 12/17] Add Neg test Signed-off-by: Christopher Rabotin --- Cargo.toml | 1 - src/duration.rs | 64 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2be51461..6472ddfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ rand_distr = "0.4" regex = "1.3" serde = "1.0" serde_derive = "1.0" -twofloat = "0.4.1" [dev-dependencies] serde_derive = "1.0" diff --git a/src/duration.rs b/src/duration.rs index 88573431..5d039861 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -2,13 +2,10 @@ extern crate divrem; extern crate regex; extern crate serde; extern crate serde_derive; -extern crate twofloat; use self::divrem::DivRemEuclid; use self::regex::Regex; use self::serde::{de, Deserialize, Deserializer}; -#[allow(unused_imports)] -use self::twofloat::TwoFloat; use crate::{ Errors, DAYS_PER_CENTURY, SECONDS_PER_CENTURY, SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE, @@ -36,12 +33,33 @@ const NANOSECONDS_PER_CENTURY: u64 = DAYS_PER_CENTURY_U64 * NANOSECONDS_PER_DAY; /// a durationn with centuries = -1 and nanoseconds = 0 is _a smaller duration_ than centuries = -1 and nanoseconds = 1. /// That difference is exactly 1 nanoseconds, where the former duration is "closer to zero" than the latter. /// As such, the largest negative duration that can be represented sets the centuries to i16::MAX and its nanoseconds to NANOSECONDS_PER_CENTURY. -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +#[derive(Clone, Copy, Debug, PartialOrd, Eq, Ord)] pub struct Duration { centuries: i16, nanoseconds: u64, } +impl PartialEq for Duration { + fn eq(&self, other: &Self) -> bool { + if self.centuries == other.centuries { + self.nanoseconds == other.nanoseconds + } else if (self.centuries - other.centuries).abs() == 1 + && (self.centuries == 0 || other.centuries == 0) + { + // Special case where we're at the zero crossing + if self.centuries < 0 { + // Self is negative, + (NANOSECONDS_PER_CENTURY - self.nanoseconds) == other.nanoseconds + } else { + // Other is negative + (NANOSECONDS_PER_CENTURY - other.nanoseconds) == self.nanoseconds + } + } else { + false + } + } +} + impl Duration { fn normalize(&mut self) { let extra_centuries = self.nanoseconds.div_euclid(NANOSECONDS_PER_CENTURY); @@ -174,9 +192,14 @@ impl Duration { /// Decomposes a Duration in its sign, days, #[must_use] pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64) { - let total_ns = self.total_nanoseconds(); + let sign = self.centuries.signum() as i8; + let total_ns = if sign != -1 { + self.total_nanoseconds() + } else { + dbg!((-*self).total_nanoseconds()) + }; - let sign = total_ns.signum() as i8; + // let sign = total_ns.signum() as i8; let ns_left = total_ns.abs(); let (days, ns_left) = ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_DAY)); @@ -541,10 +564,10 @@ impl Neg for Duration { #[must_use] fn neg(self) -> Self::Output { - Self { - centuries: -self.centuries - 1, - nanoseconds: NANOSECONDS_PER_CENTURY - self.nanoseconds, - } + Self::from_parts( + -self.centuries - 1, + NANOSECONDS_PER_CENTURY - self.nanoseconds, + ) } } @@ -744,18 +767,13 @@ fn time_unit() { let sum: Duration = quarter_hour + third_hour; dbg!(quarter_hour, third_hour, sum); assert!((sum.in_unit_f64(TimeUnit::Minute) - 35.0).abs() < EPSILON); - println!( - "Duration: {}\nFloating: {}", - sum.in_unit_f64(TimeUnit::Minute), - (1.0 / 4.0 + 1.0 / 3.0) * 60.0 - ); let quarter_hour = -0.25 * TimeUnit::Hour; let third_hour: Duration = -1 * TimeUnit::Hour / 3; let sum: Duration = quarter_hour + third_hour; - let delta = sum.in_unit(TimeUnit::Millisecond).floor() - - sum.in_unit(TimeUnit::Second).floor() * TwoFloat::from(1000.0); - println!("{:?}", delta * TwoFloat::from(-1) == TwoFloat::from(0)); + let delta = + sum.in_unit(TimeUnit::Millisecond).floor() - sum.in_unit(TimeUnit::Second).floor() * 1000.0; + println!("{:?}", sum.in_seconds()); assert!((sum.in_unit_f64(TimeUnit::Minute) + 35.0).abs() < EPSILON); } @@ -849,11 +867,19 @@ fn duration_print() { let quarter_hour = -0.25 * TimeUnit::Hour; let third_hour: Duration = -1 * TimeUnit::Hour / 3; let sum: Duration = quarter_hour + third_hour; + println!("{}", -third_hour); let delta = sum.in_unit(TimeUnit::Millisecond).floor() - sum.in_unit(TimeUnit::Second).floor() * 1000.0; println!("{:?}", (delta * -1.0) == 0.0); dbg!(sum); - assert_eq!(format!("{}", sum), "-35 min"); // Note the automatic unit selection + assert_eq!(format!("{}", sum), "-35 min"); +} + +#[test] +fn test_neg() { + assert_eq!(Duration::MIN_NEGATIVE, -Duration::MIN_POSITIVE); + assert_eq!(Duration::MIN_POSITIVE, -Duration::MIN_NEGATIVE); + assert_eq!(2.nanoseconds(), -(2.0.nanoseconds())); } #[test] From e6195676e85a887594b442cc09118467559658dd Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Mon, 14 Feb 2022 23:14:37 -0700 Subject: [PATCH 13/17] Getting there! Just gotta fix the TDB test Signed-off-by: Christopher Rabotin --- src/duration.rs | 204 +++++++++++++++++++++++++++++++----------------- src/lib.rs | 11 ++- 2 files changed, 141 insertions(+), 74 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 5d039861..84c8ccbb 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -133,8 +133,64 @@ impl Duration { /// Returns the total nanoseconds in a signed 128 bit integer #[must_use] pub fn total_nanoseconds(self) -> i128 { - i128::from(self.centuries) * i128::from(NANOSECONDS_PER_CENTURY) - + i128::from(self.nanoseconds) + if self.centuries == -1 { + -1 * i128::from(NANOSECONDS_PER_CENTURY - self.nanoseconds) + } else if self.centuries >= 0 { + i128::from(self.centuries) * i128::from(NANOSECONDS_PER_CENTURY) + + i128::from(self.nanoseconds) + } else { + // Centuries negative by a decent amount + i128::from(self.centuries + 1) * i128::from(NANOSECONDS_PER_CENTURY) + + i128::from(self.nanoseconds) + } + } + + /// Returns the truncated nanoseconds in a signed 64 bit integer, if the duration fits. + #[must_use] + pub fn try_truncated_nanoseconds(self) -> Result { + // If it fits, we know that the nanoseconds also fit + if self.centuries.abs() >= 3 { + Err(Errors::Overflow) + } else if self.centuries == -1 { + Ok(-1 * (NANOSECONDS_PER_CENTURY - self.nanoseconds) as i64) + } else if self.centuries >= 0 { + Ok( + i64::from(self.centuries) * NANOSECONDS_PER_CENTURY as i64 + + self.nanoseconds as i64, + ) + } else { + // Centuries negative by a decent amount + Ok( + i64::from(self.centuries + 1) * NANOSECONDS_PER_CENTURY as i64 + + self.nanoseconds as i64, + ) + } + } + + /// Returns the truncated nanoseconds in a signed 64 bit integer, if the duration fits. + /// WARNING: This function will NOT fail and will return the i64::MIN or i64::MAX depending on + /// the sign of the centuries if the Duration does not fit on aa i64 + #[must_use] + pub fn truncated_nanoseconds(self) -> i64 { + match self.try_truncated_nanoseconds() { + Ok(val) => val, + Err(_) => { + if self.centuries < 0 { + i64::MIN + } else { + i64::MAX + } + } + } + } + + /// Create a new duration from the truncated nanoseconds (+/- 2927.1 years of duration) + pub fn from_truncated_nanoseconds(nanos: i64) -> Self { + if nanos < 0 { + Self::from_parts(-1, NANOSECONDS_PER_CENTURY - (nanos.abs() as u64)) + } else { + Self::from_parts(0, nanos.abs() as u64) + } } /// Creates a new duration from the provided unit @@ -193,13 +249,8 @@ impl Duration { #[must_use] pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64) { let sign = self.centuries.signum() as i8; - let total_ns = if sign != -1 { - self.total_nanoseconds() - } else { - dbg!((-*self).total_nanoseconds()) - }; + let total_ns = self.total_nanoseconds(); - // let sign = total_ns.signum() as i8; let ns_left = total_ns.abs(); let (days, ns_left) = ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_DAY)); @@ -277,17 +328,20 @@ macro_rules! impl_ops_for_type { /// This method will necessarily ignore durations below nanoseconds fn mul(self, q: $type) -> Duration { let total_ns = match self { - TimeUnit::Century => (q * (NANOSECONDS_PER_CENTURY as $type)) as i128, - TimeUnit::Day => (q * (NANOSECONDS_PER_DAY as $type)) as i128, - TimeUnit::Hour => (q * (NANOSECONDS_PER_HOUR as $type)) as i128, - TimeUnit::Minute => (q * (NANOSECONDS_PER_MINUTE as $type)) as i128, - TimeUnit::Second => (q * (NANOSECONDS_PER_SECOND as $type)) as i128, - TimeUnit::Millisecond => (q * (NANOSECONDS_PER_MILLISECOND as $type)) as i128, - TimeUnit::Microsecond => (q * (NANOSECONDS_PER_MICROSECOND as $type)) as i128, - TimeUnit::Nanosecond => q as i128, + TimeUnit::Century => q * (NANOSECONDS_PER_CENTURY as $type), + TimeUnit::Day => q * (NANOSECONDS_PER_DAY as $type), + TimeUnit::Hour => q * (NANOSECONDS_PER_HOUR as $type), + TimeUnit::Minute => q * (NANOSECONDS_PER_MINUTE as $type), + TimeUnit::Second => q * (NANOSECONDS_PER_SECOND as $type), + TimeUnit::Millisecond => q * (NANOSECONDS_PER_MILLISECOND as $type), + TimeUnit::Microsecond => q * (NANOSECONDS_PER_MICROSECOND as $type), + TimeUnit::Nanosecond => q, }; - - Duration::from_total_nanoseconds(total_ns) + if total_ns.abs() < (i64::MAX as $type) { + Duration::from_truncated_nanoseconds(total_ns as i64) + } else { + Duration::from_total_nanoseconds(total_ns as i128) + } } } @@ -302,64 +356,20 @@ macro_rules! impl_ops_for_type { impl Mul<$type> for Duration { type Output = Duration; fn mul(self, q: $type) -> Self::Output { - // Compute as nanoseconds to align with Duration, and divide as needed. - let mut as_duration = q * TimeUnit::Nanosecond; - let mut me = self; - me.centuries = me.centuries.saturating_mul(as_duration.centuries); - if me.centuries == i16::MAX { - return Self::Output::MAX; - } else if me.centuries == i16::MIN { - return Self::Output::MIN; - } - me.nanoseconds = me.nanoseconds.saturating_mul(as_duration.nanoseconds); - if me.nanoseconds == u64::MAX { - // Increment the centuries and decrease nanoseconds - as_duration.centuries += 1; - as_duration.nanoseconds -= NANOSECONDS_PER_CENTURY; - // And repeat - me.centuries = me.centuries.saturating_mul(as_duration.centuries); - if me.centuries == i16::MAX { - return Self::Output::MAX; - } else if me.centuries == i16::MIN { - return Self::Output::MIN; - } - me.nanoseconds = me.nanoseconds.saturating_mul(as_duration.nanoseconds); - } - me + Duration::from_total_nanoseconds( + self.total_nanoseconds() + .saturating_mul((q * TimeUnit::Nanosecond).total_nanoseconds()), + ) } } impl Div<$type> for Duration { type Output = Duration; fn div(self, q: $type) -> Self::Output { - // Compute as nanoseconds to align with Duration, and divide as needed. - let mut as_duration = q * TimeUnit::Nanosecond; - let mut me = self; - if as_duration.centuries > 0 { - me.centuries = me.centuries.saturating_div(as_duration.centuries); - if me.centuries == i16::MAX { - return Self::Output::MAX; - } else if me.centuries == i16::MIN { - return Self::Output::MIN; - } - } - if as_duration.nanoseconds > 0 { - me.nanoseconds = me.nanoseconds.saturating_div(as_duration.nanoseconds); - if me.nanoseconds == u64::MAX { - // Increment the centuries and decrease nanoseconds - as_duration.centuries += 1; - as_duration.nanoseconds -= NANOSECONDS_PER_CENTURY; - // And repeat - me.centuries = me.centuries.saturating_mul(as_duration.centuries); - if me.centuries == i16::MAX { - return Self::Output::MAX; - } else if me.centuries == i16::MIN { - return Self::Output::MIN; - } - me.nanoseconds = me.nanoseconds.saturating_mul(as_duration.nanoseconds); - } - } - me + Duration::from_total_nanoseconds( + self.total_nanoseconds() + .saturating_div((q * TimeUnit::Nanosecond).total_nanoseconds()), + ) } } @@ -444,6 +454,9 @@ impl Add for Duration { } Some(centuries) => { me.centuries = centuries; + // if self.centuries < 0 && rhs.centuries >= 0 { + // me.centuries += 1; + // } } } // We can safely add two nanoseconds together because we can fit five centuries in one u64 @@ -773,6 +786,7 @@ fn time_unit() { let sum: Duration = quarter_hour + third_hour; let delta = sum.in_unit(TimeUnit::Millisecond).floor() - sum.in_unit(TimeUnit::Second).floor() * 1000.0; + assert!(delta < std::f64::EPSILON); println!("{:?}", sum.in_seconds()); assert!((sum.in_unit_f64(TimeUnit::Minute) + 35.0).abs() < EPSILON); } @@ -867,7 +881,7 @@ fn duration_print() { let quarter_hour = -0.25 * TimeUnit::Hour; let third_hour: Duration = -1 * TimeUnit::Hour / 3; let sum: Duration = quarter_hour + third_hour; - println!("{}", -third_hour); + dbg!(sum.total_nanoseconds()); let delta = sum.in_unit(TimeUnit::Millisecond).floor() - sum.in_unit(TimeUnit::Second).floor() * 1000.0; println!("{:?}", (delta * -1.0) == 0.0); @@ -875,6 +889,36 @@ fn duration_print() { assert_eq!(format!("{}", sum), "-35 min"); } +#[test] +fn test_ops() { + assert_eq!( + (0.25 * TimeUnit::Hour).total_nanoseconds(), + (15 * NANOSECONDS_PER_MINUTE).into() + ); + + assert_eq!( + (-0.25 * TimeUnit::Hour).total_nanoseconds(), + i128::from(15 * NANOSECONDS_PER_MINUTE) * -1 + ); + + assert_eq!( + (-0.25 * TimeUnit::Hour - 0.25 * TimeUnit::Hour).total_nanoseconds(), + i128::from(30 * NANOSECONDS_PER_MINUTE) * -1 + ); + + println!("{}", -0.25 * TimeUnit::Hour + (-0.25 * TimeUnit::Hour)); + + assert_eq!( + Duration::MIN_POSITIVE + 4 * Duration::MIN_POSITIVE, + 5 * TimeUnit::Nanosecond + ); + + assert_eq!( + Duration::MIN_NEGATIVE + 4 * Duration::MIN_NEGATIVE, + -5 * TimeUnit::Nanosecond + ); +} + #[test] fn test_neg() { assert_eq!(Duration::MIN_NEGATIVE, -Duration::MIN_POSITIVE); @@ -917,4 +961,22 @@ fn test_extremes() { ); // Check that we do not support more precise than nanosecond assert_eq!(TimeUnit::Nanosecond * 3.5, TimeUnit::Nanosecond * 3); + + assert_eq!( + Duration::MIN_POSITIVE + Duration::MIN_NEGATIVE, + Duration::ZERO + ); + + assert_eq!( + Duration::MIN_NEGATIVE + Duration::MIN_NEGATIVE, + -2 * TimeUnit::Nanosecond + ); + + // Add i64 tests + let d = Duration::from_truncated_nanoseconds(i64::MAX); + println!("{}", d); + assert_eq!( + Duration::from_truncated_nanoseconds(d.truncated_nanoseconds()), + d + ); } diff --git a/src/lib.rs b/src/lib.rs index f3a3a28d..d06a837b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,16 +199,21 @@ pub enum Errors { ParseError(String), /// Raised when trying to initialize an Epoch or Duration from its hi and lo values, but these overlap ConversionOverlapError(f64, f64), + Overflow, } impl fmt::Display for Errors { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Errors::Carry => write!(f, "a carry error (e.g. 61 seconds)"), - Errors::ParseError(ref msg) => write!(f, "ParseError: {}", msg), - Errors::ConversionOverlapError(hi, lo) => { + Self::Carry => write!(f, "a carry error (e.g. 61 seconds)"), + Self::ParseError(ref msg) => write!(f, "ParseError: {}", msg), + Self::ConversionOverlapError(hi, lo) => { write!(f, "hi and lo values overlap: {}, {}", hi, lo) } + Self::Overflow => write!( + f, + "overflow occured when trying to convert Duration information" + ), } } } From 28fb99f3cec5e66a4b04a448576723c812778cf3 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Mon, 14 Feb 2022 23:27:00 -0700 Subject: [PATCH 14/17] Removed divrem crate Signed-off-by: Christopher Rabotin --- Cargo.toml | 1 - src/duration.rs | 87 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6472ddfd..3c8b27e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ readme = "README.md" license = "Apache-2.0" [dependencies] -divrem = "1.0.0" rand = "0.8" rand_distr = "0.4" regex = "1.3" diff --git a/src/duration.rs b/src/duration.rs index 84c8ccbb..bb9751cd 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -1,9 +1,7 @@ -extern crate divrem; extern crate regex; extern crate serde; extern crate serde_derive; -use self::divrem::DivRemEuclid; use self::regex::Regex; use self::serde::{de, Deserialize, Deserializer}; use crate::{ @@ -249,30 +247,59 @@ impl Duration { #[must_use] pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64) { let sign = self.centuries.signum() as i8; - let total_ns = self.total_nanoseconds(); - - let ns_left = total_ns.abs(); - - let (days, ns_left) = ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_DAY)); - let (hours, ns_left) = ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_HOUR)); - let (minutes, ns_left) = ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_MINUTE)); - let (seconds, ns_left) = ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_SECOND)); - let (milliseconds, ns_left) = - ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_MILLISECOND)); - let (microseconds, ns_left) = - ns_left.div_rem_euclid(i128::from(NANOSECONDS_PER_MICROSECOND)); - - // Everything should fit in the expected types now - ( - sign, - days.try_into().unwrap(), - hours.try_into().unwrap(), - minutes.try_into().unwrap(), - seconds.try_into().unwrap(), - milliseconds.try_into().unwrap(), - microseconds.try_into().unwrap(), - ns_left.try_into().unwrap(), - ) + + match self.try_truncated_nanoseconds() { + Ok(total_ns) => { + let ns_left = total_ns.abs(); + + let (days, ns_left) = div_rem_i64(ns_left, NANOSECONDS_PER_DAY as i64); + let (hours, ns_left) = div_rem_i64(ns_left, NANOSECONDS_PER_HOUR as i64); + let (minutes, ns_left) = div_rem_i64(ns_left, NANOSECONDS_PER_MINUTE as i64); + let (seconds, ns_left) = div_rem_i64(ns_left, NANOSECONDS_PER_SECOND as i64); + let (milliseconds, ns_left) = + div_rem_i64(ns_left, NANOSECONDS_PER_MILLISECOND as i64); + let (microseconds, ns_left) = + div_rem_i64(ns_left, NANOSECONDS_PER_MICROSECOND as i64); + + // Everything should fit in the expected types now + ( + sign, + days.try_into().unwrap(), + hours.try_into().unwrap(), + minutes.try_into().unwrap(), + seconds.try_into().unwrap(), + milliseconds.try_into().unwrap(), + microseconds.try_into().unwrap(), + ns_left.try_into().unwrap(), + ) + } + Err(_) => { + // Doesn't fit on a i64, so let's use the slower i128 + let total_ns = self.total_nanoseconds(); + let ns_left = total_ns.abs(); + + let (days, ns_left) = div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_DAY)); + let (hours, ns_left) = div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_HOUR)); + let (minutes, ns_left) = div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_MINUTE)); + let (seconds, ns_left) = div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_SECOND)); + let (milliseconds, ns_left) = + div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_MILLISECOND)); + let (microseconds, ns_left) = + div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_MICROSECOND)); + + // Everything should fit in the expected types now + ( + sign, + days.try_into().unwrap(), + hours.try_into().unwrap(), + minutes.try_into().unwrap(), + seconds.try_into().unwrap(), + milliseconds.try_into().unwrap(), + microseconds.try_into().unwrap(), + ns_left.try_into().unwrap(), + ) + } + } } /// A duration of exactly zero nanoseconds @@ -734,6 +761,14 @@ impl TimeUnit { impl_ops_for_type!(f64); impl_ops_for_type!(i64); +fn div_rem_i128(me: i128, rhs: i128) -> (i128, i128) { + (me.div_euclid(rhs), me.rem_euclid(rhs)) +} + +fn div_rem_i64(me: i64, rhs: i64) -> (i64, i64) { + (me.div_euclid(rhs), me.rem_euclid(rhs)) +} + #[test] fn time_unit() { use std::f64::EPSILON; From ddd564f3272cd827610a6915a25f64a7c8a8e6f9 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Tue, 15 Feb 2022 12:23:20 -0700 Subject: [PATCH 15/17] TDB precision is now actually better than ET (this is good!) Signed-off-by: Christopher Rabotin --- README.md | 22 ++++++++++++++-------- src/epoch.rs | 27 ++++++++++++++------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index f4ec75fa..b5f385f5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # hifitime 2 -Precise date and time handling in Rust built on top of a tuple of two floats. -The Epoch used is TAI Epoch of 01 Jan 1900 at midnight, but that should not matter in -day-to-day use of this library. +Precise date and time handling in Rust built on top of a tuple of two integers allowing representation of durations and epochs with the exactly one nanosecond precision for 32,768 years _before_ 01 January 1900 and 32,767 years _after_ that reference epoch, all that in only 80 bits! + +The Epoch used is TAI Epoch of 01 Jan 1900 at midnight, but that should not matter in day-to-day use of this library. [![Build Status](https://app.travis-ci.com/nyx-space/hifitime.svg?branch=master)](https://app.travis-ci.com/nyx-space/hifitime) @@ -41,30 +41,32 @@ I believe that SPICE uses TDB for all dates after J2000 TT. Hence, in the follow The following examples are executed as part of the standard test suite (cf. the function called `spice_et_tdb`). +_Note:_ the differences shown here are likely due to a combination of SPICE using a different formulation for the calculation (using the constants in the SPICE kernels) and computing everything on a 64-bit floating point value. [By design](https://en.wikipedia.org/wiki/IEEE_754), a 64-bit floating point value has approximation errors. Hifitime performs all calculations on integers, which do not suffer from rounding errors. + ## Case 1 In SPICE, we chose to convert the UTC date `2012-02-07 11:22:33 UTC` into Ephemeris Time. SPICE responds with `381885819.18493587`. Initializing the same UTC date in hifitime and requesting the TDB leads to `381885819.18493646`, which is an error of **596.05 nanoseconds**. ## Case 2 In SPICE, we chose to convert the UTC date `2002-02-07 00:00:00.000 UTC` into Ephemeris Time. SPICE responds with `66312064.18493876`. -Initializing the same UTC date in hifitime and requesting the TDB leads to a difference **618.39 nanoseconds**. +Initializing the same UTC date in hifitime and requesting the TDB leads to a difference **633.29 nanoseconds**. ## Case 3 This tests that we can correctly compute TDB time which will have a negative number of days because the UTC input is prior to J2000 TT. In SPICE, we chose to convert the UTC date `1996-02-07 11:22:33 UTC` into Ephemeris Time. SPICE responds with `-123035784.81506048`. -Initializing the same UTC date in hifitime and requesting the TDB leads to a difference **610.94 nanoseconds**. +Initializing the same UTC date in hifitime and requesting the TDB leads to a difference **640.74 nanoseconds**. ## Case 4 In SPICE, we chose to convert the UTC date `2015-02-07 00:00:00.000 UTC` into Ephemeris Time. SPICE responds with `476580220.1849411`. -Initializing the same UTC date in hifitime and requesting the TDB leads to a difference **596.05 nanoseconds**. +Initializing the same UTC date in hifitime and requesting the TDB leads to a difference **655.65 nanoseconds**. ## Case 5 In SPICE, we chose to convert the TDB Julian Date in days `2452312.500372511` into Ephemeris Time, and initialize a Hifitime Epoch with that result (`66312032.18493909`). -We then convert that epoch back into **days** of Julian Date TDB and Julian Date ET, both of which lead a difference **below machine precision** on a f64 (the equivalent of a double in C/C++). +We then convert that epoch back into **days** of Julian Date TDB and Julian Date ET, which lead a difference **below machine precision** for the TDB computation and **0.46 nanoseconds** for the ET computation on a 64-bit floating point (f64/double). # Notes -Please report and bugs by [clicking here](https://github.com/ChristopherRabotin/hifitime/issues/new). +Please report and bugs by [clicking here](https://github.com/nyx-space/hifitime/issues/new). ### Leap second support Each time computing library may decide when the extra leap second exists as explained @@ -80,6 +82,10 @@ ET and TDB should now be identical. However, hifitime uses the European Space Ag # Changelog +## 3.0.0 ++ Backend rewritten from TwoFloat to a struct of the centuries in `i16` and nanoseconds in `u64`. Thanks to @pwnorbitals for proposing the idea in #107 and writing the proof of concept. ++ Fix GPS epoch, and addition of a helper functions in `Epoch` by @cjordan + ## 2.2.3 + More deterministic `as_jde_tdb_days()` in `Epoch`. In version 2.2.1, the ephemeris time and TDB _days_ were identical down to machine precision. After a number of validation cases in the rotation equations of the IAU Earth to Earth Mean Equator J2000 frame, the new formulation was shown to lead to less rounding errors when requesting the days. These rounding errors prevented otherwise trivial test cases. However, it adds an error of **40.2 nanoseconds** when initializing an Epoch with the days in ET and requesting the TDB days. diff --git a/src/epoch.rs b/src/epoch.rs index 37780270..32727112 100644 --- a/src/epoch.rs +++ b/src/epoch.rs @@ -1379,9 +1379,10 @@ fn spice_et_tdb() { let expected_et_s = 381_885_819.184_935_87; // Check reciprocity let from_et_s = Epoch::from_et_seconds(expected_et_s); - assert!(dbg!(from_et_s.as_et_seconds() - expected_et_s).abs() < std::f64::EPSILON); - assert!((sp_ex.as_et_seconds() - expected_et_s).abs() < 1e-6); - assert!(dbg!(sp_ex.as_tdb_seconds() - expected_et_s).abs() < 1e-6); + assert!((from_et_s.as_et_seconds() - expected_et_s).abs() < std::f64::EPSILON); + // Validate UTC to ET when initialization from UTC + assert!(dbg!(sp_ex.as_et_seconds() - expected_et_s).abs() < 1e-6); // -9.5367431640625e-7 s <=> -954 ns error + assert!(dbg!(sp_ex.as_tdb_seconds() - expected_et_s).abs() < 1e-6); // 5.960464477539063e-7 s <=> 596 ns error assert!((sp_ex.as_jde_utc_days() - 2455964.9739931).abs() < 1e-7); assert!( dbg!(sp_ex.as_tai_seconds() - from_et_s.as_tai_seconds()).abs() // Broken @@ -1425,20 +1426,20 @@ fn spice_et_tdb() { >>> sp.str2et("JD 2452312.500372511 TDB") 66312032.18493909 */ - let sp_ex = Epoch::from_et_seconds(66_312_032.184_939_09); - assert!(dbg!(2452312.500372511 - sp_ex.as_jde_et_days()).abs() < std::f64::EPSILON); + let ex_from_et_s = Epoch::from_et_seconds(66_312_032.184_939_09); + assert!(dbg!(2452312.500372511 - ex_from_et_s.as_jde_et_days()).abs() < std::f64::EPSILON); // 4.7e-10 is the exact difference hifitime computes between ET and TDB. // That corresponds to 4.02e-5 seconds, or 4.02 nanoseconds - assert!(dbg!(2452312.500372511 - sp_ex.as_jde_tdb_days()).abs() < 4.7e-10); + assert!(dbg!(2452312.500372511 - ex_from_et_s.as_jde_tdb_days()).abs() < 4.7e-10); - let sp_ex = Epoch::from_et_seconds(381_885_753.003_859_5); - assert!(dbg!(2455964.9739931 - sp_ex.as_jde_tdb_days()).abs() < 4.7e-10); - assert!((2455964.9739931 - sp_ex.as_jde_et_days()).abs() < std::f64::EPSILON); + let ex_from_et_s = Epoch::from_et_seconds(381_885_753.003_859_5); + assert!(dbg!(2455964.9739931 - ex_from_et_s.as_jde_tdb_days()).abs() < std::f64::EPSILON); + assert!(dbg!(2455964.9739931 - ex_from_et_s.as_jde_et_days()).abs() < 4.7e-10); - let sp_ex = Epoch::from_et_seconds(0.0); - assert!(sp_ex.as_et_seconds() < std::f64::EPSILON); - assert!(dbg!(J2000_NAIF - sp_ex.as_jde_et_days()).abs() < std::f64::EPSILON); - assert!(dbg!(J2000_NAIF - sp_ex.as_jde_tdb_days()).abs() < 1e-7); + let ex_from_et_s = Epoch::from_et_seconds(0.0); + assert!(ex_from_et_s.as_et_seconds() < std::f64::EPSILON); + assert!(dbg!(J2000_NAIF - ex_from_et_s.as_jde_et_days()).abs() < std::f64::EPSILON); + assert!(dbg!(J2000_NAIF - ex_from_et_s.as_jde_tdb_days()).abs() < 1e-7); } #[test] From 92fb0aa7f65e663bcc7041af98fbdf989d81afd4 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Tue, 15 Feb 2022 12:34:19 -0700 Subject: [PATCH 16/17] Prep for merge Signed-off-by: Christopher Rabotin --- Cargo.toml | 18 +++++++++--------- README.md | 2 +- src/epoch.rs | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d350420e..0ba4c2ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "hifitime" -version = "3.0.0-beta.1" +version = "3.0.0-beta.2" authors = ["Christopher Rabotin "] -description = "Precise date and time handling in Rust built on top of TwoFloat with leap second support" +description = "Ultra-precise date and time handling in Rust for scientific applications with leap second support" homepage = "https://nyxspace.com/MathSpec/time/" documentation = "https://docs.rs/hifitime/" repository = "https://github.com/nyx-space/hifitime" @@ -12,15 +12,15 @@ readme = "README.md" license = "Apache-2.0" [dependencies] -rand = "0.8.0" -rand_distr = "0.4.0" -regex = "1.3.0" -serde = "1.0.0" -serde_derive = "1.0.0" +rand = "0.8.5" +rand_distr = "0.4.3" +regex = "1.5.4" +serde = "1.0.136" +serde_derive = "1.0.136" [dev-dependencies] -serde_derive = "1.0.0" -criterion = "0.3.0" +serde_derive = "1.0.136" +criterion = "0.3.5" [[bench]] name = "bench_epoch" diff --git a/README.md b/README.md index b5f385f5..83427549 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ ET and TDB should now be identical. However, hifitime uses the European Space Ag # Changelog ## 3.0.0 -+ Backend rewritten from TwoFloat to a struct of the centuries in `i16` and nanoseconds in `u64`. Thanks to @pwnorbitals for proposing the idea in #107 and writing the proof of concept. ++ Backend rewritten from TwoFloat to a struct of the centuries in `i16` and nanoseconds in `u64`. Thanks to @pwnorbitals for proposing the idea in #107 and writing the proof of concept. This leads to at least a 2x speed up in most calculations, cf. [this comment](https://github.com/nyx-space/hifitime/pull/107#issuecomment-1040702004). + Fix GPS epoch, and addition of a helper functions in `Epoch` by @cjordan ## 2.2.3 diff --git a/src/epoch.rs b/src/epoch.rs index d011422f..9c80e79c 100644 --- a/src/epoch.rs +++ b/src/epoch.rs @@ -202,7 +202,7 @@ impl Epoch { // We have the time in TAI. But we were given UTC. // Hence, we need to _add_ the leap seconds to get the actual TAI time. // TAI = UTC + leap_seconds <=> UTC = TAI - leap_seconds - e.0 += cnt * TimeUnit::Second; + e.0 += i64::from(cnt) * TimeUnit::Second; e } @@ -215,7 +215,7 @@ impl Epoch { // We have the time in TAI. But we were given UTC. // Hence, we need to _add_ the leap seconds to get the actual TAI time. // TAI = UTC + leap_seconds <=> UTC = TAI - leap_seconds - e.0 += cnt * TimeUnit::Second; + e.0 += i64::from(cnt) * TimeUnit::Second; e } @@ -232,7 +232,7 @@ impl Epoch { pub fn from_mjd_utc(days: f64) -> Self { let mut e = Self::from_mjd_tai(days); // TAI = UTC + leap_seconds <=> UTC = TAI - leap_seconds - e.0 += e.get_num_leap_seconds() * TimeUnit::Second; + e.0 += i64::from(e.get_num_leap_seconds()) * TimeUnit::Second; e } @@ -249,7 +249,7 @@ impl Epoch { pub fn from_jde_utc(days: f64) -> Self { let mut e = Self::from_jde_tai(days); // TAI = UTC + leap_seconds <=> UTC = TAI - leap_seconds - e.0 += e.get_num_leap_seconds() * TimeUnit::Second; + e.0 += i64::from(e.get_num_leap_seconds()) * TimeUnit::Second; e } @@ -462,7 +462,7 @@ impl Epoch { // We have the time in TAI. But we were given UTC. // Hence, we need to _add_ the leap seconds to get the actual TAI time. // TAI = UTC + leap_seconds <=> UTC = TAI - leap_seconds - if_tai.0 += cnt * TimeUnit::Second; + if_tai.0 += i64::from(cnt) * TimeUnit::Second; Ok(if_tai) } @@ -530,7 +530,7 @@ impl Epoch { fn as_utc_duration(self) -> Duration { let cnt = self.get_num_leap_seconds(); // TAI = UTC + leap_seconds <=> UTC = TAI - leap_seconds - self.0 + (-cnt) * TimeUnit::Second + self.0 + i64::from(-cnt) * TimeUnit::Second } /// Returns the number of UTC seconds since the TAI epoch From 72d6a701f4376f15e47701a4c8c2990ac00c104a Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Tue, 15 Feb 2022 13:35:09 -0700 Subject: [PATCH 17/17] Github workflow + clippy Signed-off-by: Christopher Rabotin --- .github/workflows/tests.yml | 73 +++++++++++++++++++++++++++++++++++++ src/duration.rs | 21 ++++------- src/epoch.rs | 31 ++++++++-------- 3 files changed, 95 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..303bbe24 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,73 @@ +on: + pull_request: + push: + branches: + - main + +name: Test Workflow + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Run cargo check + uses: actions-rs/cargo@v1 + with: + command: check + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test + + lints: + name: Lints + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt, clippy + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings \ No newline at end of file diff --git a/src/duration.rs b/src/duration.rs index bb9751cd..9001b184 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -132,7 +132,7 @@ impl Duration { #[must_use] pub fn total_nanoseconds(self) -> i128 { if self.centuries == -1 { - -1 * i128::from(NANOSECONDS_PER_CENTURY - self.nanoseconds) + -i128::from(NANOSECONDS_PER_CENTURY - self.nanoseconds) } else if self.centuries >= 0 { i128::from(self.centuries) * i128::from(NANOSECONDS_PER_CENTURY) + i128::from(self.nanoseconds) @@ -144,13 +144,12 @@ impl Duration { } /// Returns the truncated nanoseconds in a signed 64 bit integer, if the duration fits. - #[must_use] pub fn try_truncated_nanoseconds(self) -> Result { // If it fits, we know that the nanoseconds also fit if self.centuries.abs() >= 3 { Err(Errors::Overflow) } else if self.centuries == -1 { - Ok(-1 * (NANOSECONDS_PER_CENTURY - self.nanoseconds) as i64) + Ok(-((NANOSECONDS_PER_CENTURY - self.nanoseconds) as i64)) } else if self.centuries >= 0 { Ok( i64::from(self.centuries) * NANOSECONDS_PER_CENTURY as i64 @@ -197,13 +196,6 @@ impl Duration { unit * value } - /// Returns this duration in f64 in the provided unit. - /// For high fidelity comparisons, it is recommended to keep using the Duration structure. - #[must_use] - pub fn in_unit_f64(&self, unit: TimeUnit) -> f64 { - f64::from(self.in_unit(unit)) - } - /// Returns this duration in seconds f64. /// For high fidelity comparisons, it is recommended to keep using the Duration structure. #[must_use] @@ -390,6 +382,7 @@ macro_rules! impl_ops_for_type { } } + #[allow(clippy::suspicious_arithmetic_impl)] impl Div<$type> for Duration { type Output = Duration; fn div(self, q: $type) -> Self::Output { @@ -585,7 +578,7 @@ impl PartialEq for Duration { } impl PartialOrd for Duration { - #[allow(clippy::identity_op)] + #[allow(clippy::identity_op, clippy::comparison_chain)] fn partial_cmp(&self, unit: &TimeUnit) -> Option { let unit_deref = *unit; let unit_as_duration: Duration = unit_deref * 1; @@ -814,7 +807,7 @@ fn time_unit() { let third_hour = (1.0 / 3.0) * TimeUnit::Hour; let sum: Duration = quarter_hour + third_hour; dbg!(quarter_hour, third_hour, sum); - assert!((sum.in_unit_f64(TimeUnit::Minute) - 35.0).abs() < EPSILON); + assert!((sum.in_unit(TimeUnit::Minute) - 35.0).abs() < EPSILON); let quarter_hour = -0.25 * TimeUnit::Hour; let third_hour: Duration = -1 * TimeUnit::Hour / 3; @@ -823,7 +816,7 @@ fn time_unit() { sum.in_unit(TimeUnit::Millisecond).floor() - sum.in_unit(TimeUnit::Second).floor() * 1000.0; assert!(delta < std::f64::EPSILON); println!("{:?}", sum.in_seconds()); - assert!((sum.in_unit_f64(TimeUnit::Minute) + 35.0).abs() < EPSILON); + assert!((sum.in_unit(TimeUnit::Minute) + 35.0).abs() < EPSILON); } #[test] @@ -908,7 +901,7 @@ fn duration_print() { let sum: Duration = quarter_hour + third_hour; println!( "Proof that Duration is more precise than f64: {} vs {}", - sum.in_unit_f64(TimeUnit::Minute), + sum.in_unit(TimeUnit::Minute), (1.0 / 4.0 + 1.0 / 3.0) * 60.0 ); assert_eq!(format!("{}", sum), "35 min"); diff --git a/src/epoch.rs b/src/epoch.rs index 9c80e79c..31dc937b 100644 --- a/src/epoch.rs +++ b/src/epoch.rs @@ -285,7 +285,7 @@ impl Epoch { let tt_duration = duration - TimeUnit::Second * TT_OFFSET_S; let tt_centuries_j2k = - (tt_duration - TimeUnit::Second * ET_EPOCH_S).in_unit_f64(TimeUnit::Century); + (tt_duration - TimeUnit::Second * ET_EPOCH_S).in_unit(TimeUnit::Century); let g_rad = (PI / 180.0) * (357.528 + 35_999.050 * tt_centuries_j2k); @@ -514,7 +514,7 @@ impl Epoch { /// Returns the epoch as a floating point value in the provided unit pub fn as_tai(self, unit: TimeUnit) -> f64 { - self.0.in_unit_f64(unit) + self.0.in_unit(unit) } pub fn as_tai_days(self) -> f64 { @@ -535,7 +535,7 @@ impl Epoch { /// Returns the number of UTC seconds since the TAI epoch pub fn as_utc(self, unit: TimeUnit) -> f64 { - self.as_utc_duration().in_unit_f64(unit) + self.as_utc_duration().in_unit(unit) } /// Returns the number of UTC days since the TAI epoch @@ -555,7 +555,7 @@ impl Epoch { } pub fn as_mjd_tai(self, unit: TimeUnit) -> f64 { - (self.0 + TimeUnit::Day * J1900_OFFSET).in_unit_f64(unit) + (self.0 + TimeUnit::Day * J1900_OFFSET).in_unit(unit) } /// Returns the Modified Julian Date in days UTC. @@ -565,7 +565,7 @@ impl Epoch { /// Returns the Modified Julian Date in the provided unit in UTC. pub fn as_mjd_utc(self, unit: TimeUnit) -> f64 { - (self.as_utc_duration() + TimeUnit::Day * J1900_OFFSET).in_unit_f64(unit) + (self.as_utc_duration() + TimeUnit::Day * J1900_OFFSET).in_unit(unit) } /// Returns the Modified Julian Date in seconds UTC. @@ -581,7 +581,7 @@ impl Epoch { } pub fn as_jde_tai(self, unit: TimeUnit) -> f64 { - self.as_jde_tai_duration().in_unit_f64(unit) + self.as_jde_tai_duration().in_unit(unit) } pub fn as_jde_tai_duration(self) -> Duration { @@ -595,7 +595,7 @@ impl Epoch { /// Returns the Julian days in UTC. pub fn as_jde_utc_days(self) -> f64 { - self.as_jde_utc_duration().in_unit_f64(TimeUnit::Day) + self.as_jde_utc_duration().in_unit(TimeUnit::Day) } pub fn as_jde_utc_duration(self) -> Duration { @@ -618,7 +618,7 @@ impl Epoch { /// Returns days past TAI epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT)) pub fn as_tt_days(self) -> f64 { - self.as_tt_duration().in_unit_f64(TimeUnit::Day) + self.as_tt_duration().in_unit(TimeUnit::Day) } /// Returns the centuries pased J2000 TT @@ -633,7 +633,7 @@ impl Epoch { /// Returns days past Julian epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT)) pub fn as_jde_tt_days(self) -> f64 { - self.as_jde_tt_duration().in_unit_f64(TimeUnit::Day) + self.as_jde_tt_duration().in_unit(TimeUnit::Day) } pub fn as_jde_tt_duration(self) -> Duration { @@ -642,7 +642,7 @@ impl Epoch { /// Returns days past Modified Julian epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT)) pub fn as_mjd_tt_days(self) -> f64 { - self.as_mjd_tt_duration().in_unit_f64(TimeUnit::Day) + self.as_mjd_tt_duration().in_unit(TimeUnit::Day) } pub fn as_mjd_tt_duration(self) -> Duration { @@ -660,7 +660,7 @@ impl Epoch { /// Returns days past GPS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS#GPS_Time_.28GPST.29). pub fn as_gpst_days(self) -> f64 { - self.as_gpst_duration().in_unit_f64(TimeUnit::Day) + self.as_gpst_duration().in_unit(TimeUnit::Day) } /// Returns the Ephemeris Time seconds past epoch @@ -695,7 +695,7 @@ impl Epoch { /// Returns the Ephemeris Time JDE past epoch pub fn as_jde_et_days(self) -> f64 { - self.as_jde_et_duration().in_unit_f64(TimeUnit::Day) + self.as_jde_et_duration().in_unit(TimeUnit::Day) } pub fn as_jde_et_duration(self) -> Duration { @@ -703,7 +703,7 @@ impl Epoch { } pub fn as_jde_et(self, unit: TimeUnit) -> f64 { - self.as_jde_et_duration().in_unit_f64(unit) + self.as_jde_et_duration().in_unit(unit) } pub fn as_jde_tdb_duration(self) -> Duration { @@ -1066,7 +1066,7 @@ pub fn is_gregorian_valid( nanos: u32, ) -> bool { let max_seconds = if (month == 12 || month == 6) - && day == USUAL_DAYS_PER_MONTH[month as usize - 1].into() + && day == USUAL_DAYS_PER_MONTH[month as usize - 1] && hour == 23 && minute == 59 && ((month == 6 && JULY_YEARS.contains(&year)) @@ -1088,8 +1088,7 @@ pub fn is_gregorian_valid( { return false; } - if day > USUAL_DAYS_PER_MONTH[month as usize - 1].into() && (month != 2 || !is_leap_year(year)) - { + if day > USUAL_DAYS_PER_MONTH[month as usize - 1] && (month != 2 || !is_leap_year(year)) { // Not in February or not a leap year return false; }