Hacker News new | ask | show | jobs
by mtklein 64 days ago
My preference in tests is a little different than just using IEEE 754 ==,

    _Bool equiv(float x, float y) {
        return (x <= y && y <= x)
            || (x != x && y != y);
    }
which both handles NaNs sensibly (all NaNs are equivalent) and won't warn about using == on floats. I find it also easy to remember how to write when starting a new project.
3 comments

what I mean here about NaNs is that from a testing perspective, I want to be able to write a test that expects NaN in the same way that I write other expectations, and you can't do that with ==.

    assert(x == 7);    // fine
    assert(y == NaN);  // never true
    assert(y != y);    // this is what you meant
so this equiv() helper fixes that,

    assert(equiv(x, 7));    // fine
    assert(equiv(y, NaN));  // also fine
now, as far as treating NaNs equivalently, the IEEE 754 float format has a huge number of possible representations of NaN, and if you did something like a bitwise comparison, you might think that 0x7fc00000, 0x7f800001, 0xffc00000, 0x7fc0f00d were all different and not equivalent, but they're all NaNs, and I find that when I'm looking for a NaN, I very rarely care about exactly which one I'm looking at. So checking (x!=x && y!=y) admits any two NaNs as equivalent.
Why is that NaN handling sensible? I don't think it makes sense to say log(-1) equals log(-2). Mathematically it isn't true and your implementation would say it's true only because of limitations in IEEE754.
Mathematically it’s also not true that 2³⁰⁹ = +∞. You simply can’t expect correct results from floats.
Could you explain the bit about "all NaNs are equivalent"? IEEE requires that NaN ≠ NaN, but I suspect that I am just misunderstanding what you mean.