Nitzan Tomer
Nitzan Tomer

Reputation: 164177

How to use modulo with two instances of Number?

I came across a scenario in which I have two Number instances and I need to check if one is a multiple of the other.
I tried to use modulo to check that, for example:

public static boolean isMultipleOf(Number a, Number b) {
    return a % b == 0;
}

But the compiler doesn't like it:

Operator '%' cannot be applied to 'java.lang.Number', 'java.lang.Number'

I get why this is the case, but my question is what will be the simplest way of achieving this?
The actual values can be any type of number, and I'd like to avoid checking for each of them what is their actual number type and only then perform the operation.

Any ideas?
Thanks!

Upvotes: 2

Views: 104

Answers (3)

kennytm
kennytm

Reputation: 523344

No you can't do it without type checking

A Number only provides methods to convert to primitives, and each of the primitive is insufficient to give an accurate answer.

doubleValue does not work

static boolean wrongIsMultipleOfUsingDouble(Number a, Number b) {
    return (a.doubleValue() % b.doubleValue()) == 0;
}

Since a double only has 53 bits of precision, it will give wrong answer when the input is a long which needs 63 bits of precision:

System.out.println(wrongIsMultipleOfUsingDouble(6969696969696969696L, 3L));
// prints `false`, but should be `true`
System.out.println(wrongIsMultipleOfUsingDouble(7777777777777777777L, 2L));
// prints `true`, but should be `false`.

longValue does not work

static boolean wrongIsMultipleOfUsingLong(Number a, Number b) {
    return (a.longValue() % b.longValue()) == 0;
}

Obviously it does not work because of truncation.

System.out.println(wrongIsMultipleOfUsingLong(5.0, 2.5));
// prints `false`, but should be `true`     
System.out.println(wrongIsMultipleOfUsingLong(4.5, 2.0));
// prints `true`, but should be `false`.

Type-checking work only for known types.

Although OP liked to avoid type checking, this is really the only way to approach an acceptable solution.

static boolean wrongIsMultipleOfUsingTypeChecking(Number a, Number b) {
    // pseudo-code for simplicity
    if (a, b instanceof (AtomicInteger | AtomicLong | Byte | Integer | Long | ...)) {
        return (a.longValue() % b.longValue()) == 0;
    } else if (a, b instanceof (Double | DoubleAccumulator | DoubleAdder | Float) {
        return (a.doubleValue() % b.doubleValue()) == 0;
    } else if (a, b instanceof (BigInteger | BigDecimal)) {
        return a.remainder(b) == ZERO;
    } else {
        throw new RuntimeError("I give up");
    }
}

This is fine in most scenario, but again it still does not work because it can't handle third-party subclasses of Number, say, org.apache.commons.math4.fraction.Fraction?

Restricting to JSON numbers only?

Now OP stated that Number is used because the number comes from JSON. Those Number are typically only long or double so the type-checking approach is sufficient.

However, most popular libraries in Java also support interpreting numbers as BigDecimal:

A BigDecimal covers both the range of double and long, and has an actual .remainder() method that solves OP's problem. If we want to perform arithmetic using only a single class, and the price of BigDecimal is not considered a big problem, this can be a viable alternative.

Upvotes: 1

Darshan Mehta
Darshan Mehta

Reputation: 30819

You can use doubleValue method to convert the arguments to double and apply the %, e.g.:

private static boolean isMultipleOf(Number a, Number b){
    return (a.doubleValue() % b.doubleValue()) == 0.0;
}

It would work with int and float as well, e.g.:

public static void main(String[] args) throws Exception{
    System.out.println(isMultipleOf(20, 10));
    System.out.println(isMultipleOf(20.0, 10));
    System.out.println(isMultipleOf(20, 10.0));
    System.out.println(isMultipleOf(20.0, 10.0));
}

The above prints true 4 times.

Update

If you are dealing with huge numbers then you can use BigDecimal class' remainder method, e.g.:

private static boolean isMultipleOf(Number a, Number b){
    return new BigDecimal(a.doubleValue()).remainder(new BigDecimal(b.doubleValue())).doubleValue() == 0.0;
}

Upvotes: 1

nasukkin
nasukkin

Reputation: 2540

You could do something like this:

public static boolean isMultipleOf(Number a, Number b) {
    return a.longValue() % b.longValue() == 0;
}

Of course, this assumes that a and b are mathematically integers (so Short, Integer, or Long). You could use doubleValue() instead, but beware floating point algebra comparisons...

Upvotes: 1

Related Questions