1
\$\begingroup\$

I made a function in Rust to add two floating point numbers (f32s) 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))
}
```
\$\endgroup\$
2
  • \$\begingroup\$ Did you finally checked this (nice) code ? \$\endgroup\$
    – JCLL
    Commented Jan 8 at 18:13
  • \$\begingroup\$ @JCLL Yes, I tested it a while ago and completely forgot to update the post! It works so long as a and b are finite (also can't be NaN), both have positive sign, and as long as a + b is not infinite. Obviously the code can be easily modified to account for these cases. \$\endgroup\$ Commented Jan 9 at 21:23

1 Answer 1

1
\$\begingroup\$

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.

I would use something like the quickcheck crate (a port of Haskell's QuickCheck) to test the property of whether your addition function has the same results as ordinary f32 addition. If you don't know what quickcheck is, then this video might help.

Fuzzing your function as described in this video about Fuzz-Driven Development (FDD) might be another option.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.