Simplify {f16, f32, f64, f128}::midpoint()#146225
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.
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 Details#![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.
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