001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2021, 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 * @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(!Double.isFinite(doubleValue)) {
431                throw unsupportedNumberValue(doubleValue);
432            }
433            if(doubleValue % 1 == 0) {
434                // double represents an integer
435                return narrow(BigDecimal.valueOf(doubleValue));
436            }
437            return number;
438        }
439        
440        if(isIntegerOnly(number)) {
441            
442            // number is one of {BigInteger, Long}
443            
444            final int total_bits_required = bitLengthOfInteger(number);
445            
446            // check whether we have enough bits to store the result into an int
447            if(total_bits_required<31) { 
448                return number.intValue();
449            }
450            
451            // check whether we have enough bits to store the result into a long
452            if(total_bits_required<63) { 
453                return number.longValue();
454            }
455            
456            return number; // cannot narrow down
457            
458        }
459
460        if(number instanceof BigDecimal) {
461            
462            final BigDecimal decimal = ((BigDecimal) number);
463            try {
464                BigInteger integer = decimal.toBigIntegerExact(); 
465                return narrow(integer);
466            } catch (ArithmeticException e) {
467                return number; // cannot narrow to integer
468            }
469        }
470        
471        if(number instanceof RationalNumber) {
472            
473            final RationalNumber rational = ((RationalNumber) number);
474            
475            return rational.isInteger() 
476                    ? narrow(rational.getDividend()) // divisor is ONE
477                            : number; // cannot narrow to integer;
478        }
479
480        // for any other number type just do nothing
481        return number;
482    }
483    
484    @Override
485    public int compare(Number x, Number y) {
486        
487        final NumberType type_x = NumberType.valueOf(x);
488        final NumberType type_y = NumberType.valueOf(y);
489        
490        final boolean reorder_args = type_y.ordinal()>type_x.ordinal();
491        
492        return reorder_args
493                ? -compareWideVsNarrow(type_y, y, type_x, x)
494                        : compareWideVsNarrow(type_x, x, type_y, y);
495    }
496    
497    @Override
498    public boolean isZero(Number number) {
499        NumberType numberType = NumberType.valueOf(number);
500        return compare(numberType.zero, number) == 0;
501    }
502
503    @Override
504    public boolean isOne(Number number) {
505        NumberType numberType = NumberType.valueOf(number);
506        return compare(numberType.one, number) == 0;
507    }
508    
509    @Override
510    public boolean isLessThanOne(Number number) {
511        NumberType numberType = NumberType.valueOf(number);
512        return compare(numberType.one, number) > 0;
513    }
514     
515    @Override
516    public boolean isInteger(Number number) {
517        NumberType numberType = NumberType.valueOf(number);
518        return isInteger(numberType, number);
519    }
520    
521    
522    // -- HELPER
523    
524    private IllegalArgumentException unsupportedNumberValue(Number number) {
525        final String msg = String.format("Unsupported number value '%s' of type '%s' in number system '%s'",
526                "" + number,
527                number.getClass(),
528                this.getClass().getName());
529        
530        return new IllegalArgumentException(msg);
531    }
532    
533    private IllegalArgumentException unsupportedNumberType(Number number) {
534        final String msg = String.format("Unsupported number type '%s' in number system '%s'",
535                number.getClass().getName(),
536                this.getClass().getName());
537        
538        return new IllegalArgumentException(msg);
539    }
540    
541    private IllegalStateException unexpectedCodeReach() {
542        final String msg = String.format("Implementation Error: Code was reached that is expected unreachable");
543        return new IllegalStateException(msg);
544    }
545    
546    private boolean isIntegerOnly(Number number) {
547        return NumberType.valueOf(number).isIntegerOnly();
548    }
549    
550    private boolean isInteger(NumberType numberType, Number number) {
551        if(numberType.isIntegerOnly()) {
552            return true; // numberType only allows integer
553        }
554        if(number instanceof RationalNumber) {
555            return ((RationalNumber)number).isInteger();
556        }
557        
558        // remaining types to check: Double, Float, BigDecimal ...
559        
560        if(number instanceof BigDecimal) {
561            final BigDecimal decimal = (BigDecimal)number; 
562            // see https://stackoverflow.com/questions/1078953/check-if-bigdecimal-is-integer-value
563            if(decimal.scale()<=0) {
564                return true;
565            }
566            try {
567                decimal.toBigIntegerExact();
568                return true;
569            } catch (ArithmeticException ex) {
570                return false;
571            }
572        }
573        if(number instanceof Double || number instanceof Float) {
574            double doubleValue = number.doubleValue();
575            // see https://stackoverflow.com/questions/15963895/how-to-check-if-a-double-value-has-no-decimal-part
576            return doubleValue % 1 == 0; 
577        }
578        throw unsupportedNumberType(number);
579    }
580    
581    private int bitLengthOfInteger(Number number) {
582        if(number instanceof BigInteger) {
583            return ((BigInteger) number).bitLength();
584        }
585        long long_value = number.longValue(); 
586        
587        if(long_value == Long.MIN_VALUE) {
588            return 63;
589        } else {
590            int leadingZeros = Long.numberOfLeadingZeros(Math.abs(long_value));
591            return 64-leadingZeros;
592        }
593    }
594    
595    private BigInteger integerToBigInteger(Number number) {
596        if(number instanceof BigInteger) {
597            return (BigInteger) number;
598        }
599        return BigInteger.valueOf(number.longValue());
600    }
601    
602    private BigDecimal toBigDecimal(Number number) {
603        if(number instanceof BigDecimal) {
604            return (BigDecimal) number;
605        }
606        if(number instanceof BigInteger) {
607            return new BigDecimal((BigInteger) number);
608        }
609        if(number instanceof Long || 
610                number instanceof AtomicLong ||
611                number instanceof Integer || 
612                number instanceof AtomicInteger ||
613                number instanceof Short || 
614                number instanceof Byte) {
615            return BigDecimal.valueOf(number.longValue());
616        }
617        if(number instanceof Double || number instanceof Float) {
618            return BigDecimal.valueOf(number.doubleValue());
619        }
620        if(number instanceof RationalNumber) {
621            throw unexpectedCodeReach();
622            //Note: don't do that (potential precision loss)
623            //return ((RationalNumber) number).bigDecimalValue(); 
624        }
625        throw unsupportedNumberType(number);
626    }
627
628    private Number addWideAndNarrow(
629            NumberType wideType, Number wide, 
630            NumberType narrowType, Number narrow) {
631        
632        if(wideType.isIntegerOnly()) {
633            // at this point we know, that narrow must also be an integer-only type
634            if(wide instanceof BigInteger) {
635                return ((BigInteger) wide).add(integerToBigInteger(narrow));
636            }
637            
638            // at this point we know, that 'wide' and 'narrow' are one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
639            
640            // +1 carry, not including sign
641            int total_bits_required = Math.max(bitLengthOfInteger(wide), bitLengthOfInteger(narrow)) + 1; 
642            
643            // check whether we have enough bits to store the result into a long
644            if(total_bits_required<63) { 
645                return wide.longValue() + narrow.longValue();
646            }
647            
648            return integerToBigInteger(wide).add(integerToBigInteger(narrow));
649        }
650        
651        if(wide instanceof RationalNumber) {
652            
653            // at this point we know, that narrow must either be rational or an integer-only type
654            if(narrow instanceof RationalNumber) {
655                return ((RationalNumber) wide).add((RationalNumber) narrow);
656            }
657            
658            return ((RationalNumber) wide).add(
659                    RationalNumber.ofInteger(integerToBigInteger(narrow)));
660        }
661        
662        // at this point we know, that wide is one of {BigDecimal, Double, Float}
663        
664        if(wide instanceof BigDecimal) {
665            
666            if(narrow instanceof BigDecimal) {
667                return ((BigDecimal) wide).add((BigDecimal) narrow, Calculus.MATH_CONTEXT);
668            }
669            
670            if(narrow instanceof Double || narrow instanceof Float) {
671                return ((BigDecimal) wide).add(BigDecimal.valueOf(narrow.doubleValue()), Calculus.MATH_CONTEXT);
672            }
673            
674            if(narrow instanceof RationalNumber) {
675                //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber 
676                return ((BigDecimal) wide).add(((RationalNumber) narrow).bigDecimalValue());
677            }
678            
679            // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
680            return ((BigDecimal) wide).add(BigDecimal.valueOf(narrow.longValue()));
681            
682        }
683        
684        // at this point we know, that wide is one of {Double, Float}
685        
686        if(narrow instanceof Double || narrow instanceof Float) {
687            //converting to BigDecimal, because especially fractional addition is sensitive to precision loss
688            return BigDecimal.valueOf(wide.doubleValue())
689                .add(BigDecimal.valueOf(narrow.doubleValue()));
690        }
691        
692        if(narrow instanceof RationalNumber) {
693            //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
694            return BigDecimal.valueOf(wide.doubleValue())
695                    .add(((RationalNumber) narrow).bigDecimalValue());
696        }
697        
698        if(narrow instanceof BigInteger) {
699            return BigDecimal.valueOf(wide.doubleValue())
700                    .add(new BigDecimal((BigInteger) narrow));
701        }
702        
703        // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
704        return BigDecimal.valueOf(wide.doubleValue())
705                .add(BigDecimal.valueOf(narrow.longValue()));
706        
707    }
708    
709    private Number multiplyWideAndNarrow(
710            NumberType wideType, Number wide, 
711            NumberType narrowType, Number narrow) {
712        
713        if(wideType.isIntegerOnly()) {
714            // at this point we know, that narrow must also be an integer-only type
715            if(wide instanceof BigInteger) {
716                return ((BigInteger) wide).multiply(integerToBigInteger(narrow));
717            }
718            
719            // at this point we know, that 'wide' and 'narrow' are one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
720            
721            int total_bits_required = bitLengthOfInteger(wide) + bitLengthOfInteger(narrow); // not including sign
722            
723            // check whether we have enough bits to store the result into a long
724            if(total_bits_required<63) { 
725                return wide.longValue() * narrow.longValue();
726            }
727            
728            return integerToBigInteger(wide).multiply(integerToBigInteger(narrow));
729        }
730        
731        if(wide instanceof RationalNumber) {
732            
733            // at this point we know, that narrow must either be rational or an integer-only type
734            if(narrow instanceof RationalNumber) {
735                return ((RationalNumber) wide).multiply((RationalNumber) narrow);
736            }
737            
738            return ((RationalNumber) wide).multiply(
739                    RationalNumber.ofInteger(integerToBigInteger(narrow)));
740        }
741        
742        // at this point we know, that wide is one of {BigDecimal, Double, Float}
743        
744        if(wide instanceof BigDecimal) {
745            
746            if(narrow instanceof BigDecimal) {
747                return ((BigDecimal) wide).multiply((BigDecimal) narrow, Calculus.MATH_CONTEXT);
748            }
749            
750            if(narrow instanceof Double || narrow instanceof Float) {
751                return ((BigDecimal) wide).multiply(BigDecimal.valueOf(narrow.doubleValue()), Calculus.MATH_CONTEXT);
752            }
753            
754            if(narrow instanceof RationalNumber) {
755                //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber 
756                return ((BigDecimal) wide).multiply(((RationalNumber) narrow).bigDecimalValue());
757            }
758            
759            // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
760            return ((BigDecimal) wide).multiply(BigDecimal.valueOf(narrow.longValue()));
761            
762        }
763        
764        // at this point we know, that wide is one of {Double, Float}
765        
766        if(narrow instanceof Double || narrow instanceof Float) {
767            // not converting to BigDecimal, because fractional multiplication is not sensitive to precision loss
768            return wide.doubleValue() * narrow.doubleValue();
769        }
770        
771        if(narrow instanceof RationalNumber) {
772            //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
773            return BigDecimal.valueOf(wide.doubleValue())
774                    .multiply(((RationalNumber) narrow).bigDecimalValue());
775        }
776        
777        if(narrow instanceof BigInteger) {
778            return BigDecimal.valueOf(wide.doubleValue())
779                    .multiply(new BigDecimal((BigInteger) narrow));
780        }
781        
782        // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
783        return BigDecimal.valueOf(wide.doubleValue())
784                .multiply(BigDecimal.valueOf(narrow.longValue()));              
785     
786    }
787    
788    
789    private int compareWideVsNarrow(
790            NumberType wideType, Number wide, 
791            NumberType narrowType, Number narrow) {
792        
793        
794        if(wideType.isIntegerOnly()) {
795            // at this point we know, that narrow must also be an integer-only type
796            if(wide instanceof BigInteger) {
797                return ((BigInteger) wide).compareTo(integerToBigInteger(narrow));
798            }
799            
800            // at this point we know, that 'wide' and 'narrow' are one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
801            return Long.compare(wide.longValue(), narrow.longValue());
802        }
803        
804        if(wide instanceof RationalNumber) {
805            
806            // at this point we know, that narrow must either be rational or an integer-only type
807            if(narrow instanceof RationalNumber) {
808                return ((RationalNumber) wide).compareTo((RationalNumber) narrow);
809            }
810            
811            return ((RationalNumber) wide).compareTo(
812                    RationalNumber.ofInteger(integerToBigInteger(narrow)));
813        }
814        
815        // at this point we know, that wide is one of {BigDecimal, Double, Float}
816        
817        if(wide instanceof BigDecimal) {
818            
819            if(narrow instanceof BigDecimal) {
820                return ((BigDecimal) wide).compareTo((BigDecimal) narrow);
821            }
822            
823            if(narrow instanceof Double || narrow instanceof Float) {
824                return ((BigDecimal) wide).compareTo(BigDecimal.valueOf(narrow.doubleValue()));
825            }
826            
827            if(narrow instanceof RationalNumber) {
828                //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
829                return ((BigDecimal) wide).compareTo(((RationalNumber) narrow).bigDecimalValue());
830            }
831            
832            if (narrow instanceof BigInteger) {
833                //TODO for optimization, can this be done without instantiating a new BigDecimal?
834                return ((BigDecimal) wide).compareTo(new BigDecimal((BigInteger) narrow));
835            }
836            
837            // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
838            return ((BigDecimal) wide).compareTo(BigDecimal.valueOf(narrow.longValue()));
839            
840        }
841        
842        // at this point we know, that wide is one of {Double, Float}
843        
844        if(narrow instanceof Double || narrow instanceof Float) {
845            return Double.compare(wide.doubleValue(), narrow.doubleValue());
846        }
847        
848        if(narrow instanceof RationalNumber) {
849            //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
850            return BigDecimal.valueOf(wide.doubleValue())
851                    .compareTo(((RationalNumber) narrow).bigDecimalValue());
852        }
853        
854        if(narrow instanceof BigInteger) {
855            return BigDecimal.valueOf(wide.doubleValue())
856                    .compareTo(new BigDecimal((BigInteger) narrow));
857        }
858        
859        // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
860        return BigDecimal.valueOf(wide.doubleValue())
861                .compareTo(BigDecimal.valueOf(narrow.longValue()));
862        
863    }
864
865    // only for non-zero sign
866    private static BigInteger copySignTo(int sign, BigInteger absNumber) {
867        if(sign==-1) {
868            return absNumber.negate();
869        }    
870        return absNumber;
871    }
872    
873    // only for non-zero sign
874    private static BigDecimal copySignTo(int sign, BigDecimal absNumber) {
875        if(sign==-1) {
876            return absNumber.negate();
877        }    
878        return absNumber;
879    }
880    
881    private static Number[] applyToArray(Number[] array, UnaryOperator<Number> operator) {
882        // only ever used for length=2
883        return new Number[] {
884                operator.apply(array[0]),
885                operator.apply(array[1])
886        };
887    }
888    
889
890}