rust-lang / rust-lang/rust

Loss of precision in `Duration::mul_f32` (and `mul_f64`)

Open
#149,794 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

A-floating-point A-time C-bug T-libs
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

The methods to multiply a Duration by a float are currently implemented by converting to that float type (as seconds), multiplying, and then converting back to a Duration. This implies unnecessary rounding that loses precision in cases that ideally wouldn't.

It is particularly surprising that multiplying by 1.0 can change a duration, as floating point multiplication by 1.0 does preserve numeric values exactly.

use std::time::Duration;

fn main() {
    let d1 = Duration::from_nanos_u128(1 << 90);
    let d2 = Duration::from_nanos_u128(2 << 90);
    let d3 = Duration::from_nanos_u128(3 << 90);
    
    // Each of these asserts fail
    assert_eq!(d1.mul_f32(1.0), d1);
    assert_eq!(d1.mul_f32(2.0), d2);
    assert_eq!(d1.mul_f32(3.0), d3);
    assert_eq!(d2.mul_f32(1.5), d3);
    // This panics due to rounding up
    Duration::MAX.mul_f32(1.0);
}

I expected these methods to behave just like floating point multiplication: As if the exact result was rounded to the resulting type.

The above uses very long durations, but for more realistic examples, this can be observed with durations like:

100ms
[src/main.rs:5:5] d = 100ms
[src/main.rs:6:5] d.mul_f32(1.0) = 100.000001ms
[src/main.rs:7:5] d.mul_f32(2.0) = 200.000003ms
[src/main.rs:8:5] d.mul_f32(3.0) = 300.000012ms
[src/main.rs:9:5] d * 1 = 100ms
[src/main.rs:10:5] d * 2 = 200ms
[src/main.rs:11:5] d * 3 = 300ms
1s + 111ns
[src/main.rs:5:5] d = 1.000000111s
[src/main.rs:6:5] d.mul_f32(1.0) = 1.000000119s
[src/main.rs:7:5] d.mul_f32(2.0) = 2.000000238s
[src/main.rs:8:5] d.mul_f32(3.0) = 3.000000477s
[src/main.rs:9:5] d * 1 = 1.000000111s
[src/main.rs:10:5] d * 2 = 2.000000222s
[src/main.rs:11:5] d * 3 = 3.000000333s

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the provided Duration::mul_f32 examples, including the Duration::MAX case, then locate the standard library implementation of Duration::mul_f32 and mul_f64. Compare their behavior with the issue's expected floating-point semantics and existing Duration multiplication behavior. Done means the documented precision-loss examples no longer fail or panic unexpectedly, with tests covering the reported cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.