Incomparable Puzzles in Java
Here are a few puzzles for you to solve in Java. The source is available here: UncomparablePuzzles.java.
Puzzle 1: Comparing long
and double
Try running the following code to reproduce the output below. See if you can work out why these results occur:
long a = (1L << 54) + 1;
double b = a;
System.out.println("b == a is " + (b == a));
System.out.println("(long) b < a is " + ((long) b < a));
When executed, it produces:
b == a is true
(long) b < a is true
Analysis
This puzzle highlights the precision limitations when converting between long
and double
.
-
Precision Loss During Conversion The
long
valuea
is(1L << 54) + 1
, which is18014398509481985
. When cast todouble
,b
becomes18014398509481984.0
. Due todouble
's 53-bit mantissa, it cannot accurately represent everylong
value beyond this range, resulting in precision loss. -
Equality Comparison (
b == a
) Despiteb
losing precision,b == a
evaluates totrue
becausea
exceeds the precision range ofdouble
. Botha
andb
effectively represent the samedouble
value. -
Casting Back to
long
and Comparison Castingb
back tolong
truncates the decimal part, yielding18014398509481984
, which is less than the originala
. Hence,(long) b < a
returnstrue
.
Puzzle 2: Large double
to long
Conversion
Examine the following code:
double c = 1e19;
long d = 0;
d += c;
System.out.println("\nd < c is " + (d < c));
System.out.println("d < (long) c is " + (d < (long) c));
Produces:
d < c is true
d < (long) c is false
Analysis
This puzzle demonstrates the intricacies of type conversion and arithmetic operations in Java.
-
Initial Assignment
c
is assigned1e19
, a value that exceeds the maximum value along
can hold (Long.MAX_VALUE
is approximately9.22e18
). Addingc
tod
results ind
becomingInfinity
due to overflow. -
Comparisons
-
d < c
: Sinced
is nowInfinity
, andc
is1e19
, the comparisonInfinity < 1e19
isfalse
. -
d < (long) c
: Castingc
tolong
results in overflow, yieldingLong.MIN_VALUE
. Thus,Infinity < Long.MIN_VALUE
isfalse
.
-
Puzzle 3: Double
Object Comparisons
Consider the following code:
Double e = 0.0;
Double f = 0.0;
System.out.println("\ne <= f is " + (e <= f));
System.out.println("e >= f is " + (e >= f));
System.out.println("e == f is " + (e == f));
Produces:
e <= f is true
e >= f is true
e == f is false
Analysis
This puzzle explores the behaviour of object comparisons in Java.
-
Comparison Operators (
<=
and>=
) These operators compare the primitivedouble
values ofe
andf
, both0.0
, resulting intrue
for both comparisons. -
Equality Operator (
==
) The==
operator checks for reference equality. Sincee
andf
are distinctDouble
objects,e == f
evaluates tofalse
despite representing the same numeric value.
Puzzle 4: BigDecimal
Equality and Comparison
Examine the following code:
BigDecimal x = new BigDecimal("0.0");
BigDecimal y = BigDecimal.ZERO;
System.out.println("\nx == y is " + (x == y));
System.out.println("x.doubleValue() == y.doubleValue() is " + (x.doubleValue() == y.doubleValue()));
System.out.println("x.equals(y) is " + x.equals(y));
System.out.println("x.compareTo(y) == 0 is " + (x.compareTo(y) == 0));
Produces:
x == y is false
x.doubleValue() == y.doubleValue() is true
x.equals(y) is false
x.compareTo(y) == 0 is true
Analysis
This puzzle delves into the comparison mechanics of the BigDecimal
class.
-
Reference Equality (
x == y
)x
andy
are different objects; hence,x == y
isfalse
. -
Primitive Equality (
doubleValue()
) Bothx
andy
convert to the same primitivedouble
value0.0
, makingx.doubleValue() == y.doubleValue()
true
. -
equals
MethodBigDecimal.equals()
considers both value and scale.x
has a scale of1
, whiley
has a scale of0
, leading tox.equals(y)
beingfalse
. -
compareTo
MethodcompareTo
only considers the numeric value, ignoring scale differences. Therefore,x.compareTo(y) == 0
istrue
.
Bonus Puzzle: The Shrinking Collections
This bonus puzzle involves a collection of BigDecimal
values. Take a look at the following code:
List<BigDecimal> bds = Arrays.asList(
new BigDecimal("1"),
new BigDecimal("1.0"),
new BigDecimal("1.00"),
BigDecimal.ONE
);
System.out.println("bds.size()= " + bds.size());
Set<BigDecimal> bdSet = new HashSet<>(bds);
System.out.println("bdSet.size()= " + bdSet.size());
Set<BigDecimal> bdSet2 = new TreeSet<>(bds);
System.out.println("bdSet2.size()= " + bdSet2.size());
Output
bds.size()= 4
bdSet.size()= 3
bdSet2.size()= 1
Explanation
At first glance, it might seem surprising that the sizes of the collections are shrinking. Here’s why this happens:
1. bds.size()= 4
The bds
list contains four distinct BigDecimal
objects, so bds.size()
returns 4. However, we need to understand why the set sizes shrink.
2. bdSet.size()= 3
A HashSet
in Java removes duplicates based on the equals()
method. In this case, BigDecimal
uses the equals()
method to check for equality. The values new BigDecimal("1")
, new BigDecimal("1.0")
, and new BigDecimal("1.00")
are considered equal because they all represent the same numeric value: 1.0. So, the HashSet
eliminates two of the four entries, leaving only three elements in the set.
3. bdSet2.size()= 1
A TreeSet
uses the compareTo()
method to determine uniqueness and respects objects' natural ordering. In this case, BigDecimal
compares the values numerically, so all the BigDecimal
objects in the list are considered equal because they all represent the value 1.0. Therefore, only one element is retained in the TreeSet
, and the size is 1.
Key Takeaways
-
Converting between
long
anddouble
can lead to precision loss, affecting equality and comparison operations. -
Operators like
==
behave differently for object references versus primitive values. -
The
equals()
method considers scale, whilecompareTo()
does not, impacting howBigDecimal
instances are treated in collections. -
Different
Set
implementations (HashSet
vs.TreeSet
) handle object uniqueness based on their respective equality mechanisms.
By understanding the behaviour of the HashSet
and TreeSet
with respect to the BigDecimal
class, you can better predict the results of such operations in your code.
Conclusion
In Java, the choice of collection can lead to surprising results when working with classes with custom equality and comparison behaviour. In this puzzle, the BigDecimal
class reveals its nuances when used with HashSet
and TreeSet
, leading to shrinking collection sizes.
About the Author
As the CEO of Chronicle Software, Peter Lawrey leads the development of cutting-edge, low-latency solutions trusted by 8 out of the top 11 global investment banks. With decades of experience in the financial technology sector, he specialises in delivering ultra-efficient enabling technology that empowers businesses to handle massive volumes of data with unparalleled speed and reliability. Follow Peter on BlueSky or Mastodon.
Comments
Post a Comment