Skip to content

Commit

Permalink
Throw exception for cast of nan and infinity to int types
Browse files Browse the repository at this point in the history
Fix cast of nan and infinity from DOUBLE/REAL to
BIGINT/INT/SMALLINT/TINYTINT.  Previously all except double -> bigint
would return zero.  Now they will all throw an INVALID_CAST_ARGUMENT
exception.

Additionally fix cast from double to bigint to return a
NUMERIC_VALUE_OUT_OF_RANGE error when casting outside a value outside
bigint range. Previously it would throw and INVALID_CAST_ARGUMENT
exception. Now the exception will be the same as the one for out
of range values for integer/smallint/tinyint casts.
  • Loading branch information
rschlussel committed Jun 4, 2024
1 parent 9c57e73 commit a857670
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import com.facebook.presto.common.block.Block;
import com.facebook.presto.common.type.AbstractLongType;
import com.facebook.presto.common.type.StandardTypes;
import com.facebook.presto.operator.scalar.MathFunctions;
import com.facebook.presto.spi.PrestoException;
import com.facebook.presto.spi.function.BlockIndex;
import com.facebook.presto.spi.function.BlockPosition;
Expand Down Expand Up @@ -58,7 +57,6 @@
import static io.airlift.slice.Slices.utf8Slice;
import static java.lang.Double.doubleToLongBits;
import static java.lang.Float.floatToRawIntBits;
import static java.lang.Math.toIntExact;
import static java.lang.String.format;
import static java.math.RoundingMode.FLOOR;
import static java.math.RoundingMode.HALF_UP;
Expand Down Expand Up @@ -195,10 +193,13 @@ public static boolean castToBoolean(@SqlType(StandardTypes.DOUBLE) double value)
public static long castToInteger(@SqlType(StandardTypes.DOUBLE) double value)
{
try {
return toIntExact((long) MathFunctions.round(value));
return DoubleMath.roundToInt(value, HALF_UP);
}
catch (ArithmeticException e) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for integer: " + value, e);
if (e.getMessage().equals("not in range")) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for integer: " + value, e);
}
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Unable to cast %s to integer", value), e);
}
}

Expand All @@ -207,7 +208,13 @@ public static long castToInteger(@SqlType(StandardTypes.DOUBLE) double value)
public static long castToSmallint(@SqlType(StandardTypes.DOUBLE) double value)
{
try {
return Shorts.checkedCast((long) MathFunctions.round(value));
return Shorts.checkedCast(DoubleMath.roundToInt(value, HALF_UP));
}
catch (ArithmeticException e) {
if (e.getMessage().equals("not in range")) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for smallint: " + value, e);
}
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Unable to cast %s to smallint", value), e);
}
catch (IllegalArgumentException e) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for smallint: " + value, e);
Expand All @@ -219,7 +226,13 @@ public static long castToSmallint(@SqlType(StandardTypes.DOUBLE) double value)
public static long castToTinyint(@SqlType(StandardTypes.DOUBLE) double value)
{
try {
return SignedBytes.checkedCast((long) MathFunctions.round(value));
return SignedBytes.checkedCast(DoubleMath.roundToInt(value, HALF_UP));
}
catch (ArithmeticException e) {
if (e.getMessage().equals("not in range")) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for tinyint: " + value, e);
}
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Unable to cast %s to tinyint", value), e);
}
catch (IllegalArgumentException e) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for tinyint: " + value, e);
Expand All @@ -234,6 +247,9 @@ public static long castToLong(@SqlType(StandardTypes.DOUBLE) double value)
return DoubleMath.roundToLong(value, HALF_UP);
}
catch (ArithmeticException e) {
if (e.getMessage().equals("not in range")) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for bigint: " + value, e);
}
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Unable to cast %s to bigint", value), e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import com.facebook.presto.common.block.Block;
import com.facebook.presto.common.type.AbstractIntType;
import com.facebook.presto.common.type.StandardTypes;
import com.facebook.presto.operator.scalar.MathFunctions;
import com.facebook.presto.spi.PrestoException;
import com.facebook.presto.spi.function.BlockIndex;
import com.facebook.presto.spi.function.BlockPosition;
Expand Down Expand Up @@ -51,13 +50,15 @@
import static com.facebook.presto.common.function.OperatorType.SUBTRACT;
import static com.facebook.presto.common.function.OperatorType.XX_HASH_64;
import static com.facebook.presto.common.type.RealType.REAL;
import static com.facebook.presto.spi.StandardErrorCode.INVALID_CAST_ARGUMENT;
import static com.facebook.presto.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE;
import static io.airlift.slice.Slices.utf8Slice;
import static java.lang.Float.floatToIntBits;
import static java.lang.Float.floatToRawIntBits;
import static java.lang.Float.intBitsToFloat;
import static java.lang.Math.toIntExact;
import static java.lang.String.format;
import static java.math.RoundingMode.FLOOR;
import static java.math.RoundingMode.HALF_UP;

