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;
}
}