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