raw floating-point bits in '==' value object comparisons (again/still)

Remi Forax forax at univ-mlv.fr
Mon Mar 11 15:42:08 UTC 2024


----- Original Message -----
> From: "Clement Cherlin" <ccherlin at gmail.com>
> To: "Valhalla Expert Group Observers" <valhalla-spec-observers at openjdk.org>
> Sent: Monday, March 11, 2024 4:27:30 PM
> Subject: Re: raw floating-point bits in '==' value object comparisons (again/still)

> On Mon, Mar 11, 2024 at 8:34 AM Remi Forax <forax at univ-mlv.fr> wrote:
>>
>> Last week, I explain at JChateau (think JCrete in France, less sun, more
>> chateaux) how value types work from the user POV, among other subject
>> describing the semantics of ==.
>>
>> First, most of the attendee knew the semantics difference between == on double
>> and Double.equals(). I suppose it's because people that attend to such
>> (un-)conference have a more intimate knowledge of Java than an average
>> developer. Second, no attendee knew that NaN was a prefix.
>>
>> So it let me think again on that subject.
>>
>> 1) The argument that of Dan that we want to be able to create a class with two
>> different NaN, does not hold because instead of storing the values as double,
>> the values can be stored as long.
>>
>>   value class C {
>>       private double d;
>>       C(double d) { this.d = d; }
>>       long bits() { return Double.doubleToRawLongBits(d); }
>>   }
>>
>>   C c1 = new C(Double.longBitsToDouble(0x7ff0000000000001L));
>>   C c2 = new C(Double.longBitsToDouble(0x7ff0000000000002L));
>>   assert c1.bits() != c2.bits();
>>
>> can be rewritten as
>>
>>   value class C {
>>       private long l;
>>       C(double d) { this.l = Double.doubleToRawLongBits(d); }
>>       long bits() { return l; }
>>   }
>>
>>
>> 2) The de-duplication of value instances by the GC works with both the bitwise
>> equivalence and the representational equivalence.
>>
>>  If the GC only de-duplicate the value instance based only on the bitwise
>>  equivalence, it is a valid algorithm under the representational equivalence.
>>
>>
>> So I not convinced that the bitwise equivalence should be choosen instead of the
>> representational equivalence, for me two semantics instead of three is a win.
>>
>> Rémi
> 
> Let's make nobody happy and define new operators for new semantics, à
> la JavaScript:
> 
> Given double d1, d2;
> d1 === d2 uses the algorithm implemented in Double.equals()
> d1 ==== d2 uses the algorithm implemented in Double.equals() but with
> doubleToRawLongBits instead of doubleToLongBits.
> 
> Same goes for floats. For float ===/==== double, the float is coerced
> to double first, just as with == today.
> 
> These operators behave exactly the same as == for reference types
> (except primitive wrappers, which they unbox first if both are
> non-null) and non-floating-point primitive types, and apply their
> respective definitions of equality recursively to the fields of value
> types.
> 
> Alternatively, === is shorthand for Objects.equals() and we can all
> stop using == for 99% of purposes (again, like JavaScript).

The current plan to makes float/double and j.l.Float/j.l.Double more similar by allowing .equals() on primitive types.
so
  double d1 = ...
  double d1 = ...
  d1.equals(d2) is equivalent to new Double(d1).equals(new Double(d2))

which reduces the need to use ==.

The open question is allowing .equals() on literals, like 2.equals(a) because currenty the lexer will consumer '2.' as a double instead as '2' '.' 

> 
> Cheers,
> Clement Cherlin

regards,
Rémi


More information about the valhalla-spec-observers mailing list