float equality

Don nospam at nospam.com
Sat Feb 19 07:40:35 PST 2011


spir wrote:
> On 02/19/2011 01:21 PM, Jonathan M Davis wrote:
>> On Saturday 19 February 2011 04:06:38 spir wrote:
>>> Hello,
>>>
>>> What do you think of this?
>>>
>>> unittest {
>>>       assert(-1.1 + 2.2 == 1.1);          // pass
>>>       assert(-1.1 + 2.2 + 3.3 == 4.4);    // pass
>>>       assert(-1.1 + 3.3 + 2.2 == 4.4);    // fail
>>>       assert(-1.1 + 3.3 == 2.2);          // fail
>>> }
>>>
>>> There is approxEquals in stdlib, right; but shouldn't builtin "==" be
>>> consistent anyway?
>>
>> == is completely consistent. The floating point values in question are 
>> _not_
>> equal. The problem is that floating point values are inexact. There 
>> are values
>> which a floating point _cannot_ hold, so it holds the closest value 
>> that it can.
>> This can quickly and easily lead to having two floating points which 
>> seem like
>> they should be equal but which aren't. == checks that they're equal, 
>> which is
>> exactly what it should do (especially in a systems language).
> 
> I know about inexact representation, due to bin <--> dec conversion.
> Well, about consistency, I meant cases 2 vs 3.
> 
>> The thing is, of course, that actual equality sucks for floating point 
>> values.
>> What's really needed in something like approxEquals. So, approxEquals 
>> really
>> _should_ be used for comparing floating points and == should only be 
>> used on them
>> when that is actually what you need (which would be extremely rare). 
>> However,
>> you can't really make == do approxEquals, because that means that == 
>> isn't
>> really exact equality anymore and because then it makes it hard to 
>> actually
>> compare floating point values for equality if that's what you actually 
>> want.
>> Making == do approxEquals instead would make == inconsistent, not 
>> consistent.
>>
>> This is just one of those cases where the programmer needs to know 
>> what they're
>> doing and not use ==.
> 
> Right, that's what I thought. I guess what people mean in 99% cases is 
> "conceptual" equality, right?

> They don't care about internal 
> representation if they are not forced to. So, maybe, default '==' should 
> perform approxEqual if needed? 

Approximate equality is not problem-free, either. You still need to know 
what you're doing. Regardless of what tolerance you use, there will be 
values which are just inside and just outside the tolerance, which in 
pure mathematics would be equal to each other.

And we may have a binEqual just for
> floats when someone really needs binary repr equality? (I cannot 
> impagine a use case, but well...)

There are actually a lot of use cases. Most obviously in unit tests, 
where you're testing for exact rounding. There is only one correct answer.

> What do you think?
> 
> I any case, what value should one use as tolerance range for approxEqual?

That's exactly the problem! There is no good universal default value.

My very first contribution to D was a floating point function called 
approxEqual, but someone insisted that it be called 'feqrel'. Which has 
pretty much ensured that everyone misses it. :(
It defines approximate equality in the correct way, in terms of numbers 
of significant bits. Basically, every time you have a multiply, you lose 
one bit. Addition can lose multiple bits.


More information about the Digitalmars-d mailing list