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.