public final class RealOperators
{
Expand Down Expand Up @@ -194,18 +195,29 @@ public static Slice castToVarchar(@SqlType(StandardTypes.REAL) long value)
@SqlType(StandardTypes.BIGINT)
public static long castToLong(@SqlType(StandardTypes.REAL) long value)
{
return (long) MathFunctions.round((double) intBitsToFloat((int) value));
try {
return DoubleMath.roundToLong(intBitsToFloat((int) value), HALF_UP);
}
catch (ArithmeticException e) {
if (e.getMessage().equals("not in range")) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for bigint: " + value, e);
}
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Unable to cast %s to bigint", value), e);
}
}

@ScalarOperator(CAST)
@SqlType(StandardTypes.INTEGER)
public static long castToInteger(@SqlType(StandardTypes.REAL) long value)
{
try {
return toIntExact((long) MathFunctions.round((double) intBitsToFloat((int) value)));
return DoubleMath.roundToInt(intBitsToFloat((int) value), HALF_UP);
}
catch (ArithmeticException e) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for integer: " + value, e);
if (e.getMessage().equals("not in range")) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for integer: " + value, e);
}
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Unable to cast %s to integer", value), e);
}
}

Expand All @@ -214,7 +226,13 @@ public static long castToInteger(@SqlType(StandardTypes.REAL) long value)
public static long castToSmallint(@SqlType(StandardTypes.REAL) long value)
{
try {
return Shorts.checkedCast((long) MathFunctions.round((double) intBitsToFloat((int) value)));
return Shorts.checkedCast(DoubleMath.roundToInt(intBitsToFloat((int) value), HALF_UP));
}
catch (ArithmeticException e) {
if (e.getMessage().equals("not in range")) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for smallint: " + value, e);
}
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Unable to cast %s to smallint", value), e);
}
catch (IllegalArgumentException e) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for smallint: " + value, e);
Expand All @@ -226,7 +244,13 @@ public static long castToSmallint(@SqlType(StandardTypes.REAL) long value)
public static long castToTinyint(@SqlType(StandardTypes.REAL) long value)
{
try {
return SignedBytes.checkedCast((long) MathFunctions.round((double) intBitsToFloat((int) value)));
return SignedBytes.checkedCast(DoubleMath.roundToInt(intBitsToFloat((int) value), HALF_UP));
}
catch (ArithmeticException e) {
if (e.getMessage().equals("not in range")) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for tinyint: " + value, e);
}
throw new PrestoException(INVALID_CAST_ARGUMENT, format("Unable to cast %s to tinyint", value), e);
}
catch (IllegalArgumentException e) {
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, "Out of range for tinyint: " + value, e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
import static com.facebook.presto.common.type.BigintType.BIGINT;
import static com.facebook.presto.common.type.BooleanType.BOOLEAN;
import static com.facebook.presto.common.type.DoubleType.DOUBLE;
import static com.facebook.presto.common.type.IntegerType.INTEGER;
import static com.facebook.presto.common.type.RealType.REAL;
import static com.facebook.presto.common.type.SmallintType.SMALLINT;
import static com.facebook.presto.common.type.TinyintType.TINYINT;
import static com.facebook.presto.common.type.VarcharType.VARCHAR;
import static com.facebook.presto.spi.StandardErrorCode.INVALID_CAST_ARGUMENT;
import static com.facebook.presto.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE;
import static java.lang.Double.doubleToLongBits;
import static java.lang.Double.doubleToRawLongBits;
import static java.lang.Double.isNaN;
Expand Down Expand Up @@ -195,20 +199,73 @@ public void testCastToBigint()
assertFunction("cast(-17.5E0 as bigint)", BIGINT, -18L);

assertFunction("cast(" + Math.nextDown(0x1.0p63) + " as bigint)", BIGINT, (long) Math.nextDown(0x1.0p63));
assertInvalidFunction("cast(" + 0x1.0p63 + " as bigint)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(" + Math.nextUp(0x1.0p63) + " as bigint)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(" + Math.nextDown(-0x1.0p63) + " as bigint)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(" + 0x1.0p63 + " as bigint)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(" + Math.nextUp(0x1.0p63) + " as bigint)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(" + Math.nextDown(-0x1.0p63) + " as bigint)", NUMERIC_VALUE_OUT_OF_RANGE);
assertFunction("cast(" + -0x1.0p63 + " as bigint)", BIGINT, (long) -0x1.0p63);
assertFunction("cast(" + Math.nextUp(-0x1.0p63) + " as bigint)", BIGINT, (long) Math.nextUp(-0x1.0p63));

assertInvalidFunction("cast(9.3E18 as bigint)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(-9.3E18 as bigint)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(9.3E18 as bigint)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(-9.3E18 as bigint)", NUMERIC_VALUE_OUT_OF_RANGE);

assertInvalidFunction("cast(infinity() as bigint)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(-infinity() as bigint)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(nan() as bigint)", INVALID_CAST_ARGUMENT);
}

