1

I try to have find solution to get away from these heavy problems of number comparisons in Java... at least for numbers in a certain range with a certain precision with certain representation error.

The task is to implement an equality check for numbers that are implementing the Number-interface of the java.lang package. Here again: I would restrict the equality check to the obvious types: byte, short, integer, long, float, double and their object equivalents. Of course BigInteger and BigDecimal should be handled as well.

My first idea was to normalize all numbers to a double value. I came up with following:

public class MyNumber {


    private double raw;


    public MyNumber(Number raw) {
        this.raw = Double.parseDouble(raw.toString());
    }


    @Override
    public int hashCode() {
        return new Double(this.raw).hashCode();
    }


    @Override
    public boolean equals(Object obj) {

        if (obj instanceof MyNumber) {

            MyNumber that = (MyNumber) obj;

            return Double.toString(that.raw).equals(Double.toString(this.raw));

        }

        return false;
    }


}

I do not search for fancy all covering solution. A solution that keeps me away from the technical explainable but semantic nonsense of...

    BigDecimal x = new BigDecimal("1");
    BigDecimal y = new BigDecimal("1.00");
    Assert.assertFalse(x.equals(y));

I wrote some test cases to show what I mean:

public class TestMyNumber {


    /**
     * Small numbers are equal if they are semantically equal.
     */
    @Test
    public void test1() {

        MyNumber myNumber1 = new MyNumber(0.000000000000001f);
        MyNumber myNumber2 = new MyNumber(new BigDecimal("0.000000000000001"));

        Assert.assertEquals(myNumber1, myNumber2);

    }


    /**
     * Big numbers are seen as equal if they are semantically equal.
     */
    @Test
    public void test3() {

        MyNumber myNumber1 = new MyNumber(999999999999999.9);
        MyNumber myNumber2 = new MyNumber(new BigDecimal("999999999999999.9"));

        Assert.assertEquals(myNumber1, myNumber2);

    }


    /**
     * Big numbers are seen as equal if they are semantically equal.
     */
    @Test
    public void test4() {

        MyNumber myNumber1 = new MyNumber(999999999999999.0);
        MyNumber myNumber2 = new MyNumber(999999999999999l);

        Assert.assertEquals(myNumber1, myNumber2);

    }


    /**
     * Really big numbers are equal. Error is acceptable.
     */
    @Test
    public void test5() {

        MyNumber myNumber1 = new MyNumber(544785684365874268756.1);
        MyNumber myNumber2 = new MyNumber(new BigDecimal("544785684365874268756.9"));

        Assert.assertEquals(myNumber1, myNumber2);

    }


}

My primary goal is to abstract from precision and representation. At least within a range. Speed is nothing I currently care about.

Do you have any hints to achieve this abstraction?

Next Approach:

public class MyNumber {


    private BigDecimal raw;


    public MyNumber(Number raw) {
        this.raw = new BigDecimal(Double.parseDouble(raw.toString())).stripTrailingZeros();
    }


    @Override
    public int hashCode() {
        return this.raw.hashCode();
    }


    @Override
    public boolean equals(Object obj) {

        if (obj instanceof MyNumber) {

            MyNumber that = (MyNumber) obj;

            return that.raw.equals(this.raw);

        }

        return false;
    }


}

Notes:

Semantically equal numbers are numbers that you can exchange in any operation you want and you do not know if they were exchanged at all because the result remains the same. The point is: 2 is equal to 2.0 is equal to 2.000000000.

MyNumbers will never be a result of an operation only an input parameter.

A Number should be able to be checked for equality. I don't want to care about the technical representation.

8
  • 6
    I believe that "abstraction" does not usually mean "introduce significant errors before doing anything". MyNumber does a double conversion, from Number to string to Double, going from base N to base 10 to base 2. Do you understand how much error that introduces into your numbers? Can you quantify it? Can you quantify the term "semantically equal"? Dec 6, 2016 at 22:40
  • 1
    A couple of initial reactions. First, all the integer types are trivial, right? Convert to long and compare. Am I misunderstanding something? As for the floating points, why do you want 24 digits of precision? Not much in the universe has that much precision.
    – user949300
    Dec 7, 2016 at 0:41
  • So you want to store a BigDecimal into a double ?
    – Spotted
    Dec 7, 2016 at 7:40
  • No. I have to deal with an arbitrary input type of "Number".
    – oopexpert
    Dec 7, 2016 at 7:45
  • 2
    Comparing floats is not a good idea at all. They can differ in their mantissa when having a high negative exponent. You should check Python for its bignum implementation.
    – user188153
    Dec 7, 2016 at 8:18

1 Answer 1

1

It's not possible to normalize all Numbers to a binary floating point value.

Numbers are a more complicated issue than you seem to recognize. It's not only about "Java having heavy problems", although handling numbers in Java is cumbersome.

There's a variety of fundamentally different types of numbers: integers, decimal numbers, floating point numbers, fractions, real numbers, complex numbers etc. Some numbers of different types are not mathematically compatible with each other. For instance, there's no an integer representation for the decimal number 0.1. There's no binary floating point representation for it either.

It's strictly not possible to write an equals method for any two Numbers of possibly different types. That's because the class Number doesn't contain any functionality or contract for checking equality. Anyone can subclass it with any custom definition of equality for objects of that particular subclass.

However, it's possible to write a method that's capable of comparing every Number that Java currently provides. For that purpose you could normalize the Numbers to BigDecimals and then use the compareTo-method of the BigDecimal class. It's not very efficient but it should work.


Answer to a comment below:

Attempts to define equality by considering numbers that are close to each other as equal will lead to problems. At least if you're trying to achieve a proper equivalence with transitive property.

For instance, there is an infinite amount of decimal numbers around a floating point number that are farther away from any other floating point number of the same precision. If they are considered equal to that "central" floating point number, then by transitive property they should be considered equal to each other as well.

Taking integers into account would lead to an even more broad definition of equality. Using half up rounding rule the exact double 0.5 would be equal to the integer 1 as well as some decimal 0.5 - ε that would be equal to the integer 0. By transitive property 0 would be equal to 1. All numbers between 0 and 1 would be equal as well.

3
  • So if 0.1 has no integer representation you can clearly say that ANY integer value cannot be equal to 0.1. As 0.1 has no exact binary floating representation it will be represented as a slightly different value that is exact. As long as ALL 0.1 will be represented the same slightly different exact way I would like to see them as equal as this is the best guess we can do. Also if some other value may fall into the same equivilence class.
    – oopexpert
    Dec 7, 2016 at 19:55
  • @oopexpert As COME FROM says, numbers are a more complicated issue than you think they are. For example, did you know that for the number A=9,223,372,036,854,775,805, long(A) != long(A+1) but double(A) == double(A+1) because double starts to lose precision on such large numbers? I'd recommend reading some of the work on floating point comparisons (such as that done by Knuth) to realize what a minefield numbers can be.
    – Cort Ammon
    Dec 7, 2016 at 20:28
  • I am fully aware of the essence although I did not know the exact boundaries. Test case no 5 shows your point with double(A)==double(A+1). I really like to know the boundaries of my approach and in which range it will somehow work reliable with which drawbacks to expect. I already took the advice to normalize any Number given to BigDecimal.
    – oopexpert
    Dec 7, 2016 at 21:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.