You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by Cyrille Artho <c....@aist.go.jp> on 2014/04/15 01:03:51 UTC

[math] Issue with equals/hashCode on Complex/Dfp instances

Dear all,
Java's default contract (in Object) states that two objects with equal data 
should return the same hashCode. In other words, if a.equals(b), their 
return value of hashCode() must be the same.

Unfortunately, with Double values, new Double(0.0d).equals(new 
Double(-0.0d)) is false. This is because their internal representation 
differs. Java's built-in Double thus returns a different hashCode for 0.0d 
and -0.0d.

However, Apache's math library uses a mathematical comparison for 
Complex/Dfp (perhaps also others), where 0.0d == -0.0d. This breaks the 
contract, and thus causes problems when Complex or Dfp instances are used 
in containers such as HashMap, HashSet, etc. See the bug report for more 
details and test cases:

https://issues.apache.org/jira/browse/MATH-1118

It is not quite clear how this should be fixed. Gilles posted a patch that 
uses Java's equals to perform the comparison. This fixes the behavior 
w.r.t. the contract but may surprise people who expect a mathematical 
comparison.

Another possible fix (without a tentative patch at this time) would be to 
change hashCode:

     public int hashCode() {
         if (imaginary == 0.0d && real == 0.0d) {
             return ZERO.hashCode();
         }
     ...

and similar for Dfp.

This fixes the issue for zero values. However, there may be issues with 
normalized vs. unnormalized floating point values with the same 
(mathematical) value but different internal representations, where this 
kind of fix cannot be used. I'm not familiar enough with various floating 
point implementations to know if Java always normalizes the values before 
using them in a statement like hashCode().

Therefore, the suggestion above is mathematically nice for +/- 0.0d, but 
otherwise much less safe than Gilles' patch.

Any comments? Probably best post them on JIRA:

https://issues.apache.org/jira/browse/MATH-1118
-- 
Regards,
Cyrille Artho - http://artho.com/
Better once than never, for never too late.
		-- Shakespeare, "The Taming of the Shrew"

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@commons.apache.org
For additional commands, e-mail: dev-help@commons.apache.org


Re: [math] Issue with equals/hashCode on Complex/Dfp instances

Posted by Gilles <gi...@harfang.homelinux.org>.
On Tue, 15 Apr 2014 09:52:08 -0700, Phil Steitz wrote:
> On 4/15/14, 5:19 AM, Gilles wrote:
>> On Tue, 15 Apr 2014 08:03:51 +0900, Cyrille Artho wrote:
>>> Dear all,
>>> Java's default contract (in Object) states that two objects with
>>> equal data should return the same hashCode. In other words, if
>>> a.equals(b), their return value of hashCode() must be the same.
>>>
>>> Unfortunately, with Double values, new Double(0.0d).equals(new
>>> Double(-0.0d)) is false. This is because their internal
>>> representation
>>> differs. Java's built-in Double thus returns a different hashCode
>>> for
>>> 0.0d and -0.0d.
>>>
>>> However, Apache's math library uses a mathematical comparison for
>>> Complex/Dfp (perhaps also others), where 0.0d == -0.0d. This breaks
>>> the contract, and thus causes problems when Complex or Dfp 
>>> instances
>>> are used in containers such as HashMap, HashSet, etc. See the bug
>>> report for more details and test cases:
>>>
>>> https://issues.apache.org/jira/browse/MATH-1118
>>>
>>> It is not quite clear how this should be fixed. Gilles posted a
>>> patch
>>> that uses Java's equals to perform the comparison. This fixes the
>>> behavior w.r.t. the contract but may surprise people who expect a
>>> mathematical comparison.
>>>
>>> Another possible fix (without a tentative patch at this time) would
>>> be to change hashCode:
>>>
>>>     public int hashCode() {
>>>         if (imaginary == 0.0d && real == 0.0d) {
>>>             return ZERO.hashCode();
>>>         }
>>>     ...
>>>
>>> and similar for Dfp.
>>>
>>> This fixes the issue for zero values. However, there may be issues
>>> with normalized vs. unnormalized floating point values with the 
>>> same
>>> (mathematical) value but different internal representations, where
>>> this kind of fix cannot be used. I'm not familiar enough with
>>> various
>>> floating point implementations to know if Java always normalizes 
>>> the
>>> values before using them in a statement like hashCode().
>>>
>>> Therefore, the suggestion above is mathematically nice for +/- 
>>> 0.0d,
>>> but otherwise much less safe than Gilles' patch.
>>>
>>> Any comments? Probably best post them on JIRA:
>>>
>>> https://issues.apache.org/jira/browse/MATH-1118
>>
>> IIUC, fixing the "hashCode" method as above would not solve the
>> problem
>> since it deals only with "0" whereas the unit test fails for "-i".
>
> Thanks.  I get that now.
>>
>> Do we agree that we first have to decide whether "Complex" should
>> behave
>> as "Double"?
>>
>> If the answer is "no" (as is the case now), shouldn't we document
>> that
>> "hash tables will not operate properly" (cf. Javadoc for "Double")?
>> And is this behaviour acceptable?
>
> I don't think having equal instances hashing to different values is
> acceptable.  We have to do something to fix that.
>>
>> One possibility is to have the "equals(Complex)" behave according to
>> the JDK semantics, and add a static method "equals(Complex, 
>> Complex)"
>> that would implement mathematical comparison (where "-0" == "0").
>
> I like the idea of imitating the way Double works for equals.
>

I've uploaded[1] a tentative patch that
  1. changes the behaviour of "equals(Object)", so that it has it has 
the
     same semantics as "Double",
  2. adds static methods to test floating-point equality of instances 
(by
     delegating to the corresponding methods in class "Precision").


Regards,
Gilles

[1] https://issues.apache.org/jira/browse/MATH-1118


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@commons.apache.org
For additional commands, e-mail: dev-help@commons.apache.org


Re: [math] Issue with equals/hashCode on Complex/Dfp instances

Posted by Phil Steitz <ph...@gmail.com>.
On 4/15/14, 5:19 AM, Gilles wrote:
> On Tue, 15 Apr 2014 08:03:51 +0900, Cyrille Artho wrote:
>> Dear all,
>> Java's default contract (in Object) states that two objects with
>> equal data should return the same hashCode. In other words, if
>> a.equals(b), their return value of hashCode() must be the same.
>>
>> Unfortunately, with Double values, new Double(0.0d).equals(new
>> Double(-0.0d)) is false. This is because their internal
>> representation
>> differs. Java's built-in Double thus returns a different hashCode
>> for
>> 0.0d and -0.0d.
>>
>> However, Apache's math library uses a mathematical comparison for
>> Complex/Dfp (perhaps also others), where 0.0d == -0.0d. This breaks
>> the contract, and thus causes problems when Complex or Dfp instances
>> are used in containers such as HashMap, HashSet, etc. See the bug
>> report for more details and test cases:
>>
>> https://issues.apache.org/jira/browse/MATH-1118
>>
>> It is not quite clear how this should be fixed. Gilles posted a
>> patch
>> that uses Java's equals to perform the comparison. This fixes the
>> behavior w.r.t. the contract but may surprise people who expect a
>> mathematical comparison.
>>
>> Another possible fix (without a tentative patch at this time) would
>> be to change hashCode:
>>
>>     public int hashCode() {
>>         if (imaginary == 0.0d && real == 0.0d) {
>>             return ZERO.hashCode();
>>         }
>>     ...
>>
>> and similar for Dfp.
>>
>> This fixes the issue for zero values. However, there may be issues
>> with normalized vs. unnormalized floating point values with the same
>> (mathematical) value but different internal representations, where
>> this kind of fix cannot be used. I'm not familiar enough with
>> various
>> floating point implementations to know if Java always normalizes the
>> values before using them in a statement like hashCode().
>>
>> Therefore, the suggestion above is mathematically nice for +/- 0.0d,
>> but otherwise much less safe than Gilles' patch.
>>
>> Any comments? Probably best post them on JIRA:
>>
>> https://issues.apache.org/jira/browse/MATH-1118
>
> IIUC, fixing the "hashCode" method as above would not solve the
> problem
> since it deals only with "0" whereas the unit test fails for "-i".

Thanks.  I get that now.
>
> Do we agree that we first have to decide whether "Complex" should
> behave
> as "Double"?
>
> If the answer is "no" (as is the case now), shouldn't we document
> that
> "hash tables will not operate properly" (cf. Javadoc for "Double")?
> And is this behaviour acceptable?

I don't think having equal instances hashing to different values is
acceptable.  We have to do something to fix that.
>
> One possibility is to have the "equals(Complex)" behave according to
> the JDK semantics, and add a static method "equals(Complex, Complex)"
> that would implement mathematical comparison (where "-0" == "0").

I like the idea of imitating the way Double works for equals.

Phil
>
>
> Regards,
> Gilles
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@commons.apache.org
> For additional commands, e-mail: dev-help@commons.apache.org
>
>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@commons.apache.org
For additional commands, e-mail: dev-help@commons.apache.org


Re: [math] Issue with equals/hashCode on Complex/Dfp instances

Posted by Gilles <gi...@harfang.homelinux.org>.
On Tue, 15 Apr 2014 08:03:51 +0900, Cyrille Artho wrote:
> Dear all,
> Java's default contract (in Object) states that two objects with
> equal data should return the same hashCode. In other words, if
> a.equals(b), their return value of hashCode() must be the same.
>
> Unfortunately, with Double values, new Double(0.0d).equals(new
> Double(-0.0d)) is false. This is because their internal 
> representation
> differs. Java's built-in Double thus returns a different hashCode for
> 0.0d and -0.0d.
>
> However, Apache's math library uses a mathematical comparison for
> Complex/Dfp (perhaps also others), where 0.0d == -0.0d. This breaks
> the contract, and thus causes problems when Complex or Dfp instances
> are used in containers such as HashMap, HashSet, etc. See the bug
> report for more details and test cases:
>
> https://issues.apache.org/jira/browse/MATH-1118
>
> It is not quite clear how this should be fixed. Gilles posted a patch
> that uses Java's equals to perform the comparison. This fixes the
> behavior w.r.t. the contract but may surprise people who expect a
> mathematical comparison.
>
> Another possible fix (without a tentative patch at this time) would
> be to change hashCode:
>
>     public int hashCode() {
>         if (imaginary == 0.0d && real == 0.0d) {
>             return ZERO.hashCode();
>         }
>     ...
>
> and similar for Dfp.
>
> This fixes the issue for zero values. However, there may be issues
> with normalized vs. unnormalized floating point values with the same
> (mathematical) value but different internal representations, where
> this kind of fix cannot be used. I'm not familiar enough with various
> floating point implementations to know if Java always normalizes the
> values before using them in a statement like hashCode().
>
> Therefore, the suggestion above is mathematically nice for +/- 0.0d,
> but otherwise much less safe than Gilles' patch.
>
> Any comments? Probably best post them on JIRA:
>
> https://issues.apache.org/jira/browse/MATH-1118

IIUC, fixing the "hashCode" method as above would not solve the problem
since it deals only with "0" whereas the unit test fails for "-i".

Do we agree that we first have to decide whether "Complex" should 
behave
as "Double"?

If the answer is "no" (as is the case now), shouldn't we document that
"hash tables will not operate properly" (cf. Javadoc for "Double")?
And is this behaviour acceptable?

One possibility is to have the "equals(Complex)" behave according to
the JDK semantics, and add a static method "equals(Complex, Complex)"
that would implement mathematical comparison (where "-0" == "0").


Regards,
Gilles


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@commons.apache.org
For additional commands, e-mail: dev-help@commons.apache.org