From 554f1c2931708f0b473a57c7a5ba117eba2c126b Mon Sep 17 00:00:00 2001 From: Rebecca Schlussel Date: Thu, 30 May 2024 11:05:55 -0400 Subject: [PATCH] Add real operator support for new nan definition This adds support for the new nan definition to =, <>, <, >, <=,>=, BETWEEN, IN, NOT IN for real types. --- .../presto/common/type/TypeUtils.java | 41 +++++ .../presto/common/type/TestTypeUtils.java | 30 ++++ ...uiltInTypeAndFunctionNamespaceManager.java | 18 ++- .../type/LegacyRealComparisonOperators.java | 145 ++++++++++++++++++ .../presto/type/RealComparisonOperators.java | 139 +++++++++++++++++ .../facebook/presto/type/RealOperators.java | 115 -------------- .../presto/type/TestRealOperators.java | 20 ++- .../presto/tests/AbstractTestNanQueries.java | 67 ++++++++ 8 files changed, 452 insertions(+), 123 deletions(-) create mode 100644 presto-main/src/main/java/com/facebook/presto/type/LegacyRealComparisonOperators.java create mode 100644 presto-main/src/main/java/com/facebook/presto/type/RealComparisonOperators.java diff --git a/presto-common/src/main/java/com/facebook/presto/common/type/TypeUtils.java b/presto-common/src/main/java/com/facebook/presto/common/type/TypeUtils.java index a90afeccac1a..5dc6372daf70 100644 --- a/presto-common/src/main/java/com/facebook/presto/common/type/TypeUtils.java +++ b/presto-common/src/main/java/com/facebook/presto/common/type/TypeUtils.java @@ -34,6 +34,7 @@ import static com.facebook.presto.common.type.SmallintType.SMALLINT; import static com.facebook.presto.common.type.TinyintType.TINYINT; import static java.lang.Double.doubleToLongBits; +import static java.lang.Float.floatToIntBits; import static java.lang.Float.intBitsToFloat; import static java.lang.Math.toIntExact; import static java.util.Locale.ENGLISH; @@ -258,4 +259,44 @@ public static int doubleCompare(double a, double b) long bBits = doubleToLongBits(b); return Long.compare(aBits, bBits); } + + public static boolean realEquals(float a, float b) + { + // the first check ensures +0 == -0 is true. the second ensures that NaN == NaN is true + // for all other cases a == b and floatToIntBits(a) == floatToIntBits(b) will return + // the same result + // floatToIntBits converts all NaNs to the same representation + return a == b || floatToIntBits(a) == floatToIntBits(b); + } + + public static long realHashCode(float value) + { + // canonicalize +0 and -0 to a single value + value = value == -0 ? 0 : value; + // floatToIntBits converts all NaNs to the same representation + return AbstractLongType.hash(floatToIntBits(value)); + } + + public static int realCompare(float a, float b) + { + // these three ifs can only be true if neither value is NaN + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + // this check ensure floatCompare(+0, -0) will return 0 + // if we just did floatToIntBits comparison, then they + // would not compare as equal + if (a == b) { + return 0; + } + + // this ensures that realCompare(NaN, NaN) will return 0 + // floatToIntBits converts all NaNs to the same representation + int aBits = floatToIntBits(a); + int bBits = floatToIntBits(b); + return Integer.compare(aBits, bBits); + } } diff --git a/presto-common/src/test/java/com/facebook/presto/common/type/TestTypeUtils.java b/presto-common/src/test/java/com/facebook/presto/common/type/TestTypeUtils.java index dea878f5d392..c84f543859c0 100644 --- a/presto-common/src/test/java/com/facebook/presto/common/type/TestTypeUtils.java +++ b/presto-common/src/test/java/com/facebook/presto/common/type/TestTypeUtils.java @@ -24,8 +24,12 @@ import static com.facebook.presto.common.type.TypeUtils.doubleCompare; import static com.facebook.presto.common.type.TypeUtils.doubleEquals; import static com.facebook.presto.common.type.TypeUtils.doubleHashCode; +import static com.facebook.presto.common.type.TypeUtils.realCompare; +import static com.facebook.presto.common.type.TypeUtils.realEquals; +import static com.facebook.presto.common.type.TypeUtils.realHashCode; import static com.facebook.presto.common.type.VarcharType.VARCHAR; import static java.lang.Double.longBitsToDouble; +import static java.lang.Float.intBitsToFloat; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @@ -82,4 +86,30 @@ public void testDoubleCompare() //0x7ff8123412341234L is a different representation of NaN assertEquals(doubleCompare(Double.NaN, longBitsToDouble(0x7ff8123412341234L)), 0); } + + @Test + public void testRealHashCode() + { + assertEquals(realHashCode(0), realHashCode(Float.parseFloat("-0"))); + // 0x7fc01234 is a different representation of NaN + assertEquals(realHashCode(Float.NaN), realHashCode(intBitsToFloat(0x7fc01234))); + } + + @Test + public void testRealEquals() + { + assertTrue(realEquals(0, Float.parseFloat("-0"))); + assertTrue(realEquals(Float.NaN, Float.NaN)); + // 0x7fc01234 is a different representation of NaN + assertTrue(realEquals(Float.NaN, intBitsToFloat(0x7fc01234))); + } + + @Test + public void testRealCompare() + { + assertEquals(realCompare(0, Float.parseFloat("-0")), 0); + assertEquals(realCompare(Float.NaN, Float.NaN), 0); + // 0x7fc01234 is a different representation of NaN + assertEquals(realCompare(Float.NaN, intBitsToFloat(0x7fc01234)), 0); + } } diff --git a/presto-main/src/main/java/com/facebook/presto/metadata/BuiltInTypeAndFunctionNamespaceManager.java b/presto-main/src/main/java/com/facebook/presto/metadata/BuiltInTypeAndFunctionNamespaceManager.java index dbc56ba03aeb..384d0e4f026d 100644 --- a/presto-main/src/main/java/com/facebook/presto/metadata/BuiltInTypeAndFunctionNamespaceManager.java +++ b/presto-main/src/main/java/com/facebook/presto/metadata/BuiltInTypeAndFunctionNamespaceManager.java @@ -257,10 +257,12 @@ import com.facebook.presto.type.IpPrefixOperators; import com.facebook.presto.type.KllSketchOperators; import com.facebook.presto.type.LegacyDoubleComparisonOperators; +import com.facebook.presto.type.LegacyRealComparisonOperators; import com.facebook.presto.type.LikeFunctions; import com.facebook.presto.type.LongEnumOperators; import com.facebook.presto.type.MapParametricType; import com.facebook.presto.type.QuantileDigestOperators; +import com.facebook.presto.type.RealComparisonOperators; import com.facebook.presto.type.RealOperators; import com.facebook.presto.type.SfmSketchOperators; import com.facebook.presto.type.SmallintOperators; @@ -800,20 +802,22 @@ private List getBuiltInFunctions(FeaturesConfig featuresC .scalar(SmallintOperators.SmallintDistinctFromOperator.class) .scalars(TinyintOperators.class) .scalar(TinyintOperators.TinyintDistinctFromOperator.class) - .scalars(DoubleOperators.class); + .scalars(DoubleOperators.class) + .scalars(RealOperators.class); if (featuresConfig.getUseNewNanDefinition()) { builder.scalars(DoubleComparisonOperators.class) - .scalar(DoubleComparisonOperators.DoubleDistinctFromOperator.class); + .scalar(DoubleComparisonOperators.DoubleDistinctFromOperator.class) + .scalars(RealComparisonOperators.class) + .scalar(RealComparisonOperators.RealDistinctFromOperator.class); } else { - builder.scalars(LegacyDoubleComparisonOperators.class) - .scalar(LegacyDoubleComparisonOperators.DoubleDistinctFromOperator.class); + .scalar(LegacyDoubleComparisonOperators.DoubleDistinctFromOperator.class) + .scalars(LegacyRealComparisonOperators.class) + .scalar(LegacyRealComparisonOperators.RealDistinctFromOperator.class); } - builder.scalars(RealOperators.class) - .scalar(RealOperators.RealDistinctFromOperator.class) - .scalars(VarcharOperators.class) + builder.scalars(VarcharOperators.class) .scalar(VarcharOperators.VarcharDistinctFromOperator.class) .scalars(VarbinaryOperators.class) .scalar(VarbinaryOperators.VarbinaryDistinctFromOperator.class) diff --git a/presto-main/src/main/java/com/facebook/presto/type/LegacyRealComparisonOperators.java b/presto-main/src/main/java/com/facebook/presto/type/LegacyRealComparisonOperators.java new file mode 100644 index 000000000000..40d0be8561e0 --- /dev/null +++ b/presto-main/src/main/java/com/facebook/presto/type/LegacyRealComparisonOperators.java @@ -0,0 +1,145 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.type; + +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.spi.function.BlockIndex; +import com.facebook.presto.spi.function.BlockPosition; +import com.facebook.presto.spi.function.IsNull; +import com.facebook.presto.spi.function.ScalarOperator; +import com.facebook.presto.spi.function.SqlNullable; +import com.facebook.presto.spi.function.SqlType; + +import static com.facebook.presto.common.function.OperatorType.BETWEEN; +import static com.facebook.presto.common.function.OperatorType.EQUAL; +import static com.facebook.presto.common.function.OperatorType.GREATER_THAN; +import static com.facebook.presto.common.function.OperatorType.GREATER_THAN_OR_EQUAL; +import static com.facebook.presto.common.function.OperatorType.HASH_CODE; +import static com.facebook.presto.common.function.OperatorType.IS_DISTINCT_FROM; +import static com.facebook.presto.common.function.OperatorType.LESS_THAN; +import static com.facebook.presto.common.function.OperatorType.LESS_THAN_OR_EQUAL; +import static com.facebook.presto.common.function.OperatorType.NOT_EQUAL; +import static com.facebook.presto.common.type.RealType.REAL; +import static java.lang.Float.floatToIntBits; +import static java.lang.Float.intBitsToFloat; + +@Deprecated +public final class LegacyRealComparisonOperators +{ + private LegacyRealComparisonOperators() + { + } + + @ScalarOperator(EQUAL) + @SqlType(StandardTypes.BOOLEAN) + @SqlNullable + public static Boolean equal(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return intBitsToFloat((int) left) == intBitsToFloat((int) right); + } + + @ScalarOperator(NOT_EQUAL) + @SqlType(StandardTypes.BOOLEAN) + @SqlNullable + public static Boolean notEqual(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return intBitsToFloat((int) left) != intBitsToFloat((int) right); + } + + @ScalarOperator(LESS_THAN) + @SqlType(StandardTypes.BOOLEAN) + public static boolean lessThan(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return intBitsToFloat((int) left) < intBitsToFloat((int) right); + } + + @ScalarOperator(LESS_THAN_OR_EQUAL) + @SqlType(StandardTypes.BOOLEAN) + public static boolean lessThanOrEqual(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return intBitsToFloat((int) left) <= intBitsToFloat((int) right); + } + + @ScalarOperator(GREATER_THAN) + @SqlType(StandardTypes.BOOLEAN) + public static boolean greaterThan(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return intBitsToFloat((int) left) > intBitsToFloat((int) right); + } + + @ScalarOperator(GREATER_THAN_OR_EQUAL) + @SqlType(StandardTypes.BOOLEAN) + public static boolean greaterThanOrEqual(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return intBitsToFloat((int) left) >= intBitsToFloat((int) right); + } + + @ScalarOperator(BETWEEN) + @SqlType(StandardTypes.BOOLEAN) + public static boolean between(@SqlType(StandardTypes.REAL) long value, @SqlType(StandardTypes.REAL) long min, @SqlType(StandardTypes.REAL) long max) + { + return intBitsToFloat((int) min) <= intBitsToFloat((int) value) && + intBitsToFloat((int) value) <= intBitsToFloat((int) max); + } + + @ScalarOperator(HASH_CODE) + @SqlType(StandardTypes.BIGINT) + public static long hashCode(@SqlType(StandardTypes.REAL) long value) + { + return AbstractIntType.hash(floatToIntBits(intBitsToFloat((int) value))); + } + + @ScalarOperator(IS_DISTINCT_FROM) + public static class RealDistinctFromOperator + { + @SqlType(StandardTypes.BOOLEAN) + public static boolean isDistinctFrom( + @SqlType(StandardTypes.REAL) long left, + @IsNull boolean leftNull, + @SqlType(StandardTypes.REAL) long right, + @IsNull boolean rightNull) + { + if (leftNull != rightNull) { + return true; + } + if (leftNull) { + return false; + } + float leftFloat = intBitsToFloat((int) left); + float rightFloat = intBitsToFloat((int) right); + if (Float.isNaN(leftFloat) && Float.isNaN(rightFloat)) { + return false; + } + return notEqual(left, right); + } + + @SqlType(StandardTypes.BOOLEAN) + public static boolean isDistinctFrom( + @BlockPosition @SqlType(value = StandardTypes.REAL, nativeContainerType = long.class) Block left, + @BlockIndex int leftPosition, + @BlockPosition @SqlType(value = StandardTypes.REAL, nativeContainerType = long.class) Block right, + @BlockIndex int rightPosition) + { + if (left.isNull(leftPosition) != right.isNull(rightPosition)) { + return true; + } + if (left.isNull(leftPosition)) { + return false; + } + return notEqual(REAL.getLong(left, leftPosition), REAL.getLong(right, rightPosition)); + } + } +} diff --git a/presto-main/src/main/java/com/facebook/presto/type/RealComparisonOperators.java b/presto-main/src/main/java/com/facebook/presto/type/RealComparisonOperators.java new file mode 100644 index 000000000000..a06c8456e6f5 --- /dev/null +++ b/presto-main/src/main/java/com/facebook/presto/type/RealComparisonOperators.java @@ -0,0 +1,139 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.type; + +import com.facebook.presto.common.block.Block; +import com.facebook.presto.common.type.StandardTypes; +import com.facebook.presto.spi.function.BlockIndex; +import com.facebook.presto.spi.function.BlockPosition; +import com.facebook.presto.spi.function.IsNull; +import com.facebook.presto.spi.function.ScalarOperator; +import com.facebook.presto.spi.function.SqlNullable; +import com.facebook.presto.spi.function.SqlType; + +import static com.facebook.presto.common.function.OperatorType.BETWEEN; +import static com.facebook.presto.common.function.OperatorType.EQUAL; +import static com.facebook.presto.common.function.OperatorType.GREATER_THAN; +import static com.facebook.presto.common.function.OperatorType.GREATER_THAN_OR_EQUAL; +import static com.facebook.presto.common.function.OperatorType.HASH_CODE; +import static com.facebook.presto.common.function.OperatorType.IS_DISTINCT_FROM; +import static com.facebook.presto.common.function.OperatorType.LESS_THAN; +import static com.facebook.presto.common.function.OperatorType.LESS_THAN_OR_EQUAL; +import static com.facebook.presto.common.function.OperatorType.NOT_EQUAL; +import static com.facebook.presto.common.type.RealType.REAL; +import static com.facebook.presto.common.type.TypeUtils.realCompare; +import static com.facebook.presto.common.type.TypeUtils.realEquals; +import static com.facebook.presto.common.type.TypeUtils.realHashCode; +import static java.lang.Float.intBitsToFloat; + +public final class RealComparisonOperators +{ + private RealComparisonOperators() + { + } + + @ScalarOperator(EQUAL) + @SqlType(StandardTypes.BOOLEAN) + @SqlNullable + public static Boolean equal(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return realEquals(intBitsToFloat((int) left), intBitsToFloat((int) right)); + } + + @ScalarOperator(NOT_EQUAL) + @SqlType(StandardTypes.BOOLEAN) + @SqlNullable + public static Boolean notEqual(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return !realEquals(intBitsToFloat((int) left), intBitsToFloat((int) right)); + } + + @ScalarOperator(LESS_THAN) + @SqlType(StandardTypes.BOOLEAN) + public static boolean lessThan(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return realCompare(intBitsToFloat((int) left), intBitsToFloat((int) right)) == -1; + } + + @ScalarOperator(LESS_THAN_OR_EQUAL) + @SqlType(StandardTypes.BOOLEAN) + public static boolean lessThanOrEqual(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return realCompare(intBitsToFloat((int) left), intBitsToFloat((int) right)) <= 0; + } + + @ScalarOperator(GREATER_THAN) + @SqlType(StandardTypes.BOOLEAN) + public static boolean greaterThan(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return realCompare(intBitsToFloat((int) left), intBitsToFloat((int) right)) == 1; + } + + @ScalarOperator(GREATER_THAN_OR_EQUAL) + @SqlType(StandardTypes.BOOLEAN) + public static boolean greaterThanOrEqual(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) + { + return realCompare(intBitsToFloat((int) left), intBitsToFloat((int) right)) >= 0; + } + + @ScalarOperator(BETWEEN) + @SqlType(StandardTypes.BOOLEAN) + public static boolean between(@SqlType(StandardTypes.REAL) long value, @SqlType(StandardTypes.REAL) long min, @SqlType(StandardTypes.REAL) long max) + { + return lessThanOrEqual(min, value) && lessThanOrEqual(value, max); + } + + @ScalarOperator(HASH_CODE) + @SqlType(StandardTypes.BIGINT) + public static long hashCode(@SqlType(StandardTypes.REAL) long value) + { + return realHashCode(intBitsToFloat((int) value)); + } + + @ScalarOperator(IS_DISTINCT_FROM) + public static class RealDistinctFromOperator + { + @SqlType(StandardTypes.BOOLEAN) + public static boolean isDistinctFrom( + @SqlType(StandardTypes.REAL) long left, + @IsNull boolean leftNull, + @SqlType(StandardTypes.REAL) long right, + @IsNull boolean rightNull) + { + if (leftNull != rightNull) { + return true; + } + if (leftNull) { + return false; + } + return notEqual(left, right); + } + + @SqlType(StandardTypes.BOOLEAN) + public static boolean isDistinctFrom( + @BlockPosition @SqlType(value = StandardTypes.REAL, nativeContainerType = long.class) Block left, + @BlockIndex int leftPosition, + @BlockPosition @SqlType(value = StandardTypes.REAL, nativeContainerType = long.class) Block right, + @BlockIndex int rightPosition) + { + if (left.isNull(leftPosition) != right.isNull(rightPosition)) { + return true; + } + if (left.isNull(leftPosition)) { + return false; + } + return notEqual(REAL.getLong(left, leftPosition), REAL.getLong(right, rightPosition)); + } + } +} diff --git a/presto-main/src/main/java/com/facebook/presto/type/RealOperators.java b/presto-main/src/main/java/com/facebook/presto/type/RealOperators.java index 5e8c23c3ea90..451a0a58a726 100644 --- a/presto-main/src/main/java/com/facebook/presto/type/RealOperators.java +++ b/presto-main/src/main/java/com/facebook/presto/type/RealOperators.java @@ -13,16 +13,11 @@ */ package com.facebook.presto.type; -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.spi.PrestoException; -import com.facebook.presto.spi.function.BlockIndex; -import com.facebook.presto.spi.function.BlockPosition; import com.facebook.presto.spi.function.IsNull; import com.facebook.presto.spi.function.LiteralParameters; import com.facebook.presto.spi.function.ScalarOperator; -import com.facebook.presto.spi.function.SqlNullable; import com.facebook.presto.spi.function.SqlType; import com.google.common.math.DoubleMath; import com.google.common.primitives.Shorts; @@ -31,25 +26,15 @@ import io.airlift.slice.XxHash64; import static com.facebook.presto.common.function.OperatorType.ADD; -import static com.facebook.presto.common.function.OperatorType.BETWEEN; import static com.facebook.presto.common.function.OperatorType.CAST; import static com.facebook.presto.common.function.OperatorType.DIVIDE; -import static com.facebook.presto.common.function.OperatorType.EQUAL; -import static com.facebook.presto.common.function.OperatorType.GREATER_THAN; -import static com.facebook.presto.common.function.OperatorType.GREATER_THAN_OR_EQUAL; -import static com.facebook.presto.common.function.OperatorType.HASH_CODE; import static com.facebook.presto.common.function.OperatorType.INDETERMINATE; -import static com.facebook.presto.common.function.OperatorType.IS_DISTINCT_FROM; -import static com.facebook.presto.common.function.OperatorType.LESS_THAN; -import static com.facebook.presto.common.function.OperatorType.LESS_THAN_OR_EQUAL; import static com.facebook.presto.common.function.OperatorType.MODULUS; import static com.facebook.presto.common.function.OperatorType.MULTIPLY; import static com.facebook.presto.common.function.OperatorType.NEGATION; -import static com.facebook.presto.common.function.OperatorType.NOT_EQUAL; import static com.facebook.presto.common.function.OperatorType.SATURATED_FLOOR_CAST; 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 io.airlift.slice.Slices.utf8Slice; import static java.lang.Float.floatToIntBits; @@ -116,65 +101,6 @@ public static long negate(@SqlType(StandardTypes.REAL) long value) return floatToRawIntBits(-intBitsToFloat((int) value)); } - @ScalarOperator(EQUAL) - @SqlType(StandardTypes.BOOLEAN) - @SqlNullable - public static Boolean equal(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) - { - return intBitsToFloat((int) left) == intBitsToFloat((int) right); - } - - @ScalarOperator(NOT_EQUAL) - @SqlType(StandardTypes.BOOLEAN) - @SqlNullable - public static Boolean notEqual(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) - { - return intBitsToFloat((int) left) != intBitsToFloat((int) right); - } - - @ScalarOperator(LESS_THAN) - @SqlType(StandardTypes.BOOLEAN) - public static boolean lessThan(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) - { - return intBitsToFloat((int) left) < intBitsToFloat((int) right); - } - - @ScalarOperator(LESS_THAN_OR_EQUAL) - @SqlType(StandardTypes.BOOLEAN) - public static boolean lessThanOrEqual(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) - { - return intBitsToFloat((int) left) <= intBitsToFloat((int) right); - } - - @ScalarOperator(GREATER_THAN) - @SqlType(StandardTypes.BOOLEAN) - public static boolean greaterThan(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) - { - return intBitsToFloat((int) left) > intBitsToFloat((int) right); - } - - @ScalarOperator(GREATER_THAN_OR_EQUAL) - @SqlType(StandardTypes.BOOLEAN) - public static boolean greaterThanOrEqual(@SqlType(StandardTypes.REAL) long left, @SqlType(StandardTypes.REAL) long right) - { - return intBitsToFloat((int) left) >= intBitsToFloat((int) right); - } - - @ScalarOperator(BETWEEN) - @SqlType(StandardTypes.BOOLEAN) - public static boolean between(@SqlType(StandardTypes.REAL) long value, @SqlType(StandardTypes.REAL) long min, @SqlType(StandardTypes.REAL) long max) - { - return intBitsToFloat((int) min) <= intBitsToFloat((int) value) && - intBitsToFloat((int) value) <= intBitsToFloat((int) max); - } - - @ScalarOperator(HASH_CODE) - @SqlType(StandardTypes.BIGINT) - public static long hashCode(@SqlType(StandardTypes.REAL) long value) - { - return AbstractIntType.hash(floatToIntBits(intBitsToFloat((int) value))); - } - @ScalarOperator(XX_HASH_64) @SqlType(StandardTypes.BIGINT) public static long xxHash64(@SqlType(StandardTypes.REAL) long value) @@ -252,47 +178,6 @@ public static boolean castToBoolean(@SqlType(StandardTypes.REAL) long value) return intBitsToFloat((int) value) != 0.0f; } - @ScalarOperator(IS_DISTINCT_FROM) - public static class RealDistinctFromOperator - { - @SqlType(StandardTypes.BOOLEAN) - public static boolean isDistinctFrom( - @SqlType(StandardTypes.REAL) long left, - @IsNull boolean leftNull, - @SqlType(StandardTypes.REAL) long right, - @IsNull boolean rightNull) - { - if (leftNull != rightNull) { - return true; - } - if (leftNull) { - return false; - } - float leftFloat = intBitsToFloat((int) left); - float rightFloat = intBitsToFloat((int) right); - if (Float.isNaN(leftFloat) && Float.isNaN(rightFloat)) { - return false; - } - return notEqual(left, right); - } - - @SqlType(StandardTypes.BOOLEAN) - public static boolean isDistinctFrom( - @BlockPosition @SqlType(value = StandardTypes.REAL, nativeContainerType = long.class) Block left, - @BlockIndex int leftPosition, - @BlockPosition @SqlType(value = StandardTypes.REAL, nativeContainerType = long.class) Block right, - @BlockIndex int rightPosition) - { - if (left.isNull(leftPosition) != right.isNull(rightPosition)) { - return true; - } - if (left.isNull(leftPosition)) { - return false; - } - return notEqual(REAL.getLong(left, leftPosition), REAL.getLong(right, rightPosition)); - } - } - @ScalarOperator(SATURATED_FLOOR_CAST) @SqlType(StandardTypes.SMALLINT) public static long saturatedFloorCastToSmallint(@SqlType(StandardTypes.REAL) long value) diff --git a/presto-main/src/test/java/com/facebook/presto/type/TestRealOperators.java b/presto-main/src/test/java/com/facebook/presto/type/TestRealOperators.java index 5b7e24af2c3f..8874fdd63a22 100644 --- a/presto-main/src/test/java/com/facebook/presto/type/TestRealOperators.java +++ b/presto-main/src/test/java/com/facebook/presto/type/TestRealOperators.java @@ -29,6 +29,7 @@ import static java.lang.Float.floatToIntBits; import static java.lang.Float.intBitsToFloat; import static java.lang.Float.isNaN; +import static java.lang.Float.parseFloat; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @@ -109,6 +110,7 @@ public void testEqual() assertFunction("REAL'-17.34' = REAL'-17.34'", BOOLEAN, true); assertFunction("REAL'71.17' = REAL'23.45'", BOOLEAN, false); assertFunction("REAL'-0.0' = REAL'0.0'", BOOLEAN, true); + assertFunction("CAST(nan() AS REAL) = CAST(nan() AS REAL)", BOOLEAN, true); } @Test @@ -119,6 +121,7 @@ public void testNotEqual() assertFunction("REAL'-17.34' <> REAL'-17.34'", BOOLEAN, false); assertFunction("REAL'71.17' <> REAL'23.45'", BOOLEAN, true); assertFunction("REAL'-0.0' <> REAL'0.0'", BOOLEAN, false); + assertFunction("CAST(nan() AS REAL) <> CAST(nan() AS REAL)", BOOLEAN, false); } @Test @@ -128,6 +131,7 @@ public void testLessThan() assertFunction("REAL'-17.34' < REAL'-16.34'", BOOLEAN, true); assertFunction("REAL'71.17' < REAL'23.45'", BOOLEAN, false); assertFunction("REAL'-0.0' < REAL'0.0'", BOOLEAN, false); + assertFunction("-CAST(nan() AS REAL) < -CAST(INFINITY() AS REAL)", BOOLEAN, false); } @Test @@ -137,6 +141,8 @@ public void testLessThanOrEqual() assertFunction("REAL'-17.34' <= REAL'-17.34'", BOOLEAN, true); assertFunction("REAL'71.17' <= REAL'23.45'", BOOLEAN, false); assertFunction("REAL'-0.0' <= REAL'0.0'", BOOLEAN, true); + assertFunction("-CAST(nan() AS REAL) <= -CAST(INFINITY() AS REAL)", BOOLEAN, false); + assertFunction("CAST(nan() AS REAL) <= CAST(nan() AS REAL)", BOOLEAN, true); } @Test @@ -146,6 +152,7 @@ public void testGreaterThan() assertFunction("REAL'-17.34' > REAL'-17.34'", BOOLEAN, false); assertFunction("REAL'71.17' > REAL'23.45'", BOOLEAN, true); assertFunction("REAL'-0.0' > REAL'0.0'", BOOLEAN, false); + assertFunction("CAST(nan() AS REAL) > CAST(infinity() AS REAL)", BOOLEAN, true); } @Test @@ -155,6 +162,8 @@ public void testGreaterThanOrEqual() assertFunction("REAL'-17.34' >= REAL'-17.34'", BOOLEAN, true); assertFunction("REAL'71.17' >= REAL'23.45'", BOOLEAN, true); assertFunction("REAL'-0.0' >= REAL'0.0'", BOOLEAN, true); + assertFunction("CAST(nan() AS REAL) >= CAST(infinity() AS REAL)", BOOLEAN, true); + assertFunction("CAST(nan() AS REAL) >= CAST(nan() AS REAL)", BOOLEAN, true); } @Test @@ -166,6 +175,8 @@ public void testBetween() assertFunction("REAL'0.0' BETWEEN REAL'-1.2' AND REAL'2.3'", BOOLEAN, true); assertFunction("REAL'56.78' BETWEEN REAL'12.34' AND REAL'34.56'", BOOLEAN, false); assertFunction("REAL'56.78' BETWEEN REAL'78.89' AND REAL'98.765'", BOOLEAN, false); + assertFunction("REAL '0.0' BETWEEN REAL '-0.0' AND REAL '-0.0'", BOOLEAN, true); + assertFunction("CAST(nan() AS REAL) BETWEEN CAST(nan() AS REAL) AND CAST(nan() AS REAL)", BOOLEAN, true); } @Test @@ -266,6 +277,7 @@ public void testIsDistinctFrom() assertFunction("REAL'37.7' IS DISTINCT FROM REAL'37.8'", BOOLEAN, true); assertFunction("NULL IS DISTINCT FROM REAL'37.7'", BOOLEAN, true); assertFunction("REAL'37.7' IS DISTINCT FROM NULL", BOOLEAN, true); + assertFunction("REAL '0.0' IS DISTINCT FROM REAL '-0.0'", BOOLEAN, false); assertFunction("CAST(nan() AS REAL) IS DISTINCT FROM CAST(nan() AS REAL)", BOOLEAN, false); } @@ -285,7 +297,13 @@ public void testNanHash() int[] nanRepresentations = {floatToIntBits(Float.NaN), 0xffc00000, 0x7fc00000, 0x7fc01234, 0xffc01234}; for (int nanRepresentation : nanRepresentations) { assertTrue(isNaN(intBitsToFloat(nanRepresentation))); - assertEquals(RealOperators.hashCode(nanRepresentation), RealOperators.hashCode(nanRepresentations[0])); + assertEquals(RealComparisonOperators.hashCode(nanRepresentation), RealComparisonOperators.hashCode(nanRepresentations[0])); } } + + @Test + public void testZeroHash() + { + assertEquals(RealComparisonOperators.hashCode(floatToIntBits(0)), RealComparisonOperators.hashCode(floatToIntBits(parseFloat("-0.0")))); + } } diff --git a/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestNanQueries.java b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestNanQueries.java index 77f38cd0f6f7..d4630828e5d0 100644 --- a/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestNanQueries.java +++ b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestNanQueries.java @@ -30,6 +30,9 @@ public abstract class AbstractTestNanQueries public static final String DOUBLE_NAN_LAST_COLUMN = "_double_nan_last"; public static final String REAL_NANS_TABLE_NAME = "real_nans_table"; + public static final String REAL_NAN_FIRST_COLUMN = "_real_nan_first"; + public static final String REAL_NAN_MIDDLE_COLUMN = "_real_nan_middle"; + public static final String REAL_NAN_LAST_COLUMN = "_real_nan_last"; @BeforeClass public void setup() @@ -71,6 +74,13 @@ public void testDoubleLessThan() assertQuery(format("SELECT nan() < _double_nan_first from %s", DOUBLE_NANS_TABLE_NAME), "SELECT * FROM (VALUES (false), (false), (false), (false))"); } + @Test + public void testRealLessThan() + { + assertQuery(format("SELECT _real_nan_first < CAST(nan() AS REAL) from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (false), (true), (true), (true))"); + assertQuery(format("SELECT CAST(nan() AS REAL) < _real_nan_first from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (false), (false), (false), (false))"); + } + @Test public void testDoubleGreaterThan() { @@ -83,6 +93,12 @@ public void testDoubleGreaterThan() } @Test + public void testRealGreaterThan() + { + assertQuery(format("SELECT _real_nan_first > cast(nan() AS REAL) from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (false), (false), (false), (false))"); + assertQuery(format("SELECT CAST(nan() AS REAL)> _real_nan_first from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (false), (true), (true), (true))"); + } + @Test public void testDoubleLessThanOrEqualTo() { @@ -94,6 +110,13 @@ public void testDoubleLessThanOrEqualTo() assertQuery(format("SELECT nan() <= _double_nan_first from %s", DOUBLE_NANS_TABLE_NAME), "SELECT * FROM (VALUES (true), (false), (false), (false))"); } + @Test + public void testRealLessThanOrEqualTo() + { + assertQuery(format("SELECT _real_nan_first <= CAST(nan() AS REAL) from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (true), (true), (true), (true))"); + assertQuery(format("SELECT CAST(nan() AS REAL) <= _real_nan_first from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (true), (false), (false), (false))"); + } + @Test public void testDoubleGreaterThanOrEqualTo() { @@ -105,6 +128,13 @@ public void testDoubleGreaterThanOrEqualTo() assertQuery(format("SELECT nan() >= _double_nan_first from %s", DOUBLE_NANS_TABLE_NAME), "SELECT * FROM (VALUES (true), (true), (true), (true))"); } + @Test + public void testRealGreaterThanOrEqualTo() + { + assertQuery(format("SELECT _real_nan_first >= CAST(nan() AS REAL) from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (true), (false), (false), (false))"); + assertQuery(format("SELECT CAST(nan() AS REAL) >= _real_nan_first from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (true), (true), (true), (true))"); + } + @Test public void testDoubleEquals() { @@ -114,6 +144,13 @@ public void testDoubleEquals() assertQuery(format("SELECT nan() = _double_nan_first from %s", DOUBLE_NANS_TABLE_NAME), "SELECT * FROM (VALUES (true), (false), (false), (false))"); } + @Test + public void testRealEquals() + { + assertQuery(format("SELECT _real_nan_first = CAST(nan() AS REAL) from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (true), (false), (false), (false))"); + assertQuery(format("SELECT CAST(nan() AS REAL) = _real_nan_first from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (true), (false), (false), (false))"); + } + @Test public void testDoubleNotEquals() { @@ -122,6 +159,14 @@ public void testDoubleNotEquals() assertQuery(format("SELECT _double_nan_first <> nan() from %s", DOUBLE_NANS_TABLE_NAME), "SELECT * FROM (VALUES (false), (true), (true), (true))"); assertQuery(format("SELECT nan() <> _double_nan_first from %s", DOUBLE_NANS_TABLE_NAME), "SELECT * FROM (VALUES (false), (true), (true), (true))"); } + + @Test + public void testRealNotEquals() + { + assertQuery(format("SELECT _real_nan_first <> CAST(nan() AS REAL) from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (false), (true), (true), (true))"); + assertQuery(format("SELECT CAST(nan() AS REAL) <> _real_nan_first from %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (false), (true), (true), (true))"); + } + @Test public void testDoubleBetween() { @@ -129,6 +174,13 @@ public void testDoubleBetween() assertQuery(format("SELECT _double_nan_first BETWEEN -infinity() AND nan() FROM %s", DOUBLE_NANS_TABLE_NAME), "SELECT * FROM (VALUES(true), (true), (true), (true))"); } + @Test + public void testRealBetween() + { + assertQuery(format("SELECT CAST(nan() AS REAL) BETWEEN CAST(-infinity() AS REAL) AND _real_nan_first FROM %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (true), (false), (false), (false))"); + assertQuery(format("SELECT _real_nan_first BETWEEN CAST(-infinity() AS REAL) AND cast(nan() AS REAL) FROM %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES(true), (true), (true), (true))"); + } + @Test public void testDoubleIn() { @@ -136,9 +188,24 @@ public void testDoubleIn() assertQuery(format("SELECT _double_nan_first IN (nan(), 0, 6)FROM %s", DOUBLE_NANS_TABLE_NAME), "SELECT * FROM (VALUES(true), (true), (false), (false))"); } + @Test + public void testRealIn() + { + assertQuery(format("SELECT CAST(nan() as REAL) IN (REAL '1', REAL '2', _real_nan_first) FROM %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (true), (false), (false), (false))"); + assertQuery(format("SELECT _real_nan_first IN (CAST(nan() as REAL), REAL '0', REAL '6')FROM %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES(true), (true), (false), (false))"); + } + @Test public void testDoubleNotIn() { assertQuery(format("SELECT nan() NOT IN (1, 2, _double_nan_first) FROM %s", DOUBLE_NANS_TABLE_NAME), "SELECT * FROM (VALUES (false), (true), (true), (true))"); assertQuery(format("SELECT _double_nan_first NOT IN (nan(), 0, 6)FROM %s", DOUBLE_NANS_TABLE_NAME), "SELECT * FROM (VALUES(false), (false), (true), (true))"); } + + @Test + public void testRealNotIn() + { + assertQuery(format("SELECT CAST(nan() as REAL) NOT IN (REAL '1', REAL '2', _real_nan_first) FROM %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES (false), (true), (true), (true))"); + assertQuery(format("SELECT _real_nan_first NOT IN (CAST(nan() as REAL), 0, 6)FROM %s", REAL_NANS_TABLE_NAME), "SELECT * FROM (VALUES(false), (false), (true), (true))"); + } +}