-
Notifications
You must be signed in to change notification settings - Fork 13.7k
Simplify {f16, f32, f64, f128}::midpoint()
#146225
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
Conversation
`(float_ty::MAX / 2) - (float_ty::MIN_POSITIVE * 2)` equals `(float_ty::MAX / 2) + (float_ty::MIN_POSITIVE * 2)` equals `(float_ty::MAX / 2)`. So these branches are pointless
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense to me but I'll wait for Urgau to double check. I assume the original libcxx version may have been accounting for ftz or something.
One extra nice thing about f16 is it's feasible to test 2 to 3-dimensional operations exhaustively (passed for me):
#![feature(f16)]
pub const fn midpoint1(a: f16, b: f16) -> f16 {
const LO: f16 = f16::MIN_POSITIVE * 2.;
const HI: f16 = f16::MAX / 2.;
let abs_a = a.abs();
let abs_b = b.abs();
if abs_a <= HI && abs_b <= HI {
// Overflow is impossible
(a + b) / 2.
} else if abs_a < LO {
// Not safe to halve `a` (would underflow)
a + (b / 2.)
} else if abs_b < LO {
// Not safe to halve `b` (would underflow)
(a / 2.) + b
} else {
// Safe to halve `a` and `b`
(a / 2.) + (b / 2.)
}
}
pub const fn midpoint2(a: f16, b: f16) -> f16 {
const HI: f16 = f16::MAX / 2.;
let abs_a = a.abs();
let abs_b = b.abs();
if abs_a <= HI && abs_b <= HI {
// Overflow is impossible
(a + b) / 2.
} else {
// Safe to halve `a` and `b`
(a / 2.) + (b / 2.)
}
}
fn main() {
for i in 0..=u16::MAX {
for j in 0..=u16::MAX {
let a = f16::from_bits(i);
let b = f16::from_bits(j);
let m1 = midpoint1(a, b);
let m2 = midpoint2(a, b);
assert_eq!(m1.to_bits(), m2.to_bits());
}
}
}
{f16, f32, f54, f128}::midpoint()
{f16, f32, f64, f128}::midpoint()
Tested #![feature(f128, f16)]
type F = f64;
pub fn old(a: F, b: F) -> F {
const LO: F = F::MIN_POSITIVE * 2.;
const HI: F = F::MAX / 2.;
let abs_a = a.abs();
let abs_b = b.abs();
if abs_a <= HI && abs_b <= HI {
// Overflow is impossible
(a + b) / 2.
} else if abs_a < LO {
// Not safe to halve `a` (would underflow)
a + (b / 2.)
} else if abs_b < LO {
// Not safe to halve `b` (would underflow)
(a / 2.) + b
} else {
// Safe to halve `a` and `b`
(a / 2.) + (b / 2.)
}
}
pub fn new(a: F, b: F) -> F {
const HI: F = F::MAX / 2.;
let abs_a = a.abs();
let abs_b = b.abs();
if abs_a <= HI && abs_b <= HI {
// Overflow is impossible
(a + b) / 2.
} else {
(a / 2.) + (b / 2.)
}
}
#[cfg(kani)]
#[kani::proof]
fn equiv() {
let a: F = kani::any();
let b: F = kani::any();
kani::assume(!(a == -b && a.abs() == F::INFINITY));
let prev = old(a, b);
let next = new(a, b);
assert_eq!(prev.is_nan(), next.is_nan());
if !prev.is_nan() && !next.is_nan() {
assert_eq!(prev, next)
}
} It's telling me the implementations are equivalent. The only thing it complained about was "addition with Currently testing One thing I've noticed during the testing is that UPD: |
It’s a known TODO. #116909 (“Figure out |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me as well.
However, I also wonder why libcxx implementation was and still does that. Perhaps to save a division for performance reasons?
Perhaps. But in that case, they/we could skip the addition as well. |
I think it's pretty much this, or more generally supporting non-default floating point environments. The condition Rust doesn't support any of that so the patch seems fine to me. |
For a bit more on this, we can't use the generic implementation used for other types because it uses u64s and we'd pay a cost there. It's on my todo list but a much bigger change than f16 |
@bors r+ |
Rollup of 11 pull requests Successful merges: - #138944 (Add `__isPlatformVersionAtLeast` and `__isOSVersionAtLeast` symbols) - #139113 (unstable book: in a sanitizer example, check the code) - #145735 (style-guide: Document absence of trailing whitespace) - #146041 (tidy: --bless now makes escheck run with --fix) - #146144 (compiler: Apply target features to the entry function) - #146225 (Simplify `{f16, f32, f64, f128}::midpoint()`) - #146234 (change file-is-generated doc comment to inner) - #146241 (rustc_infer: change top-level doc comment to inner) - #146242 (Ensure that `--html-after-content` option is used to check `scrape_examples_ice` rustdoc GUI test) - #146243 (remove couple of redundant clones) - #146250 (Bump stage0 rustfmt) Failed merges: - #146200 (Simplify rustdoc-gui tester by calling directly browser-ui-test) r? `@ghost` `@rustbot` modify labels: rollup
Rollup merge of #146225 - Jules-Bertholet:simplify-float-midpoint, r=tgross35 Simplify `{f16, f32, f64, f128}::midpoint()` `(float_ty::MAX / 2) - (float_ty::MIN_POSITIVE * 2)` equals `(float_ty::MAX / 2) + (float_ty::MIN_POSITIVE * 2)` equals `(float_ty::MAX / 2)`. So these branches are pointless. CC `@Urgau` who wrote the original implementation in #92048; does this seem right? `@rustbot` label A-floating-point
…point, r=tgross35 Simplify `{f16, f32, f64, f128}::midpoint()` `(float_ty::MAX / 2) - (float_ty::MIN_POSITIVE * 2)` equals `(float_ty::MAX / 2) + (float_ty::MIN_POSITIVE * 2)` equals `(float_ty::MAX / 2)`. So these branches are pointless. CC `@Urgau` who wrote the original implementation in rust-lang#92048; does this seem right? `@rustbot` label A-floating-point
(float_ty::MAX / 2) - (float_ty::MIN_POSITIVE * 2)
equals(float_ty::MAX / 2) + (float_ty::MIN_POSITIVE * 2)
equals(float_ty::MAX / 2)
. So these branches are pointless.CC @Urgau who wrote the original implementation in #92048; does this seem right?
@rustbot label A-floating-point