Live data from Hacker News

It's OK to compare floating-points for equality

lisyarus.github.io

131–140 of 150 posts

Re: It's OK to compare floating-points for equality

#131
post #93

My preference in tests is a little different than just using IEEE 754 ==, _Bool equiv(float x, float y) { return (x 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.

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.

Re: It's OK to compare floating-points for equality

#132
post #7

I have this floating-point problem at scale and will donate $100 to the author, or to anyone here, who can improve my code the most. The Rust code in the assert_f64_eq macro is: if (a >= b && a - b I'm the author of the Rust assertables crate. It provides floating-point assert macros much as described in the article. https://github.com/SixArm/assertables-rust-crate/blob/main/s... If there's a way to make it more prec…

Numeric comparison implies subtraction or similar a - b sign and zero extraction at some lower level (in an ALU micro-op perhaps), so that's duplicated effort of a - b unless it can be optimized away.

    match a - b {
      d if d >= 0.0 => d  d >= -f64::EPSILON, /* true if -EPSILON to -0.0 */
    }
https://godbolt.org/z/71PGzd7oa

Re: It's OK to compare floating-points for equality

#133
post #17

Earlier quoted context omitted.

Is there any constant more misused in compsci than ieee epsilon? :) It's defined as the difference between 1.0 and the smallest number larger than 1.0. More usefully, it's the spacing between adjacent representable float numbers in the range 1.0 to 2.0. Because floats get less precise at every integer power of two, it's impossible for two numbers greater than or equal to 2.0 to be epsilon apart. The spacing between 2…

The term I've seen a lot is https://en.wikipedia.org/wiki/Unit_in_the_last_place So I'd probably rewrite that code to first find the ulp of the larger of the abs of a and b and then assert that their difference is less than or equal to that. Edit: Or maybe the smaller of the abs of the two, I haven't totally thought through the consequences. It might not matter, because the ulps will only differ when the numbers are…

Because of the representation of floats, couldn't you just bitwise cast to uints and see if the (abs) difference was less than or equal to one? But practically you probably should check if it's less than or equal to say ten, depending on your tolerance.

Re: It's OK to compare floating-points for equality

#134

Earlier quoted context omitted.

Nobody's code will be compiled to use x87 any more.

There is plenty of demand for so called "secure code", where such coder arrogance will not be tolerated. Trust me on that, I know it.

What is "coder arrogance"? The expectation that the 20-year-old API (SSE) will be used over the deprecated 40-year-old API that is currently losing compiler (and silicon) support? Can you point to a running x86 machine today that does not have SSE?

It is also faster and more precise to use double-double for math over x87 extended precision. There is literally no reason to compile an x87 instruction today aside from a programmer not knowing better.

Re: It's OK to compare floating-points for equality

#135

Earlier quoted context omitted.

I think his point is: rather than "leaning into" it as in, masking through epsilons, he argues that tolerance is fundamental to the problem space, not a way to resolve edge cases.

Right. And my point is that "leaning in" doesn't mean masking, it means committing to. Taking seriously. Exactly the sort of thing he's describing. I'm wondering if people have heard the expression "leaning in" from people who were insincere/lying, and assumed that that was what the phrase means?

I think you should revisit the word "just", its presence in the comment you're trying to discuss, and how it's used.

Re: It's OK to compare floating-points for equality

#136
post #93

My preference in tests is a little different than just using IEEE 754 ==, _Bool equiv(float x, float y) { return (x 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.

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.

Re: It's OK to compare floating-points for equality

#137
I cringed very hard in the slerp example seeing `acos(dot(a,b))`. Clamping to [-1,1] to avoid NaNs still gives you bad answers and numerical sensitivity around small angles. acos and asin in general lose ~half your sig figs around their singularities[0]. Working around this by introducing a threshold seems like exactly same flavor of issue he's complaining about to begin with.

There are perfectly good solutions for finding the angle using atan and the cross product. Calculating A x B will yield a vector which lies is along their normal with length tan(θ)(A · B) so we can straightforwardly say e.g.:

`θ = atan2(norm(A ⅹ B), (A · B))`

No thresholds, no branching, only ~1 bit of significance lost. As the original author says himself, when you're introducing arbitrary-feeling thresholds, it's likely you're missing a solution which would improve more than just the weirdness at those thresholds.

[0] A good analysis of this is on Page 46 here, including a more optimized (but less obviously true) alternative to the formula above: https://people.eecs.berkeley.edu/~wkahan/Mindless.pdf

Re: It's OK to compare floating-points for equality

#138

Earlier quoted context omitted.

> This is a fundamentally unsolvable problem with floating point math It's a fundamentally unsolvable problem with B-reps ! The problem completely disappears with F-reps. (In exchange for some other difficult problems).

>(In exchange for some other difficult problems). Ahhaha. (I used to work in nTop, and boy is this an understatement when it comes to field based solid modeling)

I was working on an SDF-based CAD tool but gave up when I couldn't find a good way to do fillets.

It's very deceptive because the easy way works so well (Use smoothmin instead of min and you get smooth blends for free! You can even use a circular approximation of smoothmin and get proper fillets!). But when you want the user to be able to pick a couple of surfaces and fillet between them, it gets really hard.

This is the best I got: https://www.youtube.com/watch?v=LOvqdlDbkBs

It worked by rewriting the expression tree so that the blend arguments become sibling nodes and then applying the blend to the union/intersection that is their parent.

That works every time if you only want 1 single targeted blend, but if you want several of them then you can run into unsatisfiable cases where the same object needs to blended with several others and can't be siblings of all of them.

So I gave up :(. For me, CAD without fillets and chamfers is no CAD at all.

(Also, apropos for this thread: the discontinuity in the chamfer was a floating point precision problem...)

Re: It's OK to compare floating-points for equality

#139
post #24
post #7

I have this floating-point problem at scale and will donate $100 to the author, or to anyone here, who can improve my code the most. The Rust code in the assert_f64_eq macro is: if (a >= b && a - b I'm the author of the Rust assertables crate. It provides floating-point assert macros much as described in the article. https://github.com/SixArm/assertables-rust-crate/blob/main/s... If there's a way to make it more prec…

I suggest if a.abs()+b.abs() >= (a-b).abs() * 2f64.powi(48) It remains accurate for small and for big numbers. 48 is slightly less than 52.

that will return incorrect answers if a=b and 2a=Inf

Re: It's OK to compare floating-points for equality

#140
post #7

I have this floating-point problem at scale and will donate $100 to the author, or to anyone here, who can improve my code the most. The Rust code in the assert_f64_eq macro is: if (a >= b && a - b I'm the author of the Rust assertables crate. It provides floating-point assert macros much as described in the article. https://github.com/SixArm/assertables-rust-crate/blob/main/s... If there's a way to make it more prec…

You want equality? ‘a.to_bits() == b.to_bits()’ Alternatively, use ‘partial_eq’ and fall back to bit equality if it returns None.

This would have worked if ieee hadn't severely messed up when (not) designing NaN semantics, but they did, so in rust, this can return false when comparing a NaN value to itself. (see the NaN section of https://doc.rust-lang.org/std/primitive.f32.html)
Post reply on HN