-2

I want to sum a list of Number object and for each to use only the real value (if it's an Integer I want to use only .intValue() method and not .doubleValue e.x...) and I don't want to use instanceof.

The return value needs to be of Number type.

How can I do it with double dispatch or strategy pattern or something similar?

I can't extend each implementing class of Number and I can't sum two Number vars.

there are only 6 .xValue() methods in Number and I want to use each of them accordingly.

Roy Ash
  • 1,078
  • 1
  • 11
  • 28
  • 1
    What is the "real value" when you need to add up a List that contains a Double and a Long? – Thilo Jan 28 '19 at 12:29
  • sum(Short, Short) -> Short sum(Integer, Short) -> Integer sum(Integer, Double) -> Double . . . – Roy Ash Jan 28 '19 at 12:32
  • 3
    I suppose `new BigDecimal(number.toString())` works with all types. The result will be a `BigDecimal` (which is a `Number`). – Thilo Jan 28 '19 at 12:32
  • You are not supposed or required to use the right one depending on the exact type of the Number. That is kinda the point of an abstract class. Just always call the value which return a long. Then you can add the 2 long values you get, and create a new number with the result. – MTilsted Jan 28 '19 at 12:33
  • 2
    @MTilsted: `longValue()` will not work (very well) for `double`. – Thilo Jan 28 '19 at 12:34
  • I don't want to use BigDecimal because it will give me a double type result and i want to be flexible – Roy Ash Jan 28 '19 at 12:38
  • How can you avoid a "double type result" if you put in a double and want to preserve "the real value"? You could inspect the `BigDecimal` at the end to see if it fits into a `Long` or `Integer` and "down-convert" again. But it will be inconvenient for the caller of your method if the return type is so loose. – Thilo Jan 28 '19 at 12:40
  • That depend on what the original poster want. If you call longValue() you get the truncated long value(So the integer part of value only, which was what I though the author wanted). But to get the exact result just call doubleValue() instead of intValue() and then add the 2 doubles together and create a new Number with the result – MTilsted Jan 28 '19 at 12:40
  • @MTilsted: `doubleValue()` will not work very well for (large) `long`s. – Thilo Jan 28 '19 at 12:41
  • @MTilsted "create a new Number" - `Number` is abstract. – J-Alex Jan 28 '19 at 12:42
  • 5
    This is starting to look like an X-Y-problem. What do you really need to do? – Thilo Jan 28 '19 at 12:43
  • 3
    why do you even care what the actual return type is? If you return `Number`... well... `BigDecimal` is a `Number`... the rest is detail... what should the benefit be for the caller to get something specific which is then again just hidden behind a `Number`? – Roland Jan 28 '19 at 12:43
  • @Roland i was just thinking if it was possible and how to implement it. I didn't want to return only double like type and personally I think it's an interesting problem – Roy Ash Jan 28 '19 at 12:50
  • I'm using Number because i'm getting the list via varargs and want to be able to calculate all sort of Numbers. in addition if the user will give me only ints, I want to be able to give him back an int. More so, as @Thilo said 'doubleValue() will not work very well for (large) longs.' – Roy Ash Jan 28 '19 at 13:03
  • but then again: the caller wouldn't even know what is returned (so why even bother?)... if you sum up two `BigDecimal`s... what should the return type be? `Double`? that rather asks for trouble... please don't implement such an arbitrary sum... – Roland Jan 28 '19 at 13:22
  • I think it's worth more to align all the number types deliberately beforehand than to hope that a generic sum will deal with it correctly. Even more so if you do not want to use `instanceof` or `BigDecimal`, e.g. you may want to even use `Double.intValue()` to sum,... it all depends on the context and on your requirements. – Roland Jan 28 '19 at 13:25
  • @Roland but if I want to sum Long numbers, would it be accurate? – Roy Ash Jan 28 '19 at 13:35
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/187429/discussion-between-roland-and-roy-ash). – Roland Jan 28 '19 at 13:42

2 Answers2

5

Since the actually returned type is relevant to the caller and has not much use for the caller when it still is non-obvious due to a declared type of Number, it should be under the caller’s control and combined with a generic type signature, which allows the caller to actually use a particular return type. E.g.

public static <N extends Number, R extends Number> R sum(
        List<? extends N> input, Function<? super N, ? extends R> cast,
        BinaryOperator<R> addition) {

    return input.stream().<R>map(cast).reduce(addition).orElse(null);
}
public static <N extends Number> N sum(
        List<? extends N> input, BinaryOperator<N> addition) {

    return sum(input, Function.identity(), addition);
}

This allows to request the calculation to be within the input types, e.g.

List<Integer> list = Arrays.asList(1, 2, 3, 4);
Integer iSum1 = sum(list, Integer::sum);
Integer iSum2 = sum(list, Math::addExact);//throw on overflow

but also widening the type before summing up:

