Value object equality & floating-point values
Dan Smith
daniel.smith at oracle.com
Fri Feb 9 18:56:14 UTC 2024
On Feb 9, 2024, at 10:13 AM, Remi Forax <forax at univ-mlv.fr> wrote:
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();
Will this assert ever fail? Well, it depends on the JVM treats c1 and c2 as belonging to the same equivalence class. If they are, it's allowed to substitute c1 for c2 at any time. I think it's pretty clear that would be a mistake.
I do not compute that statement :)
Why do you want users to care about the bitwise representation of NaN ?
Both 0x7ff0000000000001L and 0x7ff0000000000002L represents NaN, if we print c1.d and c2.d both will print NaN, if we use c1.d or c2.d in numeric computation, they will both behave as NaN.
To be very specific about this example, I think it's bad if the result of the 'c.bits()' method is nondeterministic, making the 'assert' result unpredictable.
Two instances of C with representationally-equivalent state can produce different results from their 'c.bits()' method. So they aren't substitutable (per the "can substituted for one another without changing the result of the expression" definition). I would be uncomfortable with the JVM substituting one for the other whenever it wants to.
Sure, the *native* operations on *double* almost never distinguish between different NaN encodings. But a *custom* operation on *a class that wraps a double* certainly can.
(The example could be improved by doing a better job of illustrating that the double is private internal state, and that the operations exposed by the class need not look at all like floating-point operations, as far as the client of the class is concerned. All they know is they've got an object that is randomly producing nondeterministic results.)
This, by itself, is not an argument for '==' being defined to use bitwise equivalence, but it is an argument for a well-defined concept of "substitutable value object" that is based on bitwise equivalence.
Using your example, but with a value record (supposing the bitwise equivalence)
value record C(double d) { }
C c1 = new C(Double.longBitsToDouble(0x7ff0000000000001L));
C c2 = new C(Double.longBitsToDouble(0x7ff0000000000002L));
System.out.println(c1); // C[d=NaN]
System.out.println(c2); // C[d=NaN]
System.out.println(c1 == c2); // false ??
System.out.println(c1.equals(c2)); // true
Sure. I mean, this is the exact same result as you get with an identity record, so I don't think it should be surprising. The fallacy, I think, is in expecting '==' for value objects to be something more than a substitutability test. (Which, like I said, could be done, but it seems like a distraction when what you really want to use is 'equals'.)
Compare:
value record S(String s) {}
S s1 = new S("abc");
S s2 = new S("abcd".substring(0,3));
System.out.println(s1); // S[s=abc]
System.out.println(s2); // S[s=abc]
System.out.println(s1 == s2); // false
System.out.println(s1.quals(s2)); // true
This may be a new concept to learn: value objects with double fields can be 'equals' but not '==', just like value objects with reference fields can be 'equals' but not '=='. But I think that's a better quirk to learn than '==' sometimes not meaning "substitutable".
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-spec-experts/attachments/20240209/d2a1d6f5/attachment.htm>
More information about the valhalla-spec-experts
mailing list