Uncomparable puzzles in Java

This is a few puzzles for you to solve in Java.

b == a is true
(long) b < a is true

d < c is true
d < (long) c is false

e <= f is true
e >= f is true
e == f is false

x == y is false
x.doubleValue() == y.doubleValue() is true
x.equals(y) is false
x.compareTo(y) == 0 is true

See if you can work out why this code produces the output above (if you use StackOverflow, I will know ;)

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));

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));

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));

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));

Bonus puzzle:  The Shrinking collections.

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());


bds.size()=    4
bdSet.size()=  3
bdSet2.size()= 1


  1. 1. a and b, the equality is simple Java casts a to double prior to comparison, and the b was populated with the very same cast, so no wonders there - they are equal. Secondly the comparison: it is a Narrowing Primitive Conversion "if the floating-point number is not an infinity, the floating-point value is rounded to an integer value V, rounding toward zero using IEEE 754 round-toward-zero mode" so the b value became less then a, since loosing some digits with rounding.
    2. 1e19 is bigger the Long.MAX_VALUE. So according to "The value must be too large (a positive value of large magnitude or positive infinity), and the result of the first step is the largest representable value of type int or long" d is assigned with Long.MAX_VALUE, so first comparison shows that c is greater, and second comparison is narrowing conversion, which makes d and (long) c equal. That's why it returns false, d == (long) c is true.
    3. Nice one. We have two different Double objects boxed from double literal 0.0. With comparison operators >, < >=, <= the Double objects are unboxed to primitives and compared as double primitives, so all the comparison works as expected. But the equality operator == does not do the unboxing, so this is simple equality check, which returns false for different Double objects.
    4. First equality check is the same as for previous puzzle - false for different objects. Second check, doubleValue returns double 0.0 for both: x is converted from string representation of BigDecimal "0.0" with Double.parseDouble, which is obviously = 0.0, y is converted from integer conversion of intVal field which is int 0 for ZERO, and in turn this will give double 0.0. The trickiest part is equals - the false result is received due to different scales, and equals method implementation does have this statement: "objects equal only if they are equal in value and scale (thus 2.0 is not equal to 2.00 when compared by this method)". For x we have one according to the javadoc of the BigDecimal class "The scale of the returned {@code BigDecimal} will be the number of digits in the fraction, or zero if the string contains no decimal point". For y we have scale = 0, as it is passed in constructor for ZERO constant. And last one compareTo uses signum which is 0 for both objects as they are zero representation "Two {@code BigDecimal} objects that are equal in value but have a different scale (like 2.0 and 2.00) are considered equal by this method.", so true here.

    bonus: according to the equals and compareTo the HashMap is using equals to compare objects in the bucket, so BigDecimal("1") and BigDecimal.ONE are equal from equals perspective, so only one of them was added - size is reduced by one. CompareTo returns 0 for all of the versions of BigDecimal object: BigDecimal("1.00"), BigDecimal("1.0"), BigDecimal("1"), BigDecimal.ONE, so only one object is residing in the collection in the end.

  2. 1. It appears that the value of 'b' is actually less than 'a' by 1 due to the loss of precision at assignment b=a. This fact is muddled by the positive outcome of the test b == a. The reason we are deceived is that this comparison does not strictly compare 'b' and 'a'. It compares 'b' to value that results from promoting 'a' to a double. This promotion is implicit in the comparison. And that value is 1 less than 'a' and therefore equal to b.

    We can actually see that b == a-1 is true!!!

    This fact is confirmed when we compare (long)b < a. Here the actual values of 'b' and 'a' are compared without loss of precision: a-1 < a! because cast to long is lossless.

    It is the + 1 that created inequality of 'b' and 'a'. The problem is that the value of 'a' is larger than the m-part of a double can hold. 'a' is (1<<54) + 1 while 'm' of a double must be less than 1<<53. The conversion is forced to round to nearest by discarding 1 in the lowest bit. By discarding 1 'a' becomes 1<<54 or 2^54 but it can now be represented with an 'm' of only 2 as 2*2^53.


Post a Comment

Popular posts from this blog

Low Latency Microservices, A Retrospective

Unusual Java: StackTrace Extends Throwable

System wide unique nanosecond timestamps