Use f128 internally for Duration-float methods#154107
Use f128 internally for Duration-float methods#154107cuviper wants to merge 3 commits intorust-lang:mainfrom
f128 internally for Duration-float methods#154107Conversation
If we want to keep as much `Duration` precision as possible when multiplying or dividing by a float, then we shouldn't convert it to `f32` nor `f64` to match the operand, because their mantissas aren't large enough to hold a full `Duration`. However, `f128` is large enough with `MANTISSA_DIGITS = 113`, since `Duration::MAX` only needs 94 bits.
|
r? @scottmcm rustbot has assigned @scottmcm. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
I think this is a good solution but we can't actually do this unconditionally now, |
|
More specific list of that: rust/compiler/rustc_codegen_llvm/src/llvm_util.rs Lines 386 to 405 in 1f7f8ea |
|
Hmm, conditional implementation would be straightforward, but conditional testing would be quite annoying. I'll wait for feedback whether we actually want this change before I attempt that. |
|
Since a software float implementation would effectively be identical to the other PR, it does feel like this might be a better long-term change, but perhaps in the short-term the other PR is better? As in, leave a |
|
@tgross35 is it possible to use explicit software mul/div from e.g. compiler-builtins on the non-reliable targets? |
|
We need LLVM to not crash when it sees a Could we stomach some platform differences for now and use f128 where it is well-supported? |
library/core/src/time.rs
Outdated
| if nanos < 0.0 { | ||
| panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::Negative }); | ||
| } else if nanos <= const { Self::MAX.as_nanos() as f128 } { | ||
| Self::from_nanos_u128(nanos.round_ties_even() as u128) |
There was a problem hiding this comment.
I think this is subject to error due to double rounding, as the product of f128's has possibly already be rounded. Test case:
assert_eq!(Duration::MAX.mul_f64(0.5_f64.next_up()).as_nanos(), 9223372036854777855999999999); Validation:
https://www.wolframalpha.com/input?i=%2810%5E9*2%5E64-1%29*%282%5E52%2B1%29%2F2%5E53
That truly is an edge case, but it's part of the reason I mentioned using the subnormal range to get the correct integer rounding already at the multiplication. (It might also perform better, since it can replace the integer-float conversions with bitcasts.) Here's how that might look:
https://play.rust-lang.org/?version=nightly&mode=release&edition=2024&gist=78d6e7cde0d5016968e7db06e5896c9b
There was a problem hiding this comment.
Thanks you for explaining, along with the clear test case. I've made a change similar to what you suggest, and I applied that to division too, although I didn't figure out a good test on the edge for that. If you know how to construct a similar division test, I'd be happy to add it!
There was a problem hiding this comment.
Could you try with 1 << 93 nanos divided by ((1 << 47) - 1) as f64 / (1 << 47) as f64? The exact value is 2^93 + 2^46 + 2^-1 + 2^-48 + ... and f128 can only hold the first three terms, so double rounding should incorrectly round the half down. I can't test it properly on mobile at this time.
edit: Playground worked well enough: playground including a bonus test case suitable for f32 as well
|
To match #150933, |
|
|
|
I added conditions for |
If we want to keep as much
Durationprecision as possible whenmultiplying or dividing by a float, then we shouldn't convert it to
f32norf64to match the operand, because their mantissas aren'tlarge enough to hold a full
Duration. However,f128is large enoughwith
MANTISSA_DIGITS = 113, sinceDuration::MAXonly needs 94 bits.Fixes #149794, as an alternative to #150933.