Long lSum = sum(list, Integer::longValue, Long::sum);

Likewise, you can handle Long or Double input types:

List<Long> list = Arrays.asList(1L, 2L, 3L, 4L);
Long lSum1 = sum(list, Long::sum);
Long lSum2 = sum(list, Math::addExact);//throw on overflow
// without precision loss:
BigInteger biSum = sum(list, BigInteger::valueOf, BigInteger::add);
List<Double> list = Arrays.asList(1.0, 2.0, 3.0, 4.0);
Double dSum = sum(list, Double::sum);
// without precision loss:
BigDecimal bdSum = sum(list, BigDecimal::valueOf, BigDecimal::add);

Or deal with mixed types:

List<Number> list = Arrays.asList(1, 2L, 3.0, 4F);
Double dSum = sum(list, Number::doubleValue, Double::sum);
BigDecimal bdSum = sum(list, n -> new BigDecimal(n.toString()), BigDecimal::add);

Note that Java’s Number type hierarchy does not reflect the type conversion rules of the primitive types. So while a mixture of int and long values could be handled as long whereas mixing int and double would require using double to prevent loss of precision, there is no difference between mixing Integer and Long vs. mixing Integer and Double, both are just mixtures of different Number subtypes. So in either case, you need a Number::xxxValue conversion in-between and regardless of the actual combination, any Number::xxxValue conversion would compile without a warning, even when it implies loss of precision.

Since large long values could lose precision when being converted to double, the last example use an intermediate String value, to ensure that, in the presence of long and double input value, all conversions to BigDecimal are lossless.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I think the OP wanted to rather have something like the following in place: doubleValue + doubleValue = Long, if the double values could be represented as long itself without precision loss. (maybe also longValue + longValue = int?) ... at least that is what I got from the chat when he wrote about `number.longValue() == number.doubleValue()` to decide whether a `longValue` should be returned... finally he wanted to apply an appropriate design pattern to solve that all :-) that's a nice solution nonetheless :-) – Roland Jan 29 '19 at 09:18
  • @Roland the repeated mentioning of `instanceof` was hinting into the other direction. But regardless of the originally intended idea, I tried to show a reasonable design pattern that is also used in practice. As said in the answer, it’s all about the caller. A result value based selection would only work with a declared return type of `Number`, so the caller would not even notice the changing type, which would cause problems without a benefit. Think of `Number n1 = 0.5 + 0.5, n2 = sum(0.5, 0.5);` where `n1.equals(n2)` would yield `false` as `1` (`Integer`) is not equal to `1.0` (`Double`). – Holger Jan 29 '19 at 09:33
  • that mentioning of `instanceof` was probably a mistake on my side... (I should have, as you did, rather state more clearly that it doesn't really make any difference if you just get a `Number`)... regarding your example... I get your point... it's not me who wants a `Number` that is the most common type (or the smallest possible type?) out of a sum ;-) I don't even know what I should do with such a number... except hoping that my memory consumption has decreased somewhere by ... probably nearly nothing :-) – Roland Jan 29 '19 at 09:40
  • @Roland the `instanceof` does already appear in the question’s first sentence, which clearly describes an input type based choice. Memory savings by using a different `Number` type may even be exactly nothing, due to the JVM’s object alignment. – Holger Jan 29 '19 at 10:21
  • 1
    Why do you know so much about Java & the JVM? or what were/are your sources? :-) I'm inspired by and admire your comments/answers – Roland Jan 29 '19 at 10:32
  • 3
    @Roland it helps doing Java software for more than twenty years now while keeping the curiosity and reading all the published papers about the topic. Even [Oracle’s Java documentation starting point](https://docs.oracle.com/en/java/javase/11/) does already lead to some interesting reads. Also Stackoverflow itself is a source, providing pointers to interesting topics, to investigate further. – Holger Jan 29 '19 at 10:48
  • 2
    @Holger you are just being very modest here... literally. I work with people that have more than 20 years of java and coding, no one reaches your level though. Hey, I do 10 years already, and reading some of your answers makes me feel like I have not even started. Overall, this place and users have great benefit (and luck!) from your presence... – Eugene Jan 29 '19 at 10:51
  • 1
    @Holger I have to agree with Eugene here; same applies here... but to be also honest I just started looking at the JLS when I first saw an answer from you (from then on regularly). Your style and conciceness inspired me. "reading all the published papers about the topic"...that's it then ;-) with all the tons of sources I will probably never catch up; not that I need to, but I would like ;-) the link you provided is indeed a great resource and out of curiosity I already jumped into a topic ;-) I am glad you are here and that you share your wisdom... thank you – Roland Jan 29 '19 at 11:59
1

Just to summarize what was also discussed in the comments/chat.

Disclaimer(s): do not use this code in production or just be sure to understand what it really does. By no means is this solution the way to go or best practice. Returning a Number whose type is adapted and a caller doesn't realize will give you troubles sooner or later. Please have a look at Holgers answer if you want to solve it in a caller-friendly way. This answer here just solves the issue of the OP the way he requested it. It hasn't any real benefit. It's basically here to just show what a bad idea it could be to solve it the way it was requested ;-). That having said, lets begin...

One way to define a strategy:

class Strategy {
    Predicate<Number> predicate;
    UnaryOperator<Number> transformation;
    Strategy(Predicate<Number> predicate, UnaryOperator<Number> transformation) {
        this.predicate = predicate;
        this.transformation = transformation;
    }
    boolean applies(Number number) {
        return predicate.test(number);
    }

    Number transformNumber(Number number) {
        return transformation.apply(number);
    }
}

A list of possible strategies could then look like

List<Strategy> strategies = Arrays.asList(
        new Strategy(n -> n.byteValue() == n.doubleValue(), Number::byteValue),
        new Strategy(n -> n.shortValue() == n.doubleValue(), Number::shortValue),
        new Strategy(n -> n.intValue() == n.doubleValue(), Number::intValue),
        new Strategy(n -> n.longValue() == n.doubleValue(), Number::longValue),                                                // please read the disclaimer again...  
        new Strategy(n -> n.floatValue() == n.doubleValue(), Number::floatValue),                                              // please spare your comments :-)
        new Strategy(n -> true, Number::doubleValue)                                                                           // ... lets continue!
);

A simple sum and the application of the strategies:

Optional<Number> sum(Number... numbers) {
    return Arrays.stream(numbers)
            .reduce(this::sumBasedOnStrategy);
}
Number sumBasedOnStrategy(Number one, Number two) {
    Number result = one.doubleValue() + two.doubleValue();
    return strategies.stream()
            .filter(s -> s.applies(result))
            .map(s -> s.transformNumber(result))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("No known strategy for the given number"));
}

