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
longvalueais(1L << 54) + 1, which is18014398509481985. When cast todouble,bbecomes18014398509481984.0. Due todouble's 53-bit mantissa, it cannot accurately represent everylongvalue beyond this range, resulting in precision loss. -
Equality Comparison (
b == a) Despiteblosing precision,b == aevaluates totruebecauseaexceeds the precision range ofdouble. Bothaandbeffectively represent the samedoublevalue. -
Casting Back to
longand Comparison Castingbback tolongtruncates the decimal part, yielding18014398509481984, which is less than the originala. Hence,(long) b < areturnstrue.
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
cis assigned1e19, a value that exceeds the maximum value alongcan hold (Long.MAX_VALUEis approximately9.22e18). Addingctodresults indbecomingInfinitydue to overflow. -
Comparisons
-
d < c: Sincedis nowInfinity, andcis1e19, the comparisonInfinity < 1e19isfalse. -
d < (long) c: Castingctolongresults in overflow, yieldingLong.MIN_VALUE. Thus,Infinity < Long.MIN_VALUEisfalse.
-
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 primitivedoublevalues ofeandf, both0.0, resulting intruefor both comparisons. -
Equality Operator (
==) The==operator checks for reference equality. Sinceeandfare distinctDoubleobjects,e == fevaluates tofalsedespite 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)xandyare different objects; hence,x == yisfalse. -
Primitive Equality (
doubleValue()) Bothxandyconvert to the same primitivedoublevalue0.0, makingx.doubleValue() == y.doubleValue()true. -
equalsMethodBigDecimal.equals()considers both value and scale.xhas a scale of1, whileyhas a scale of0, leading tox.equals(y)beingfalse. -
compareToMethodcompareToonly considers the numeric value, ignoring scale differences. Therefore,x.compareTo(y) == 0istrue.
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
longanddoublecan 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 howBigDecimalinstances are treated in collections. -
Different
Setimplementations (HashSetvs.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