Skip to content

Commit 684d543

Browse files
beetreestgross35
authored andcommitted
Fix sig::add_or_sub and IeeeFloat::normalize
1 parent 08528d6 commit 684d543

File tree

2 files changed

+205
-23
lines changed

2 files changed

+205
-23
lines changed

src/ieee.rs

Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2051,7 +2051,8 @@ impl<S: Semantics> IeeeFloat<S> {
20512051
// Before rounding normalize the exponent of Category::Normal numbers.
20522052
let mut omsb = sig::omsb(&self.sig);
20532053

2054-
if omsb > 0 {
2054+
// Only skip this `if` if the value is exactly zero.
2055+
if omsb > 0 || loss != Loss::ExactlyZero {
20552056
// OMSB is numbered from 1. We want to place it in the integer
20562057
// bit numbered PRECISION if possible, with a compensating change in
20572058
// the exponent.
@@ -3008,37 +3009,60 @@ mod sig {
30083009
// an addition or subtraction.
30093010
// Subtraction is more subtle than one might naively expect.
30103011
if *a_sign ^ b_sign {
3011-
let loss;
3012-
3013-
if bits == 0 {
3014-
loss = Loss::ExactlyZero;
3012+
let (mut loss, loss_is_from_b) = if bits == 0 {
3013+
(Loss::ExactlyZero, false)
30153014
} else if bits > 0 {
3016-
loss = shift_right(b_sig, &mut 0, (bits - 1) as usize);
30173015
shift_left(a_sig, a_exp, 1);
3016+
(shift_right(b_sig, &mut 0, (bits - 1) as usize), true)
30183017
} else {
3019-
loss = shift_right(a_sig, a_exp, (-bits - 1) as usize);
30203018
shift_left(b_sig, &mut 0, 1);
3021-
}
3022-
3023-
let borrow = (loss != Loss::ExactlyZero) as Limb;
3024-
3025-
// Should we reverse the subtraction.
3026-
if cmp(a_sig, b_sig) == Ordering::Less {
3027-
// The code above is intended to ensure that no borrow is necessary.
3028-
assert_eq!(sub(b_sig, a_sig, borrow), 0);
3029-
a_sig.copy_from_slice(b_sig);
3030-
*a_sign = !*a_sign;
3031-
} else {
3032-
// The code above is intended to ensure that no borrow is necessary.
3033-
assert_eq!(sub(a_sig, b_sig, borrow), 0);
3034-
}
3019+
(shift_right(a_sig, a_exp, (-bits - 1) as usize), false)
3020+
};
30353021

3036-
// Invert the lost fraction - it was on the RHS and subtracted.
3037-
match loss {
3022+
let invert_loss = |loss| match loss {
30383023
Loss::LessThanHalf => Loss::MoreThanHalf,
30393024
Loss::MoreThanHalf => Loss::LessThanHalf,
30403025
_ => loss,
3026+
};
3027+
3028+
// Should we reverse the subtraction.
3029+
match cmp(a_sig, b_sig) {
3030+
Ordering::Less => {
3031+
let borrow = if loss != Loss::ExactlyZero && !loss_is_from_b {
3032+
// The loss is being subtracted, borrow from the significand and invert
3033+
// `loss`.
3034+
loss = invert_loss(loss);
3035+
1
3036+
} else {
3037+
0
3038+
};
3039+
// The code above is intended to ensure that no borrow is necessary.
3040+
assert_eq!(sub(b_sig, a_sig, borrow), 0);
3041+
a_sig.copy_from_slice(b_sig);
3042+
*a_sign = !*a_sign;
3043+
}
3044+
Ordering::Greater => {
3045+
let borrow = if loss != Loss::ExactlyZero && loss_is_from_b {
3046+
// The loss is being subtracted, borrow from the significand and invert
3047+
// `loss`.
3048+
loss = invert_loss(loss);
3049+
1
3050+
} else {
3051+
0
3052+
};
3053+
// The code above is intended to ensure that no borrow is necessary.
3054+
assert_eq!(sub(a_sig, b_sig, borrow), 0);
3055+
}
3056+
Ordering::Equal => {
3057+
a_sig.fill(0);
3058+
if loss != Loss::ExactlyZero && loss_is_from_b {
3059+
// b is slightly larger due to the loss, flip the sign.
3060+
*a_sign = !*a_sign;
3061+
}
3062+
}
30413063
}
3064+
3065+
loss
30423066
} else {
30433067
let loss = if bits > 0 {
30443068
shift_right(b_sig, &mut 0, bits as usize)
@@ -3051,6 +3075,69 @@ mod sig {
30513075
}
30523076
}
30533077

3078+
#[test]
3079+
fn test_add_or_sub() {
3080+
#[track_caller]
3081+
fn run_test(
3082+
subtract: bool,
3083+
mut lhs_sign: bool,
3084+
mut lhs_exponent: ExpInt,
3085+
mut lhs_significand: Limb,
3086+
rhs_sign: bool,
3087+
rhs_exponent: ExpInt,
3088+
mut rhs_significand: Limb,
3089+
expected_sign: bool,
3090+
expected_exponent: ExpInt,
3091+
expected_significand: Limb,
3092+
expected_loss: Loss,
3093+
) {
3094+
let loss = add_or_sub(
3095+
core::array::from_mut(&mut lhs_significand),
3096+
&mut lhs_exponent,
3097+
&mut lhs_sign,
3098+
core::array::from_mut(&mut rhs_significand),
3099+
rhs_exponent,
3100+
rhs_sign ^ subtract,
3101+
);
3102+
assert_eq!(loss, expected_loss);
3103+
assert_eq!(lhs_sign, expected_sign);
3104+
assert_eq!(lhs_exponent, expected_exponent);
3105+
assert_eq!(lhs_significand, expected_significand);
3106+
}
3107+
3108+
// Test cases are all combinations of:
3109+
// {equal exponents, LHS larger exponent, RHS larger exponent}
3110+
// {equal significands, LHS larger significand, RHS larger significand}
3111+
// {no loss, loss}
3112+
3113+
// Equal exponents (loss cannot occur as their is no shifting)
3114+
run_test(true, false, 1, 0x10, false, 1, 0x5, false, 1, 0xb, Loss::ExactlyZero);
3115+
run_test(false, false, -2, 0x20, true, -2, 0x20, false, -2, 0, Loss::ExactlyZero);
3116+
run_test(false, true, 3, 0x20, false, 3, 0x30, false, 3, 0x10, Loss::ExactlyZero);
3117+
3118+
// LHS larger exponent
3119+
// LHS significand greater after shitfing
3120+
run_test(true, false, 7, 0x100, false, 3, 0x100, false, 6, 0x1e0, Loss::ExactlyZero);
3121+
run_test(true, false, 7, 0x100, false, 3, 0x101, false, 6, 0x1df, Loss::MoreThanHalf);
3122+
// Significands equal after shitfing
3123+
run_test(true, false, 7, 0x100, false, 3, 0x1000, false, 6, 0, Loss::ExactlyZero);
3124+
run_test(true, false, 7, 0x100, false, 3, 0x1001, true, 6, 0, Loss::LessThanHalf);
3125+
// RHS significand greater after shitfing
3126+
run_test(true, false, 7, 0x100, false, 3, 0x10000, true, 6, 0x1e00, Loss::ExactlyZero);
3127+
run_test(true, false, 7, 0x100, false, 3, 0x10001, true, 6, 0x1e00, Loss::LessThanHalf);
3128+
3129+
// RHS larger exponent
3130+
// RHS significand greater after shitfing
3131+
run_test(true, false, 3, 0x100, false, 7, 0x100, true, 6, 0x1e0, Loss::ExactlyZero);
3132+
run_test(true, false, 3, 0x101, false, 7, 0x100, true, 6, 0x1df, Loss::MoreThanHalf);
3133+
// Significands equal after shitfing
3134+
run_test(true, false, 3, 0x1000, false, 7, 0x100, false, 6, 0, Loss::ExactlyZero);
3135+
run_test(true, false, 3, 0x1001, false, 7, 0x100, false, 6, 0, Loss::LessThanHalf);
3136+
// LHS significand greater after shitfing
3137+
run_test(true, false, 3, 0x10000, false, 7, 0x100, false, 6, 0x1e00, Loss::ExactlyZero);
3138+
run_test(true, false, 3, 0x10001, false, 7, 0x100, false, 6, 0x1e00, Loss::LessThanHalf);
3139+
}
3140+
30543141
/// `[low, high] = a * b`.
30553142
///
30563143
/// This cannot overflow, because

tests/ieee.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,101 @@ fn fma() {
635635
assert_eq!(-8.85242279E-41, f1.to_f32());
636636
}
637637

638+
// `sig::add_or_sub` can be considered to have 9 possible cases
639+
// when subtracting: all combinations of `Ordering::{Less, Greater, Equal} and
640+
// {no loss, loss from lhs, loss from rhs}. Test each reachable case here.
641+
642+
// Regression test for failing the assertions in `sig::add_or_sub` and normalizing
643+
// the exponent even when the significand is zero if there is a lost fraction.
644+
// This tests `Ordering::Equal`, loss from lhs
645+
{
646+
let mut f1 = Single::from_f32(-1.4728589E-38);
647+
let f2 = Single::from_f32(3.7105144E-6);
648+
let f3 = Single::from_f32(5.5E-44);
649+
f1 = f1.mul_add(f2, f3).value;
650+
assert_eq!(-0.0, f1.to_f32());
651+
}
652+
653+
// Test `Ordering::Greater`, no loss
654+
{
655+
let mut f1 = Single::from_f32(2.0);
656+
let f2 = Single::from_f32(2.0);
657+
let f3 = Single::from_f32(-3.5);
658+
f1 = f1.mul_add(f2, f3).value;
659+
assert_eq!(0.5, f1.to_f32());
660+
}
661+
662+
// Test `Ordering::Less`, no loss
663+
{
664+
let mut f1 = Single::from_f32(2.0);
665+
let f2 = Single::from_f32(2.0);
666+
let f3 = Single::from_f32(-4.5);
667+
f1 = f1.mul_add(f2, f3).value;
668+
assert_eq!(-0.5, f1.to_f32());
669+
}
670+
671+
// Test `Ordering::Equal`, no loss
672+
{
673+
let mut f1 = Single::from_f32(2.0);
674+
let f2 = Single::from_f32(2.0);
675+
let f3 = Single::from_f32(-4.0);
676+
f1 = f1.mul_add(f2, f3).value;
677+
assert_eq!(0.0, f1.to_f32());
678+
}
679+
680+
// Test `Ordering::Less`, loss from lhs
681+
{
682+
let mut f1 = Single::from_f32(2.0000002);
683+
let f2 = Single::from_f32(2.0000002);
684+
let f3 = Single::from_f32(-32.0);
685+
f1 = f1.mul_add(f2, f3).value;
686+
assert_eq!(-27.999998, f1.to_f32());
687+
}
688+
689+
// Test `Ordering::Greater`, loss from rhs
690+
{
691+
let mut f1 = Single::from_f32(1e10);
692+
let f2 = Single::from_f32(1e10);
693+
let f3 = Single::from_f32(-2.0000002);
694+
f1 = f1.mul_add(f2, f3).value;
695+
assert_eq!(1e20, f1.to_f32());
696+
}
697+
698+
// Test `Ordering::Greater`, loss from lhs
699+
{
700+
let mut f1 = Single::from_f32(1e-36);
701+
let f2 = Single::from_f32(0.0019531252);
702+
let f3 = Single::from_f32(-1e-45);
703+
f1 = f1.mul_add(f2, f3).value;
704+
assert_eq!(1.953124e-39, f1.to_f32());
705+
}
706+
707+
// `Ordering::{Equal, Less}` with loss from rhs can't occur for the usage in
708+
// `mul_add` as `mul_add_r` normalises the MSB of lhs to one bit below the top.
709+
710+
// Test cases from llvm/llvm-project#104984
711+
{
712+
let mut f1 = Single::from_f32(0.24999998);
713+
let f2 = Single::from_f32(2.3509885e-38);
714+
let f3 = Single::from_f32(-1e-45);
715+
f1 = f1.mul_add(f2, f3).value;
716+
assert_eq!(5.87747e-39, f1.to_f32());
717+
}
718+
{
719+
let mut f1 = Single::from_f32(4.4501477170144023e-308);
720+
let f2 = Single::from_f32(0.24999999999999997);
721+
let f3 = Single::from_f32(-8.475904604373977e-309);
722+
f1 = f1.mul_add(f2, f3).value;
723+
assert_eq!(2.64946468816203e-309, f1.to_f32());
724+
}
725+
{
726+
let mut f1 = Half::from_bits(0x8fff);
727+
let f2 = Half::from_bits(0x2bff);
728+
let f3 = Half::from_bits(0x0172);
729+
f1 = f1.mul_add(f2, f3).value;
730+
assert_eq!(0x808e, f1.to_bits());
731+
}
732+
638733
// Test using only a single instance of APFloat.
639734
{
640735
let mut f = Double::from_f64(1.5);

0 commit comments

Comments
 (0)