@Test
public void testCastToInteger()
{
assertFunction("cast(37.7E0 as integer)", INTEGER, 38);
assertFunction("cast(-37.7E0 as integer)", INTEGER, -38);
assertFunction("cast(17.1E0 as integer)", INTEGER, 17);
assertFunction("cast(-17.1E0 as integer)", INTEGER, -17);
assertFunction("cast(9.2E8 as integer)", INTEGER, 920000000);
assertFunction("cast(-9.2E8 as integer)", INTEGER, -920000000);
assertFunction("cast(2.21E8 as integer)", INTEGER, 221000000);
assertFunction("cast(-2.21E8 as integer)", INTEGER, -221000000);
assertFunction("cast(17.5E0 as integer)", INTEGER, 18);
assertFunction("cast(-17.5E0 as integer)", INTEGER, -18);

assertFunction("cast(" + Math.nextDown(0x1.0p31f) + " as integer)", INTEGER, (int) Math.nextDown(0x1.0p31f));
assertInvalidFunction("cast(" + 0x1.0p31 + " as integer)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(" + Math.nextUp(0x1.0p31f) + " as integer)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(" + Math.nextDown(-0x1.0p31f) + " as integer)", NUMERIC_VALUE_OUT_OF_RANGE);
assertFunction("cast(" + -0x1.0p31 + " as integer)", INTEGER, (int) -0x1.0p31);
assertFunction("cast(" + Math.nextUp(-0x1.0p31f) + " as integer)", INTEGER, (int) Math.nextUp(-0x1.0p31f));

assertInvalidFunction("cast(9.3E9 as integer)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(-9.3E9 as integer)", NUMERIC_VALUE_OUT_OF_RANGE);

assertInvalidFunction("cast(infinity() as integer)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(-infinity() as integer)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(nan() as integer)", INVALID_CAST_ARGUMENT);
}

@Test
public void testCastToSmallInt()
{
assertFunction("cast(" + (0x1.0p15 - 0.6) + " as smallint)", SMALLINT, Short.MAX_VALUE);
assertInvalidFunction("cast(DOUBLE '" + 0x1.0p15 + "' as smallint)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(9.2E9 as smallint)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(-9.2E9 as smallint)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(infinity() as smallint)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(-infinity() as smallint)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(nan() as smallint)", INVALID_CAST_ARGUMENT);
}

@Test
public void testCastToTinyInt()
{
assertFunction("cast(" + (0x1.0p7 - 0.6) + " as tinyint)", TINYINT, Byte.MAX_VALUE);
assertInvalidFunction("cast(DOUBLE '" + 0x1.0p7 + "' as tinyint)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(9.2E9 as tinyint)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(-9.2E9 as tinyint)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(infinity() as tinyint)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(-infinity() as tinyint)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(nan() as tinyint)", INVALID_CAST_ARGUMENT);
}

@Test
public void testCastToBoolean()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import static com.facebook.presto.common.type.TinyintType.TINYINT;
import static com.facebook.presto.common.type.VarcharType.VARCHAR;
import static com.facebook.presto.spi.StandardErrorCode.INVALID_CAST_ARGUMENT;
import static com.facebook.presto.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE;
import static com.facebook.presto.testing.DateTimeTestingUtils.sqlTimestampOf;
import static com.facebook.presto.util.StructuralTestUtil.mapType;
import static java.lang.Double.NEGATIVE_INFINITY;
Expand Down Expand Up @@ -79,7 +80,7 @@ public void testCastToBigint()
assertInvalidFunction("cast(JSON '12345678901234567890' as BIGINT)", INVALID_CAST_ARGUMENT);
assertFunction("cast(JSON '128.9' as BIGINT)", BIGINT, 129L);
assertFunction("cast(JSON '1234567890123456789.0' as BIGINT)", BIGINT, 1234567890123456768L); // loss of precision
assertInvalidFunction("cast(JSON '12345678901234567890.0' as BIGINT)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("cast(JSON '12345678901234567890.0' as BIGINT)", NUMERIC_VALUE_OUT_OF_RANGE);
assertFunction("cast(JSON '1e-324' as BIGINT)", BIGINT, 0L);
assertInvalidFunction("cast(JSON '1e309' as BIGINT)", INVALID_CAST_ARGUMENT);
assertFunction("cast(JSON 'true' as BIGINT)", BIGINT, 1L);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import static com.facebook.presto.common.type.SmallintType.SMALLINT;
import static com.facebook.presto.common.type.TinyintType.TINYINT;
import static com.facebook.presto.common.type.VarcharType.VARCHAR;
import static com.facebook.presto.spi.StandardErrorCode.INVALID_CAST_ARGUMENT;
import static com.facebook.presto.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE;
import static java.lang.Float.floatToIntBits;
import static java.lang.Float.intBitsToFloat;
import static java.lang.Float.isNaN;
Expand Down Expand Up @@ -183,6 +185,14 @@ public void testCastToBigInt()
assertFunction("CAST(REAL'-754.2008' as BIGINT)", BIGINT, -754L);
assertFunction("CAST(REAL'1.98' as BIGINT)", BIGINT, 2L);
assertFunction("CAST(REAL'-0.0' as BIGINT)", BIGINT, 0L);

assertInvalidFunction("cast(9.3E18 as bigint)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(-9.3E18 as bigint)", NUMERIC_VALUE_OUT_OF_RANGE);

assertInvalidFunction("CAST(cast(nan() AS REAL) as BIGINT)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(cast(infinity() AS REAL) as BIGINT)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(cast(-infinity() AS REAL) as BIGINT)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(REAL '" + Float.MAX_VALUE + "' as BIGINT)", NUMERIC_VALUE_OUT_OF_RANGE);
}

