<html><body><div style="font-family: arial, helvetica, sans-serif; font-size: 12pt; color: #000000"><div><br></div><div><br></div><hr id="zwchr" data-marker="__DIVIDER__"><div data-marker="__HEADERS__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><b>From: </b>"-" <liangchenblue@gmail.com><br><b>To: </b>"Remi Forax" <forax@univ-mlv.fr>, "valhalla-spec-experts" <valhalla-spec-experts@openjdk.org><br><b>Sent: </b>Monday, March 11, 2024 5:19:15 PM<br><b>Subject: </b>Re: raw floating-point bits in '==' value object comparisons (again/still)<br></blockquote></div><div data-marker="__QUOTED_TEXT__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div dir="ltr">Hi Remi,<div>I believe we can stick with bitwise equivalence, at least for now. The bitwise equivalence, in essence, is a remedy for the compatibility issues around == with the removal of identity. Thus, it should not replace and is still not preferable to equals(), even though the migration happens to make == a good implementation in many more cases.</div></div></blockquote><div><br></div><div>This is true for both the bitwise equivalence and the representational equivalent. The idea is not to replace equals(), but to provide an implementation of == for value types.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div dir="ltr"><br><div>For a Double object d, there are sets S0 that are all objects with the same identity as d, S1 that are all objects with the same bitwise representation (headers ignored of course) as d, S2 that are all objects with the same representational equivalence as d, and Sequals that are all objects that equals(d). We can see (<= for "is subset of") {d} == S0 <= S1 <= S2 <= (actually ==) Sequals, and whatever the set d == holds true against (call it S==) has S== <= Sequals.</div><br><div>With the removal of identity, S0 is gone, so S1 becomes S==. What you call for is to use S2 for S==. I believe that the move from S1 to S2 will be a backward-compatible change in the future, but not from S2 to S1. Given the significant performance benefits of using S1 for S== instead of S2, I believe we can stay with the bitwise equivalence and investigate using S2 for S== if the future hardware improvements make it feasible.</div></div></blockquote><div><br></div><div>I think you are too optimistic about the performance benefits of the bitwise equivalence compared to the representational equivalence. And having the right semantics is more important.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>The representational equivalence semantics is the same as the bitwise equivalence semantics for all types but floating points. So for a lot of value classes, there is no difference.<br data-mce-bogus="1"></div><div>You may think that having the bitwise equivalence semantics allows to compare the content the fields using wider registers but this is not true if you are using a concurrent GC (references may need patching), this is not true if a field is a non-null value type represented as a pointer (the default value may be encoded as null). So it's far from clear to me that there is a "significant" performance benefit. Yes, checking if a field is not the nominal NaN as a cost, but given that in most case, the branch will be never taken, we may have hard time to see the difference.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>And again, I prefer a clear and simple semantics compared to mostly the same semantics with a foot gun attached that fires if a NaN is encoded in a funny way.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div dir="ltr"><br><div>Regards,</div><div>Chen Liang</div></div></blockquote><div><br></div><div>Rémi<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Mar 11, 2024 at 8:34 AM Remi Forax <<a href="mailto:forax@univ-mlv.fr" target="_blank">forax@univ-mlv.fr</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">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 ==.<br>
<br>
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.<br>
<br>
So it let me think again on that subject.<br>
<br>
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.<br>
<br>
value class C {<br>
private double d;<br>
C(double d) { this.d = d; }<br>
long bits() { return Double.doubleToRawLongBits(d); }<br>
}<br>
<br>
C c1 = new C(Double.longBitsToDouble(0x7ff0000000000001L));<br>
C c2 = new C(Double.longBitsToDouble(0x7ff0000000000002L));<br>
assert c1.bits() != c2.bits();<br>
<br>
can be rewritten as<br>
<br>
value class C {<br>
private long l;<br>
C(double d) { this.l = Double.doubleToRawLongBits(d); }<br>
long bits() { return l; }<br>
} <br>
<br>
<br>
2) The de-duplication of value instances by the GC works with both the bitwise equivalence and the representational equivalence.<br>
<br>
If the GC only de-duplicate the value instance based only on the bitwise equivalence, it is a valid algorithm under the representational equivalence.<br>
<br>
<br>
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.<br>
<br>
Rémi<br>
</blockquote></div><br></blockquote></div></div></body></html>