I made a function in Rust to add two floating point numbers (f32
s) together using only their bit representation and integer operations. I have tested it for a quite a few different cases but I'm not sure how I can efficiently test it for all floating point numbers without it taking ages.
fn add_f32(mut a: f32, mut b: f32) -> f32 {
if b > a {
// If b has a larger exponent than a, swap a and b so that a has the larger exponent
core::mem::swap(&mut a, &mut b);
}
let a_normal = a.is_normal();
let b_normal = b.is_normal();
let a_bits = a.to_bits();
let b_bits = b.to_bits();
let mut a_exp = (a_bits << 1) >> (23 + 1);
let mut b_exp = (b_bits << 1) >> (23 + 1);
let mut a_mant = a_bits & 0x007fffff;
let mut b_mant = b_bits & 0x007fffff;
if a_exp == 0 {
a_exp += 1;
}
if b_exp == 0 {
b_exp += 1;
}
let exp_diff = a_exp - b_exp;
let sticky_bit = b_mant.trailing_zeros() + 1 < exp_diff;
// Add the implicit leading 1 bit to the mantissas
if a_normal {
a_mant |= 1 << 23;
}
if b_normal {
b_mant |= 1 << 23;
}
// Append extra bits to the mantissas to ensure correct rounding
a_mant <<= 2;
b_mant <<= 2;
// If the shift causes an overflow, the b_mant is too small so is set to 0
b_mant = b_mant.checked_shr(exp_diff).unwrap_or(0);
if sticky_bit {
b_mant |= 1;
}
let mut mant = a_mant + b_mant;
let overflow = (mant >> 26) != 0;
if !overflow {
if mant & 0b11 == 0b11 {
mant += 0b100;
if (mant >> 26) != 0 {
mant >>= 1;
a_exp += 1;
}
} else if mant & 0b110 == 0b110 {
mant += 0b100;
if (mant >> 26) != 0 {
mant >>= 1;
a_exp += 1;
}
}
} else {
match mant & 0b111 {
0b111 | 0b110 | 0b101 => {
mant += 0b1000;
},
0b100 => {
if mant & 0b1000 == 0b1000 {
mant += 0b1000;
}
},
_ => {},
}
mant >>= 1;
a_exp += 1;
}
mant >>= 2;
if mant >> 23 == 0 {
a_exp = 0;
} else {
mant <<= 9;
mant >>= 9;
}
f32::from_bits(mant | (a_exp << 23))
}
```