001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2020, Units of Measurement project.
004 *
005 * All rights reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without modification,
008 * are permitted provided that the following conditions are met:
009 *
010 * 1. Redistributions of source code must retain the above copyright notice,
011 *    this list of conditions and the following disclaimer.
012 *
013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
014 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
015 *
016 * 3. Neither the name of JSR-385, Indriya nor the names of their contributors may be used to endorse or promote products
017 *    derived from this software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package tech.units.indriya.function;
031
032import java.math.BigDecimal;
033import java.math.BigInteger;
034import java.math.MathContext;
035import java.math.RoundingMode;
036import java.util.concurrent.atomic.AtomicInteger;
037import java.util.concurrent.atomic.AtomicLong;
038import java.util.function.UnaryOperator;
039
040import tech.units.indriya.spi.NumberSystem;
041
042/**
043 * {@link NumberSystem} implementation to support Java's built-in {@link Number}s and the
044 * {@link RationalNumber} type.   
045 * 
046 * @author Andi Huber
047 * @since 2.0
048 */
049public class DefaultNumberSystem implements NumberSystem {
050    
051    /**
052     *  In order of increasing number type 'widening'.
053     */
054    private enum NumberType {
055        
056        // integer types
057        BYTE_BOXED(true, Byte.class, (byte)1, (byte)0),
058        SHORT_BOXED(true, Short.class, (short)1, (short)0),
059        INTEGER_BOXED(true, Integer.class, 1, 0),
060        INTEGER_ATOMIC(true, AtomicInteger.class, 1, 0),
061        LONG_BOXED(true, Long.class, 1L, 0L),
062        LONG_ATOMIC(true, AtomicLong.class, 1L, 0),
063        BIG_INTEGER(true, BigInteger.class, BigInteger.ONE, BigInteger.ZERO),
064        
065        // rational types
066        RATIONAL(false, RationalNumber.class, RationalNumber.ONE, RationalNumber.ZERO),
067        
068        // fractional types
069        FLOAT_BOXED(false, Float.class, 1.f, 0.f),
070        DOUBLE_BOXED(false, Double.class, 1.d, 0.d),
071        BIG_DECIMAL(false, BigDecimal.class, BigDecimal.ONE, BigDecimal.ZERO),
072        
073        ;
074        private final boolean integerOnly;
075        private final Class<? extends Number> type;
076        private final Number one;
077        private final Number zero;
078        
079        private NumberType(boolean integerOnly, Class<? extends Number> type, 
080                Number one, Number zero) {
081            
082            this.integerOnly = integerOnly;
083            this.type = type;
084            this.one = one;
085            this.zero = zero;
086        }
087
088        public boolean isIntegerOnly() {
089            return integerOnly;
090        }
091        
092        @SuppressWarnings("unused")
093        public Class<? extends Number> getType() {
094            return type;
095        }
096
097        // 'hardcoded' for performance reasons
098        static NumberType valueOf(Number number) {
099            if(number instanceof Long) {
100                return LONG_BOXED; 
101            }
102            if(number instanceof AtomicLong) {
103                return LONG_ATOMIC; 
104            }
105            if(number instanceof Integer) {
106                return INTEGER_BOXED;
107            }
108            if(number instanceof AtomicInteger) {
109                return INTEGER_ATOMIC;
110            }
111            if(number instanceof Double) {
112                return DOUBLE_BOXED;
113            }
114            if(number instanceof Short) {
115                return SHORT_BOXED;
116            }
117            if(number instanceof Byte) {
118                return BYTE_BOXED;
119            }
120            if(number instanceof Float) {
121                return FLOAT_BOXED;
122            }
123            if(number instanceof BigDecimal) {
124                return BIG_DECIMAL;
125            }
126            if(number instanceof BigInteger) {
127                return BIG_INTEGER;
128            }
129            if(number instanceof RationalNumber) {
130                return RATIONAL;
131            }
132            final String msg = String.format("Unsupported number type '%s'",
133                    number.getClass().getName());
134            throw new IllegalArgumentException(msg);
135        }
136        
137    }
138
139    @Override
140    public Number add(Number x, Number y) {
141        
142        final NumberType type_x = NumberType.valueOf(x);
143        final NumberType type_y = NumberType.valueOf(y);
144        
145        final boolean reorder_args = type_y.ordinal()>type_x.ordinal();
146        
147        return reorder_args
148                ? addWideAndNarrow(type_y, y, type_x, x)
149                        : addWideAndNarrow(type_x, x, type_y, y);
150    }
151
152    @Override
153    public Number subtract(Number x, Number y) {
154        return add(x, negate(y));
155    }
156
157    @Override
158    public Number multiply(Number x, Number y) {
159        
160        final NumberType type_x = NumberType.valueOf(x);
161        final NumberType type_y = NumberType.valueOf(y);
162        
163        final boolean reorder_args = type_y.ordinal()>type_x.ordinal();
164        
165        return reorder_args
166                ? multiplyWideAndNarrow(type_y, y, type_x, x)
167                        : multiplyWideAndNarrow(type_x, x, type_y, y);
168    }
169
170    @Override
171    public Number divide(Number x, Number y) {
172        return multiply(x, reciprocal(y));
173    }
174    
175    @Override
176    public Number[] divideAndRemainder(Number x, Number y, boolean roundRemainderTowardsZero) {
177        
178        final int sign_x = signum(x);
179        final int sign_y = signum(y);
180        
181        final int sign = sign_x * sign_y;
182        // handle corner cases when x or y are zero
183        if(sign == 0) {
184            if(sign_y == 0) {
185                throw new ArithmeticException("division by zero");
186            }
187            if(sign_x==0) {
188                return new Number[] {0, 0};
189            }
190        }
191        
192        final Number absX = abs(x);
193        final Number absY = abs(y);
194        
195        final NumberType type_x = NumberType.valueOf(absX);
196        final NumberType type_y = NumberType.valueOf(absY);
197        
198        // if x and y are both integer types than we can calculate integer results,
199        // otherwise we resort to BigDecimal
200        final boolean yieldIntegerResult = type_x.isIntegerOnly() && type_y.isIntegerOnly();
201        
202        if(yieldIntegerResult) {
203                            
204            final BigInteger integer_x = integerToBigInteger(absX);
205            final BigInteger integer_y = integerToBigInteger(absY);
206            
207            final BigInteger[] divAndRemainder = integer_x.divideAndRemainder(integer_y);
208            
209            return applyToArray(divAndRemainder, number->copySignTo(sign, (BigInteger)number));
210            
211        } else {
212            
213            final MathContext mathContext = 
214                    new MathContext(Calculus.MATH_CONTEXT.getPrecision(), RoundingMode.FLOOR);
215            
216            final BigDecimal decimal_x = (type_x == NumberType.RATIONAL)
217                    ? ((RationalNumber) absX).bigDecimalValue()
218                            : toBigDecimal(absX);
219            final BigDecimal decimal_y = (type_y == NumberType.RATIONAL)
220                    ? ((RationalNumber) absY).bigDecimalValue()
221                            : toBigDecimal(absY);
222            
223            final BigDecimal[] divAndRemainder = decimal_x.divideAndRemainder(decimal_y, mathContext);
224            
225            if(roundRemainderTowardsZero) {
226                return new Number[] {
227                        copySignTo(sign, divAndRemainder[0]), 
228                        copySignTo(sign, divAndRemainder[1].toBigInteger())};
229                
230            } else {
231                return applyToArray(divAndRemainder, number->copySignTo(sign, (BigDecimal)number));
232            }
233            
234        }
235
236    }
237
238    @Override
239    public Number reciprocal(Number number) {
240        if(isIntegerOnly(number)) {
241            return RationalNumber.of(BigInteger.ONE, integerToBigInteger(number));
242        }
243        if(number instanceof BigDecimal) {
244            return RationalNumber.of((BigDecimal) number).reciprocal();
245        }
246        if(number instanceof RationalNumber) {
247            return ((RationalNumber) number).reciprocal();
248        }
249        if(number instanceof Double) {
250            return RationalNumber.of((double)number).reciprocal();
251        }
252        if(number instanceof Float) {
253            return RationalNumber.of(number.doubleValue()).reciprocal();
254        }
255        throw unsupportedNumberType(number);
256    }
257
258    @Override
259    public int signum(Number number) {
260        if(number instanceof BigInteger) {
261            return ((BigInteger) number).signum();
262        }
263        if(number instanceof BigDecimal) {
264            return ((BigDecimal) number).signum();
265        }
266        if(number instanceof RationalNumber) {
267            return ((RationalNumber) number).signum();
268        }
269        if(number instanceof Double) {
270            return (int)Math.signum((double)number);
271        }
272        if(number instanceof Float) {
273            return (int)Math.signum((float)number);
274        }
275        if(number instanceof Long || number instanceof AtomicLong) {
276            final long longValue = number.longValue();
277            return Long.signum(longValue);
278        }
279        if(number instanceof Integer || number instanceof AtomicInteger ||
280                number instanceof Short || number instanceof Byte) {
281            final int intValue = number.intValue();
282            return Integer.signum(intValue);
283        }
284        throw unsupportedNumberType(number);    
285    }
286    
287    @Override
288    public Number abs(Number number) {
289        if(number instanceof BigInteger) {
290            return ((BigInteger) number).abs();
291        }
292        if(number instanceof BigDecimal) {
293            return ((BigDecimal) number).abs();
294        }
295        if(number instanceof RationalNumber) {
296            return ((RationalNumber) number).abs();
297        }
298        if(number instanceof Double) {
299            return Math.abs((double)number);
300        }
301        if(number instanceof Float) {
302            return Math.abs((float)number);
303        }
304        if(number instanceof Long || number instanceof AtomicLong) {
305            final long longValue = number.longValue();
306            if(longValue == Long.MIN_VALUE) {
307                return BigInteger.valueOf(longValue).abs(); // widen to BigInteger
308            }
309            return Math.abs(longValue);
310        }
311        if(number instanceof Integer || number instanceof AtomicInteger) {
312            final int intValue = number.intValue();
313            if(intValue == Integer.MIN_VALUE) {
314                return Math.abs(number.longValue()); // widen to long
315            }
316            return Math.abs(intValue);
317        }
318        if(number instanceof Short || number instanceof Byte) {
319            Math.abs(number.intValue()); // widen to int
320        }
321        throw unsupportedNumberType(number);    
322    }
323    
324    @Override
325    public Number negate(Number number) {
326        if(number instanceof BigInteger) {
327            return ((BigInteger) number).negate();
328        }
329        if(number instanceof BigDecimal) {
330            return ((BigDecimal) number).negate();
331        }
332        if(number instanceof RationalNumber) {
333            return ((RationalNumber) number).negate();
334        }
335        if(number instanceof Double) {
336            return -((double)number);
337        }
338        if(number instanceof Float) {
339            return -((float)number);
340        }
341        if(number instanceof Long || number instanceof AtomicLong) {
342            final long longValue = number.longValue();
343            if(longValue == Long.MIN_VALUE) {
344                return BigInteger.valueOf(longValue).negate(); // widen to BigInteger
345            }
346            return -longValue;
347        }
348        if(number instanceof Integer || number instanceof AtomicInteger) {
349            final int intValue = number.intValue();
350            if(intValue == Integer.MIN_VALUE) {
351                return -number.longValue(); // widen to long
352            }
353            return -intValue;
354        }
355        if(number instanceof Short) {
356            final short shortValue = (short)number;
357            if(shortValue == Short.MIN_VALUE) {
358                return -number.intValue(); // widen to int
359            }
360            return -shortValue;
361        }
362        if(number instanceof Byte) {
363            final short byteValue = (byte)number;
364            if(byteValue == Byte.MIN_VALUE) {
365                return -number.intValue(); // widen to int
366            }
367            return -byteValue;
368        }
369        throw unsupportedNumberType(number);
370    }
371    
372    @Override
373    public Number power(Number number, int exponent) {
374        if(exponent==0) {
375            if(isZero(number)) {
376                throw new ArithmeticException("0^0 is not defined");
377            }
378            return 1; // x^0 == 1, for any x!=0
379        }
380        if(exponent==1) {
381            return number; // x^1 == x, for any x
382        }
383        if(number instanceof BigInteger ||
384                number instanceof Long || number instanceof AtomicLong ||
385                number instanceof Integer || number instanceof AtomicInteger ||
386                number instanceof Short || number instanceof Byte) {
387            final BigInteger bigInt = integerToBigInteger(number);
388            if(exponent>0) {
389                return bigInt.pow(exponent);    
390            }
391            return RationalNumber.ofInteger(bigInt).pow(exponent);
392            
393        }
394        if(number instanceof BigDecimal) {
395            return ((BigDecimal) number).pow(exponent, Calculus.MATH_CONTEXT);
396        }
397        if(number instanceof RationalNumber) {
398            ((RationalNumber) number).pow(exponent);
399        }
400        if(number instanceof Double || number instanceof Float) {
401            return toBigDecimal(number).pow(exponent, Calculus.MATH_CONTEXT);
402        }
403        throw unsupportedNumberType(number);
404    }
405    
406    @Override
407    public Number exp(Number number) {
408        //TODO[220] this is a poor implementation, certainly we can do better using BigDecimal 
409        return Math.exp(number.doubleValue());
410    }
411    
412    @Override
413    public Number log(Number number) {
414        //TODO[220] this is a poor implementation, certainly we can do better using BigDecimal
415        return Math.log(number.doubleValue());
416    }
417    
418    @Override
419    public Number narrow(Number number) {
420        
421        //Implementation Note: for performance we stop narrowing down at 'double' or 'integer' level
422        
423        if(number instanceof Integer || number instanceof AtomicInteger ||
424                number instanceof Short || number instanceof Byte) {
425            return number;
426        }
427        
428        if(number instanceof Double || number instanceof Float) {
429            final double doubleValue = number.doubleValue();
430            if(doubleValue % 1 == 0) {
431                // double represents an integer
432                return narrow(BigDecimal.valueOf(doubleValue));
433            }
434            return number;
435        }
436        
437        if(isIntegerOnly(number)) {
438            
439            // number is one of {BigInteger, Long}
440            
441            final int total_bits_required = bitLengthOfInteger(number);
442            
443            // check whether we have enough bits to store the result into an int
444            if(total_bits_required<31) { 
445                return number.intValue();
446            }
447            
448            // check whether we have enough bits to store the result into a long
449            if(total_bits_required<63) { 
450                return number.longValue();
451            }
452            
453            return number; // cannot narrow down
454            
455        }
456
457        if(number instanceof BigDecimal) {
458            
459            final BigDecimal decimal = ((BigDecimal) number);
460            try {
461                BigInteger integer = decimal.toBigIntegerExact(); 
462                return narrow(integer);
463            } catch (ArithmeticException e) {
464                return number; // cannot narrow to integer
465            }
466        }
467        
468        if(number instanceof RationalNumber) {
469            
470            final RationalNumber rational = ((RationalNumber) number);
471            
472            return rational.isInteger() 
473                    ? narrow(rational.getDividend()) // divisor is ONE
474                            : number; // cannot narrow to integer;
475        }
476
477        // for any other number type just do nothing
478        return number;
479    }
480    
481    @Override
482    public int compare(Number x, Number y) {
483        
484        final NumberType type_x = NumberType.valueOf(x);
485        final NumberType type_y = NumberType.valueOf(y);
486        
487        final boolean reorder_args = type_y.ordinal()>type_x.ordinal();
488        
489        return reorder_args
490                ? -compareWideVsNarrow(type_y, y, type_x, x)
491                        : compareWideVsNarrow(type_x, x, type_y, y);
492    }
493    
494    @Override
495    public boolean isZero(Number number) {
496        NumberType numberType = NumberType.valueOf(number);
497        return compare(numberType.zero, number) == 0;
498    }
499
500    @Override
501    public boolean isOne(Number number) {
502        NumberType numberType = NumberType.valueOf(number);
503        return compare(numberType.one, number) == 0;
504    }
505    
506    @Override
507    public boolean isLessThanOne(Number number) {
508        NumberType numberType = NumberType.valueOf(number);
509        return compare(numberType.one, number) < 0;
510    }
511     
512    @Override
513    public boolean isInteger(Number number) {
514        NumberType numberType = NumberType.valueOf(number);
515        return isInteger(numberType, number);
516    }
517    
518    
519    // -- HELPER
520    
521    private IllegalArgumentException unsupportedNumberType(Number number) {
522        final String msg = String.format("Unsupported number type '%s' in number system '%s'",
523                number.getClass().getName(),
524                this.getClass().getName());
525        
526        return new IllegalArgumentException(msg);
527    }
528    
529    private IllegalStateException unexpectedCodeReach() {
530        final String msg = String.format("Implementation Error: Code was reached that is expected unreachable");
531        return new IllegalStateException(msg);
532    }
533    
534    private boolean isIntegerOnly(Number number) {
535        return NumberType.valueOf(number).isIntegerOnly();
536    }
537    
538    private boolean isInteger(NumberType numberType, Number number) {
539        if(numberType.isIntegerOnly()) {
540            return true; // numberType only allows integer
541        }
542        if(number instanceof RationalNumber) {
543            return ((RationalNumber)number).isInteger();
544        }
545        
546        // remaining types to check: Double, Float, BigDecimal ...
547        
548        if(number instanceof BigDecimal) {
549            final BigDecimal decimal = (BigDecimal)number; 
550            // see https://stackoverflow.com/questions/1078953/check-if-bigdecimal-is-integer-value
551            if(decimal.scale()<=0) {
552                return true;
553            }
554            try {
555                decimal.toBigIntegerExact();
556                return true;
557            } catch (ArithmeticException ex) {
558                return false;
559            }
560        }
561        if(number instanceof Double || number instanceof Float) {
562            double doubleValue = number.doubleValue();
563            // see https://stackoverflow.com/questions/15963895/how-to-check-if-a-double-value-has-no-decimal-part
564            return doubleValue % 1 == 0; 
565        }
566        throw unsupportedNumberType(number);
567    }
568    
569    private int bitLengthOfInteger(Number number) {
570        if(number instanceof BigInteger) {
571            return ((BigInteger) number).bitLength();
572        }
573        long long_value = number.longValue(); 
574        
575        if(long_value == Long.MIN_VALUE) {
576            return 63;
577        } else {
578            int leadingZeros = Long.numberOfLeadingZeros(Math.abs(long_value));
579            return 64-leadingZeros;
580        }
581    }
582    
583    private BigInteger integerToBigInteger(Number number) {
584        if(number instanceof BigInteger) {
585            return (BigInteger) number;
586        }
587        return BigInteger.valueOf(number.longValue());
588    }
589    
590    private BigDecimal toBigDecimal(Number number) {
591        if(number instanceof BigDecimal) {
592            return (BigDecimal) number;
593        }
594        if(number instanceof BigInteger) {
595            return new BigDecimal((BigInteger) number);
596        }
597        if(number instanceof Long || 
598                number instanceof AtomicLong ||
599                number instanceof Integer || 
600                number instanceof AtomicInteger ||
601                number instanceof Short || 
602                number instanceof Byte) {
603            return BigDecimal.valueOf(number.longValue());
604        }
605        if(number instanceof Double || number instanceof Float) {
606            return BigDecimal.valueOf(number.doubleValue());
607        }
608        if(number instanceof RationalNumber) {
609            throw unexpectedCodeReach();
610            //Note: don't do that (potential precision loss)
611            //return ((RationalNumber) number).bigDecimalValue(); 
612        }
613        throw unsupportedNumberType(number);
614    }
615
616    private Number addWideAndNarrow(
617            NumberType wideType, Number wide, 
618            NumberType narrowType, Number narrow) {
619        
620        if(wideType.isIntegerOnly()) {
621            // at this point we know, that narrow must also be an integer-only type
622            if(wide instanceof BigInteger) {
623                return ((BigInteger) wide).add(integerToBigInteger(narrow));
624            }
625            
626            // at this point we know, that 'wide' and 'narrow' are one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
627            
628            // +1 carry, not including sign
629            int total_bits_required = Math.max(bitLengthOfInteger(wide), bitLengthOfInteger(narrow)) + 1; 
630            
631            // check whether we have enough bits to store the result into a long
632            if(total_bits_required<63) { 
633                return wide.longValue() + narrow.longValue();
634            }
635            
636            return integerToBigInteger(wide).add(integerToBigInteger(narrow));
637        }
638        
639        if(wide instanceof RationalNumber) {
640            
641            // at this point we know, that narrow must either be rational or an integer-only type
642            if(narrow instanceof RationalNumber) {
643                return ((RationalNumber) wide).add((RationalNumber) narrow);
644            }
645            
646            return ((RationalNumber) wide).add(
647                    RationalNumber.ofInteger(integerToBigInteger(narrow)));
648        }
649        
650        // at this point we know, that wide is one of {BigDecimal, Double, Float}
651        
652        if(wide instanceof BigDecimal) {
653            
654            if(narrow instanceof BigDecimal) {
655                return ((BigDecimal) wide).add((BigDecimal) narrow, Calculus.MATH_CONTEXT);
656            }
657            
658            if(narrow instanceof Double || narrow instanceof Float) {
659                return ((BigDecimal) wide).add(BigDecimal.valueOf(narrow.doubleValue()), Calculus.MATH_CONTEXT);
660            }
661            
662            if(narrow instanceof RationalNumber) {
663                //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber 
664                return ((BigDecimal) wide).add(((RationalNumber) narrow).bigDecimalValue());
665            }
666            
667            // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
668            return ((BigDecimal) wide).add(BigDecimal.valueOf(narrow.longValue()));
669            
670        }
671        
672        // at this point we know, that wide is one of {Double, Float}
673        
674        if(narrow instanceof Double || narrow instanceof Float) {
675            //converting to BigDecimal, because especially fractional addition is sensitive to precision loss
676            return BigDecimal.valueOf(wide.doubleValue())
677                .add(BigDecimal.valueOf(narrow.doubleValue()));
678        }
679        
680        if(narrow instanceof RationalNumber) {
681            //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
682            return BigDecimal.valueOf(wide.doubleValue())
683                    .add(((RationalNumber) narrow).bigDecimalValue());
684        }
685        
686        if(narrow instanceof BigInteger) {
687            return BigDecimal.valueOf(wide.doubleValue())
688                    .add(new BigDecimal((BigInteger) narrow));
689        }
690        
691        // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
692        return BigDecimal.valueOf(wide.doubleValue())
693                .add(BigDecimal.valueOf(narrow.longValue()));
694        
695    }
696    
697    private Number multiplyWideAndNarrow(
698            NumberType wideType, Number wide, 
699            NumberType narrowType, Number narrow) {
700        
701        if(wideType.isIntegerOnly()) {
702            // at this point we know, that narrow must also be an integer-only type
703            if(wide instanceof BigInteger) {
704                return ((BigInteger) wide).multiply(integerToBigInteger(narrow));
705            }
706            
707            // at this point we know, that 'wide' and 'narrow' are one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
708            
709            int total_bits_required = bitLengthOfInteger(wide) + bitLengthOfInteger(narrow); // not including sign
710            
711            // check whether we have enough bits to store the result into a long
712            if(total_bits_required<63) { 
713                return wide.longValue() * narrow.longValue();
714            }
715            
716            return integerToBigInteger(wide).multiply(integerToBigInteger(narrow));
717        }
718        
719        if(wide instanceof RationalNumber) {
720            
721            // at this point we know, that narrow must either be rational or an integer-only type
722            if(narrow instanceof RationalNumber) {
723                return ((RationalNumber) wide).multiply((RationalNumber) narrow);
724            }
725            
726            return ((RationalNumber) wide).multiply(
727                    RationalNumber.ofInteger(integerToBigInteger(narrow)));
728        }
729        
730        // at this point we know, that wide is one of {BigDecimal, Double, Float}
731        
732        if(wide instanceof BigDecimal) {
733            
734            if(narrow instanceof BigDecimal) {
735                return ((BigDecimal) wide).multiply((BigDecimal) narrow, Calculus.MATH_CONTEXT);
736            }
737            
738            if(narrow instanceof Double || narrow instanceof Float) {
739                return ((BigDecimal) wide).multiply(BigDecimal.valueOf(narrow.doubleValue()), Calculus.MATH_CONTEXT);
740            }
741            
742            if(narrow instanceof RationalNumber) {
743                //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber 
744                return ((BigDecimal) wide).multiply(((RationalNumber) narrow).bigDecimalValue());
745            }
746            
747            // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
748            return ((BigDecimal) wide).multiply(BigDecimal.valueOf(narrow.longValue()));
749            
750        }
751        
752        // at this point we know, that wide is one of {Double, Float}
753        
754        if(narrow instanceof Double || narrow instanceof Float) {
755            // not converting to BigDecimal, because fractional multiplication is not sensitive to precision loss
756            return wide.doubleValue() * narrow.doubleValue();
757        }
758        
759        if(narrow instanceof RationalNumber) {
760            //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
761            return BigDecimal.valueOf(wide.doubleValue())
762                    .multiply(((RationalNumber) narrow).bigDecimalValue());
763        }
764        
765        if(narrow instanceof BigInteger) {
766            return BigDecimal.valueOf(wide.doubleValue())
767                    .multiply(new BigDecimal((BigInteger) narrow));
768        }
769        
770        // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
771        return BigDecimal.valueOf(wide.doubleValue())
772                .multiply(BigDecimal.valueOf(narrow.longValue()));              
773     
774    }
775    
776    
777    private int compareWideVsNarrow(
778            NumberType wideType, Number wide, 
779            NumberType narrowType, Number narrow) {
780        
781        
782        if(wideType.isIntegerOnly()) {
783            // at this point we know, that narrow must also be an integer-only type
784            if(wide instanceof BigInteger) {
785                return ((BigInteger) wide).compareTo(integerToBigInteger(narrow));
786            }
787            
788            // at this point we know, that 'wide' and 'narrow' are one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
789            return Long.compare(wide.longValue(), narrow.longValue());
790        }
791        
792        if(wide instanceof RationalNumber) {
793            
794            // at this point we know, that narrow must either be rational or an integer-only type
795            if(narrow instanceof RationalNumber) {
796                return ((RationalNumber) wide).compareTo((RationalNumber) narrow);
797            }
798            
799            return ((RationalNumber) wide).compareTo(
800                    RationalNumber.ofInteger(integerToBigInteger(narrow)));
801        }
802        
803        // at this point we know, that wide is one of {BigDecimal, Double, Float}
804        
805        if(wide instanceof BigDecimal) {
806            
807            if(narrow instanceof BigDecimal) {
808                return ((BigDecimal) wide).compareTo((BigDecimal) narrow);
809            }
810            
811            if(narrow instanceof Double || narrow instanceof Float) {
812                return ((BigDecimal) wide).compareTo(BigDecimal.valueOf(narrow.doubleValue()));
813            }
814            
815            if(narrow instanceof RationalNumber) {
816                //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
817                return ((BigDecimal) wide).compareTo(((RationalNumber) narrow).bigDecimalValue());
818            }
819            
820            // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
821            return ((BigDecimal) wide).compareTo(BigDecimal.valueOf(narrow.longValue()));
822            
823        }
824        
825        // at this point we know, that wide is one of {Double, Float}
826        
827        if(narrow instanceof Double || narrow instanceof Float) {
828            return Double.compare(wide.doubleValue(), narrow.doubleValue());
829        }
830        
831        if(narrow instanceof RationalNumber) {
832            //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
833            return BigDecimal.valueOf(wide.doubleValue())
834                    .compareTo(((RationalNumber) narrow).bigDecimalValue());
835        }
836        
837        if(narrow instanceof BigInteger) {
838            return BigDecimal.valueOf(wide.doubleValue())
839                    .compareTo(new BigDecimal((BigInteger) narrow));
840        }
841        
842        // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
843        return BigDecimal.valueOf(wide.doubleValue())
844                .compareTo(BigDecimal.valueOf(narrow.longValue()));
845        
846    }
847
848    // only for non-zero sign
849    private static BigInteger copySignTo(int sign, BigInteger absNumber) {
850        if(sign==-1) {
851            return absNumber.negate();
852        }    
853        return absNumber;
854    }
855    
856    // only for non-zero sign
857    private static BigDecimal copySignTo(int sign, BigDecimal absNumber) {
858        if(sign==-1) {
859            return absNumber.negate();
860        }    
861        return absNumber;
862    }
863    
864    private static Number[] applyToArray(Number[] array, UnaryOperator<Number> operator) {
865        // only ever used for length=2
866        return new Number[] {
867                operator.apply(array[0]),
868                operator.apply(array[1])
869        };
870    }
871    
872
873}