static Number addFallbackType()

in freemarker-core/src/main/java/freemarker/ext/beans/OverloadedNumberUtil.java [95:313]


    static Number addFallbackType(final Number num, final int typeFlags) {
        final Class numClass = num.getClass();
        if (numClass == BigDecimal.class) {
            // For now we only support the backward-compatible mode that doesn't prevent roll overs and magnitude loss.
            // However, we push the overloaded selection to the right direction, so we will at least indicate if the
            // number has decimals.
            BigDecimal n = (BigDecimal) num; 
            if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) != 0
                    && (typeFlags & TypeFlags.MASK_KNOWN_NONINTEGERS) != 0
                    && NumberUtil.isIntegerBigDecimal(n) /* <- can be expensive */) {
                return new IntegerBigDecimal(n);
            } else {
                // Either it was a non-integer, or it didn't mater what it was, as we don't have both integer and
                // non-integer target types. 
                return n;
            }
        } else if (numClass == Integer.class) {
            int pn = num.intValue();
            // Note that we try to return the most specific type (i.e., the numerical type with the smallest range), but
            // only among the types that are possible targets. Like if the only target is int and the value is 1, we
            // will return Integer 1, not Byte 1, even though byte is automatically converted to int so it would
            // work too. Why we avoid unnecessarily specific types is that they generate more overloaded method lookup
            // cache entries, since the cache key is the array of the types of the argument values. So we want as few
            // permutations as possible. 
            if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
                return new IntegerOrByte((Integer) num, (byte) pn);
            } else if ((typeFlags & TypeFlags.SHORT) != 0 && pn <= Short.MAX_VALUE && pn >= Short.MIN_VALUE) {
                return new IntegerOrShort((Integer) num, (short) pn);
            } else {
                return num;
            }
        } else if (numClass == Long.class) {
            final long pn = num.longValue(); 
            if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
                return new LongOrByte((Long) num, (byte) pn);
            } else if ((typeFlags & TypeFlags.SHORT) != 0 && pn <= Short.MAX_VALUE && pn >= Short.MIN_VALUE) {
                return new LongOrShort((Long) num, (short) pn);
            } else if ((typeFlags & TypeFlags.INTEGER) != 0 && pn <= Integer.MAX_VALUE && pn >= Integer.MIN_VALUE) {
                return new LongOrInteger((Long) num, (int) pn);
            } else {
                return num;
            }
        } else if (numClass == Double.class) {
            final double doubleN = num.doubleValue();
            
            // Can we store it in an integer type?
            checkIfWholeNumber: do {
                if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) == 0) break checkIfWholeNumber;
                
                // There's no hope to be 1-precise outside this region. (Although problems can occur even inside it...)
                if (doubleN > MAX_DOUBLE_OR_LONG || doubleN < MIN_DOUBLE_OR_LONG) break checkIfWholeNumber;
                
                long longN = num.longValue(); 
                double diff = doubleN - longN;
                boolean exact;  // We will try to ignore precision glitches (like 0.3 - 0.2 - 0.1 = -2.7E-17)
                if (diff == 0) {
                    exact = true;
                } else if (diff > 0) {
                    if (diff < LOWEST_ABOVE_ZERO) {
                        exact = false;
                    } else if (diff > HIGHEST_BELOW_ONE) {
                        exact = false;
                        longN++;
                    } else {
                        break checkIfWholeNumber;
                    }
                } else {  // => diff < 0
                    if (diff > -LOWEST_ABOVE_ZERO) {
                        exact = false;
                    } else if (diff < -HIGHEST_BELOW_ONE) {
                        exact = false;
                        longN--;
                    } else {
                        break checkIfWholeNumber;
                    }
                }
                
                // If we reach this, it can be treated as a whole number.
                
                if ((typeFlags & TypeFlags.BYTE) != 0
                        && longN <= Byte.MAX_VALUE && longN >= Byte.MIN_VALUE) {
                    return new DoubleOrByte((Double) num, (byte) longN);
                } else if ((typeFlags & TypeFlags.SHORT) != 0
                        && longN <= Short.MAX_VALUE && longN >= Short.MIN_VALUE) {
                    return new DoubleOrShort((Double) num, (short) longN);
                } else if ((typeFlags & TypeFlags.INTEGER) != 0
                        && longN <= Integer.MAX_VALUE && longN >= Integer.MIN_VALUE) {
                    final int intN = (int) longN; 
                    return (typeFlags & TypeFlags.FLOAT) != 0 && intN >= MIN_FLOAT_OR_INT && intN <= MAX_FLOAT_OR_INT
                                    ? new DoubleOrIntegerOrFloat((Double) num, intN)
                                    : new DoubleOrInteger((Double) num, intN);
                } else if ((typeFlags & TypeFlags.LONG) != 0) {
                    if (exact) {
                        return new DoubleOrLong((Double) num, longN);
                    } else {
                        // We don't deal with non-exact numbers outside the range of int, as we already reach
                        // ULP 2.384185791015625E-7 there.
                        if (longN >= Integer.MIN_VALUE && longN <= Integer.MAX_VALUE) {
                            return new DoubleOrLong((Double) num, longN);
                        } else {
                            break checkIfWholeNumber;
                        }
                    }
                }
                // This point is reached if the double value was out of the range of target integer type(s). 
                // Falls through!
            } while (false);
            // If we reach this that means that it can't be treated as a whole number.
            
            if ((typeFlags & TypeFlags.FLOAT) != 0 && doubleN >= -Float.MAX_VALUE && doubleN <= Float.MAX_VALUE) {
                return new DoubleOrFloat((Double) num);
            } else {
                // Simply Double:
                return num;
            }
        } else if (numClass == Float.class) {
            final float floatN = num.floatValue();
            
            // Can we store it in an integer type?
            checkIfWholeNumber: do {
                if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) == 0) break checkIfWholeNumber;
                
                // There's no hope to be 1-precise outside this region. (Although problems can occur even inside it...)
                if (floatN > MAX_FLOAT_OR_INT || floatN < MIN_FLOAT_OR_INT) break checkIfWholeNumber;
                
                int intN = num.intValue();
                double diff = floatN - intN;
                boolean exact;  // We will try to ignore precision glitches (like 0.3 - 0.2 - 0.1 = -2.7E-17)
                if (diff == 0) {
                    exact = true;
                // We already reach ULP 7.6293945E-6 with bytes, so we don't continue with shorts.
                } else if (intN >= Byte.MIN_VALUE && intN <= Byte.MAX_VALUE) {
                    if (diff > 0) {
                        if (diff < 0.00001) {
                            exact = false;
                        } else if (diff > 0.99999) {
                            exact = false;
                            intN++;
                        } else {
                            break checkIfWholeNumber;
                        }
                    } else {  // => diff < 0
                        if (diff > -0.00001) {
                            exact = false;
                        } else if (diff < -0.99999) {
                            exact = false;
                            intN--;
                        } else {
                            break checkIfWholeNumber;
                        }
                    }
                } else {
                    break checkIfWholeNumber;
                }
                
                // If we reach this, it can be treated as a whole number.
                
                if ((typeFlags & TypeFlags.BYTE) != 0 && intN <= Byte.MAX_VALUE && intN >= Byte.MIN_VALUE) {
                    return new FloatOrByte((Float) num, (byte) intN);
                } else if ((typeFlags & TypeFlags.SHORT) != 0 && intN <= Short.MAX_VALUE && intN >= Short.MIN_VALUE) {
                    return new FloatOrShort((Float) num, (short) intN);
                } else if ((typeFlags & TypeFlags.INTEGER) != 0) {
                    return new FloatOrInteger((Float) num, intN);
                } else if ((typeFlags & TypeFlags.LONG) != 0) {
                    // We can't even go outside the range of integers, so we don't need Long variation:
                    return exact
                            ? new FloatOrInteger((Float) num, intN)
                            : new FloatOrByte((Float) num, (byte) intN);  // as !exact implies (-128..127)
                }
                // This point is reached if the float value was out of the range of target integer type(s). 
                // Falls through!
            } while (false);
            // If we reach this that means that it can't be treated as a whole number. So it's simply a Float:
            return num;
        } else if (numClass == Byte.class) {
            return num;
        } else if (numClass == Short.class) {
            short pn = num.shortValue(); 
            if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
                return new ShortOrByte((Short) num, (byte) pn);
            } else {
                return num;
            }
        } else if (numClass == BigInteger.class) {
            if ((typeFlags
                    & ((TypeFlags.MASK_KNOWN_INTEGERS | TypeFlags.MASK_KNOWN_NONINTEGERS)
                            ^ (TypeFlags.BIG_INTEGER | TypeFlags.BIG_DECIMAL))) != 0) {
                BigInteger biNum = (BigInteger) num;
                final int bitLength = biNum.bitLength();  // Doesn't include sign bit, so it's one less than expected
                if ((typeFlags & TypeFlags.BYTE) != 0 && bitLength <= 7) {
                    return new BigIntegerOrByte(biNum);
                } else if ((typeFlags & TypeFlags.SHORT) != 0 && bitLength <= 15) {
                    return new BigIntegerOrShort(biNum);
                } else if ((typeFlags & TypeFlags.INTEGER) != 0 && bitLength <= 31) {
                    return new BigIntegerOrInteger(biNum);
                } else if ((typeFlags & TypeFlags.LONG) != 0 && bitLength <= 63) {
                    return new BigIntegerOrLong(biNum);
                } else if ((typeFlags & TypeFlags.FLOAT) != 0
                        && (bitLength <= MAX_FLOAT_OR_INT_LOG_2
                            || bitLength == MAX_FLOAT_OR_INT_LOG_2 + 1
                               && biNum.getLowestSetBit() >= MAX_FLOAT_OR_INT_LOG_2)) {
                    return new BigIntegerOrFloat(biNum);
                } else if ((typeFlags & TypeFlags.DOUBLE) != 0
                        && (bitLength <= MAX_DOUBLE_OR_LONG_LOG_2
                            || bitLength == MAX_DOUBLE_OR_LONG_LOG_2 + 1
                               && biNum.getLowestSetBit() >= MAX_DOUBLE_OR_LONG_LOG_2)) {
                    return new BigIntegerOrDouble(biNum);
                } else {
                    return num;
                }
            } else {
                // No relevant coercion target types; return the BigInteger as is:
                return num;
            }
        } else {
            // Unknown number type:
            return num;
        }
    }