@Test
Expand All @@ -192,6 +202,20 @@ public void testCastToInteger()
assertFunction("CAST(REAL'-754.1985' AS INTEGER)", INTEGER, -754);
assertFunction("CAST(REAL'9.99' AS INTEGER)", INTEGER, 10);
assertFunction("CAST(REAL'-0.0' AS INTEGER)", INTEGER, 0);

assertFunction("cast(REAL '" + Math.nextDown(0x1.0p31f) + "' as integer)", INTEGER, (int) Math.nextDown(0x1.0p31f));
assertInvalidFunction("cast(REAL '" + 0x1.0p31 + "' as integer)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(REAL '" + Math.nextUp(0x1.0p31f) + "' as integer)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(REAL '" + Math.nextDown(-0x1.0p31f) + "' as integer)", NUMERIC_VALUE_OUT_OF_RANGE);
assertFunction("cast(REAL '" + -0x1.0p31 + "' as integer)", INTEGER, (int) -0x1.0p31);
assertFunction("cast(REAL '" + Math.nextUp(-0x1.0p31f) + "' as integer)", INTEGER, (int) Math.nextUp(-0x1.0p31f));

assertInvalidFunction("cast(9.3E9 as integer)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("cast(-9.3E9 as integer)", NUMERIC_VALUE_OUT_OF_RANGE);
assertInvalidFunction("CAST(cast(nan() AS REAL) as INTEGER)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(cast(infinity() AS REAL) as INTEGER)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(cast(-infinity() AS REAL) as INTEGER)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(REAL '" + (Integer.MAX_VALUE + 0.6) + "' as INTEGER)", NUMERIC_VALUE_OUT_OF_RANGE);
}

@Test
Expand All @@ -201,6 +225,10 @@ public void testCastToSmallint()
assertFunction("CAST(REAL'-754.1985' AS SMALLINT)", SMALLINT, (short) -754);
assertFunction("CAST(REAL'9.99' AS SMALLINT)", SMALLINT, (short) 10);
assertFunction("CAST(REAL'-0.0' AS SMALLINT)", SMALLINT, (short) 0);
assertInvalidFunction("CAST(cast(nan() AS REAL) as SMALLINT)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(cast(infinity() AS REAL) as SMALLINT)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(cast(-infinity() AS REAL) as SMALLINT)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(REAL '" + (Short.MAX_VALUE + 0.6) + "' as SMALLINT)", NUMERIC_VALUE_OUT_OF_RANGE);
}

@Test
Expand All @@ -210,6 +238,10 @@ public void testCastToTinyint()
assertFunction("CAST(REAL'-128.234' AS TINYINT)", TINYINT, (byte) -128);
assertFunction("CAST(REAL'9.99' AS TINYINT)", TINYINT, (byte) 10);
assertFunction("CAST(REAL'-0.0' AS TINYINT)", TINYINT, (byte) 0);
assertInvalidFunction("CAST(cast(nan() AS REAL) as TINYINT)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(cast(infinity() AS REAL) as TINYINT)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(cast(-infinity() AS REAL) as TINYINT)", INVALID_CAST_ARGUMENT);
assertInvalidFunction("CAST(REAL '" + (Byte.MAX_VALUE + 0.6) + "' as TINYINT)", NUMERIC_VALUE_OUT_OF_RANGE);
}

@Test
Expand Down

0 comments on commit a857670

Please sign in to comment.