Now testing the sum strategies:

Stream.of(1, 256, 66000, 3000000000L, 1.1f, 3.4f, Double.MAX_VALUE)
            .map(n -> sum(n, 1))
            .map(Optional::get)
            .map(Object::getClass)
            .forEach(System.out::println);

What would you expect?

class java.lang.Byte
class java.lang.Short
class java.lang.Integer
class java.lang.Long
class java.lang.Double // really?
class java.lang.Float
class java.lang.Double

And here are the corresponding sum results...

2
257
66001
3000000001
2.100000023841858 // thank you for your attention ;-)
4.4
1.7976931348623157E308

Note that there are also other constellations that lead to incorrect results. And again: what does it help to get a Integer after summing two double values? As Holger also showed in his comment (quoted):

A result value based selection would only work with a declared return type of Number, so the caller would not even notice the changing type, which would cause problems without a benefit. Think of Number n1 = 0.5 + 0.5, n2 = sum(0.5, 0.5); where n1.equals(n2) would yield false as 1 (Integer) is not equal to 1.0 (Double).

Roland
  • 22,259
  • 4
  • 57
  • 84
  • Thanks a lot! You and @Holger teached me so much! And 2 questions: 1. Why is `sum(1.1f, 1)` becomes `2.100000023841858`? 2. Why did you use `Optional`? – Roy Ash Jan 29 '19 at 19:28
  • I don’t care about memory consumption and such... all I wanted to do is implement “Scheme” like behavior. I wanted to maintain accuracy on one hand and let the code to decide which life of `Number` to return so `System.out.println()` would print it nicely – Roy Ash Jan 29 '19 at 19:44
  • 1
    @RoyAsh. Because `1.1f` already is `1.100000023841858`. Not all decimal fractions can be represented as binary fractions. – Thilo Jan 30 '19 at 08:05
  • @Thilo can you elaborate please? I know for example that it's not possible to represemt `1/3` as binary fraction, but what about `1/10`? why `(double).1f` equals `0.10000000149011612`? – Roy Ash Jan 30 '19 at 10:40
  • 1
    Both, `float` and `double` have their unique purpose and may not interact that good with each other... I think a good example that shows it really well is [this answer to "Convert float to double without losing precision" showing the binary representation of each](https://stackoverflow.com/a/9173262/6202869) (note that casting to `double` or calling `Float.doubleValue()` is basically the same) – Roland Jan 30 '19 at 11:51
  • 1
    regarding "accuracy": I would not rely on that using such an approach ;-) don't know exactly what you mean by "Scheme like", but if you want to be accurate, switch to `BigDecimal` and skip that `longValue() == doubleValue()`-comparison altogether. My answer was really just a demonstration on what pitfalls you can run into. And it showed only one of many. Overflow is just around the corner and again: there is no benefit for the caller!! It's even worse... many problems, not that easily understandable code, potentially unexpected behaviour (summing 2 doubles and getting an integer? ;-)) – Roland Jan 30 '19 at 12:07