Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add checked_mul and checked_div #1565

Merged
merged 1 commit into from
Apr 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 41 additions & 18 deletions src/time_delta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,40 @@
TimeDelta::new(secs, nanos as u32)
}

/// Multiply a `TimeDelta` with a i32, returning `None` if overflow occurred.
#[must_use]
pub const fn checked_mul(&self, rhs: i32) -> Option<TimeDelta> {
// Multiply nanoseconds as i64, because it cannot overflow that way.
let total_nanos = self.nanos as i64 * rhs as i64;
let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64);
// Multiply seconds as i128 to prevent overflow
let secs: i128 = self.secs as i128 * rhs as i128 + extra_secs as i128;
pitdicker marked this conversation as resolved.
Show resolved Hide resolved
if secs <= i64::MIN as i128 || secs >= i64::MAX as i128 {
return None;
};
Some(TimeDelta { secs: secs as i64, nanos: nanos as i32 })
}

/// Divide a `TimeDelta` with a i32, returning `None` if dividing by 0.
#[must_use]
pub const fn checked_div(&self, rhs: i32) -> Option<TimeDelta> {
if rhs == 0 {
Zomtir marked this conversation as resolved.
Show resolved Hide resolved
return None;
}
let secs = self.secs / rhs as i64;
let carry = self.secs % rhs as i64;
let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64;
let nanos = self.nanos / rhs + extra_nanos as i32;

let (secs, nanos) = match nanos {
i32::MIN..=-1 => (secs - 1, nanos + NANOS_PER_SEC),
NANOS_PER_SEC..=i32::MAX => (secs + 1, nanos - NANOS_PER_SEC),

Check warning on line 402 in src/time_delta.rs

View check run for this annotation

Codecov / codecov/patch

src/time_delta.rs#L402

Added line #L402 was not covered by tests
_ => (secs, nanos),
};

Some(TimeDelta { secs, nanos })
}

/// Returns the `TimeDelta` as an absolute (non-negative) value.
#[inline]
pub const fn abs(&self) -> TimeDelta {
Expand Down Expand Up @@ -489,31 +523,15 @@
type Output = TimeDelta;

fn mul(self, rhs: i32) -> TimeDelta {
// Multiply nanoseconds as i64, because it cannot overflow that way.
let total_nanos = self.nanos as i64 * rhs as i64;
let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64);
let secs = self.secs * rhs as i64 + extra_secs;
TimeDelta { secs, nanos: nanos as i32 }
self.checked_mul(rhs).expect("`TimeDelta * i32` overflowed")
}
}

impl Div<i32> for TimeDelta {
type Output = TimeDelta;

fn div(self, rhs: i32) -> TimeDelta {
let mut secs = self.secs / rhs as i64;
let carry = self.secs - secs * rhs as i64;
let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64;
let mut nanos = self.nanos / rhs + extra_nanos as i32;
if nanos >= NANOS_PER_SEC {
nanos -= NANOS_PER_SEC;
secs += 1;
}
if nanos < 0 {
nanos += NANOS_PER_SEC;
secs -= 1;
}
TimeDelta { secs, nanos }
self.checked_div(rhs).expect("`i32` is zero")
}
}

Expand Down Expand Up @@ -1034,6 +1052,7 @@
#[test]
fn test_duration_checked_ops() {
let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap();
let seconds = |s| TimeDelta::try_seconds(s).unwrap();

assert_eq!(
milliseconds(i64::MAX).checked_add(&milliseconds(0)),
Expand All @@ -1056,6 +1075,10 @@
);
assert!(milliseconds(-i64::MAX).checked_sub(&milliseconds(1)).is_none());
assert!(milliseconds(-i64::MAX).checked_sub(&TimeDelta::nanoseconds(1)).is_none());

assert!(seconds(i64::MAX / 1000).checked_mul(2000).is_none());
assert!(seconds(i64::MIN / 1000).checked_mul(2000).is_none());
assert!(seconds(1).checked_div(0).is_none());
}

#[test]
Expand Down
Loading