2

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!

Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
  • 4
    You _cannot, cannot, cannot_ do this. _Give up_ on trying to generalize over different types of `Number`; it's really deeply not designed to be able to do anything like that. Everyone tries to do it, and everybody fails. Don't even try. The one and only thing you can do with a `Number` is convert it to a primitive number type, where possible, and even then you can't know if you're reducing precision. – Louis Wasserman Apr 03 '17 at 19:32
  • @LouisWasserman I'm only using `Number` because I want to be able to have `null` and primitives don't allow that. Obviously I need to convert to primitives (the compiler won't let me do it otherwise), the question was "what will be the simplest way to do that", and seems that using `doubleValue` is. – Nitzan Tomer Apr 03 '17 at 19:50
  • 1
    that won't necessarily produce valid results. `long` values will get rounded when converted to `double`, and that'll mess up `%` results. Why do you want nulls? Null numbers don't make sense. – Louis Wasserman Apr 03 '17 at 19:51
  • @LouisWasserman I won't check this if the number is `null`, but my class holds the number in a `Number` member because that member can be null. As for the `long` part, do you suggest to check if the value is `long` and if so to then use `longValue` instead? – Nitzan Tomer Apr 03 '17 at 19:57
  • 1
    I suggest to redesign your entire program so you stick to one single type to represent your numbers. – Louis Wasserman Apr 03 '17 at 19:58
  • @LouisWasserman No can do, it's based on json schema where there are two types of numbers: `integer` and `number` and that's what I have to work with. If it's an `integer` then there's no problem, but a `number` can be any number. – Nitzan Tomer Apr 03 '17 at 20:02
  • 2
    So? Convert them both to BigDecimal, and use that universally. – Louis Wasserman Apr 03 '17 at 20:03
  • @LouisWasserman Fair enough. Thanks! – Nitzan Tomer Apr 03 '17 at 20:12

3 Answers3

1

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...

nasukkin
  • 2,460
  • 1
  • 12
  • 19
1

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;
}
Darshan Mehta
  • 30,102
  • 11
  • 68
  • 102
1

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.

Community
  • 1
  • 1
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • Thanks for the profound answer. Using `BigDecimal` is probably an overkill for my purposes, as the numbers we'll be using will be only double and long. But it has been educational! – Nitzan Tomer Apr 03 '17 at 21:57