Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1635,8 +1635,10 @@ public static SqlNode toSql(RexLiteral literal) {
case EXACT_NUMERIC: {
if (SqlTypeName.APPROX_TYPES.contains(typeName)) {
final Double d = castNonNull(literal.getValueAs(Double.class));
// BigDecimal cannot represent IEEE 754 special values (NaN, ±Infinity).
if (!Double.isFinite(d)) {
// BigDecimal cannot represent IEEE 754 special values
// (NaN, ±Infinity) or negative zero.
if (!Double.isFinite(d)
|| (d == 0 && Double.doubleToRawLongBits(d) != 0L)) {
final SqlNode strLiteral =
SqlLiteral.createCharString(d.toString(), POS);
final SqlDataTypeSpec typeSpec =
Expand Down
16 changes: 10 additions & 6 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -5101,15 +5101,19 @@ public static double lesser(double b0, double b1) {
/** CAST(FLOAT AS VARCHAR). */
public static String toString(float x) {
if (x == 0) {
return "0E0";
// The comparison 'x == 0' does not distinguish -0.0 from 0.0,
// but the bit pattern does
return Float.floatToRawIntBits(x) != 0 ? "-0E0" : "0E0";
}
return Float.toString(x);
}

/** CAST(DOUBLE AS VARCHAR). */
public static String toString(double x) {
if (x == 0) {
return "0E0";
// The comparison 'x == 0' does not distinguish -0.0 from 0.0,
// but the bit pattern does
return Double.doubleToRawLongBits(x) != 0L ? "-0E0" : "0E0";
}
return Double.toString(x);
}
Expand Down Expand Up @@ -5159,12 +5163,12 @@ public static boolean toBoolean(Number number) {
return decimal.compareTo(BigDecimal.ZERO) != 0;
}
if (number instanceof Double) {
Double d = (Double) number;
return !d.equals(Double.valueOf(0));
// Compare primitives: IEEE 754 treats -0.0 as equal to 0.0,
// whereas Double.equals does not
return ((Double) number).doubleValue() != 0d;
}
if (number instanceof Float) {
Float f = (Float) number;
return !f.equals(Float.valueOf(0));
return ((Float) number).floatValue() != 0f;
}
return !number.equals(0);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@
return node;
}

private static SqlNode convert(PrecedenceClimbingParser.Token token) {

Check failure on line 970 in core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 18 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ_Y4FLFUJhHF7j7d3nv&open=AZ_Y4FLFUJhHF7j7d3nv&pullRequest=5162
switch (token.type) {
case ATOM:
return requireNonNull((SqlNode) token.o);
Expand All @@ -983,8 +983,14 @@
SqlNode firstItem = list.get(0);
if (item.op == SqlStdOperatorTable.UNARY_MINUS
&& firstItem instanceof SqlNumericLiteral) {
return SqlLiteral.createNegative((SqlNumericLiteral) firstItem,
item.pos.plusAll(list));
final SqlNumericLiteral num = (SqlNumericLiteral) firstItem;
// Do not fold "-0.0E0" into a literal: BigDecimal, which backs
// SqlNumericLiteral, cannot represent IEEE 754 negative zero.
// Keeping the unary minus call preserves the sign at runtime.
if (num.isExact()
|| ((BigDecimal) requireNonNull(num.getValue())).signum() != 0) {
return SqlLiteral.createNegative(num, item.pos.plusAll(list));
}
}
if (item.op == SqlStdOperatorTable.UNARY_PLUS
&& firstItem instanceof SqlNumericLiteral) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12973,10 +12973,11 @@ generated, isLinux("SELECT \"$cor0\".\"id\"\n"
.ok("SELECT *\n"
+ "FROM (VALUES (CAST('-Infinity' AS DOUBLE))) AS \"t\" (\"EXPR$0\")");

// Test Negative Zero
// Test Negative Zero: must round-trip through a CAST, because a
// SqlNumericLiteral cannot represent it
sql("select cast('-0.0' as DOUBLE)")
.ok("SELECT *\n"
+ "FROM (VALUES (0E0)) AS \"t\" (\"EXPR$0\")");
+ "FROM (VALUES (CAST('-0.0' AS DOUBLE))) AS \"t\" (\"EXPR$0\")");

// Test Subnormal values
sql("select cast('1e-310' as DOUBLE)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,22 +152,30 @@ static <E> List<E> list() {

@Test void testToString() {
assertThat(SqlFunctions.toString(0f), is("0E0"));
assertThat(SqlFunctions.toString(-0f), is("-0E0"));
assertThat(SqlFunctions.toString(1f), is("1.0"));
assertThat(SqlFunctions.toString(1.5f), is("1.5"));
assertThat(SqlFunctions.toString(-1.5f), is("-1.5"));
assertThat(SqlFunctions.toString(1.5e8f), is("1.5E8"));
assertThat(SqlFunctions.toString(-0.0625f), is("-0.0625"));
assertThat(SqlFunctions.toString(0.0625f), is("0.0625"));
assertThat(SqlFunctions.toString(-5e-12f), is("-5.0E-12"));
assertThat(SqlFunctions.toString(Float.NaN), is("NaN"));
assertThat(SqlFunctions.toString(Float.POSITIVE_INFINITY), is("Infinity"));
assertThat(SqlFunctions.toString(Float.NEGATIVE_INFINITY), is("-Infinity"));

assertThat(SqlFunctions.toString(0d), is("0E0"));
assertThat(SqlFunctions.toString(-0d), is("-0E0"));
assertThat(SqlFunctions.toString(1d), is("1.0"));
assertThat(SqlFunctions.toString(1.5d), is("1.5"));
assertThat(SqlFunctions.toString(-1.5d), is("-1.5"));
assertThat(SqlFunctions.toString(1.5e8d), is("1.5E8"));
assertThat(SqlFunctions.toString(-0.0625d), is("-0.0625"));
assertThat(SqlFunctions.toString(0.0625d), is("0.0625"));
assertThat(SqlFunctions.toString(-5e-12d), is("-5.0E-12"));
assertThat(SqlFunctions.toString(Double.NaN), is("NaN"));
assertThat(SqlFunctions.toString(Double.POSITIVE_INFINITY), is("Infinity"));
assertThat(SqlFunctions.toString(Double.NEGATIVE_INFINITY), is("-Infinity"));

assertThat(SqlFunctions.toString(new BigDecimal("0")), is("0"));
assertThat(SqlFunctions.toString(new BigDecimal("1")), is("1"));
Expand Down
78 changes: 78 additions & 0 deletions testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,84 @@ protected SqlOperatorFixture fixture() {
}
}

@Test void testFPSpecialValues() {
SqlOperatorFixture f = fixture();
f.checkScalarApprox("CAST('Infinity' AS REAL)",
"REAL NOT NULL", "Infinity");
f.checkScalarApprox("CAST('Infinity' AS DOUBLE)",
"DOUBLE NOT NULL", "Infinity");
f.checkScalarApprox("CAST('Infinity' AS FLOAT)",
"FLOAT NOT NULL", "Infinity");
f.checkScalarApprox("CAST('-Infinity' AS REAL)",
"REAL NOT NULL", "-Infinity");
f.checkScalarApprox("CAST('-Infinity' AS DOUBLE)",
"DOUBLE NOT NULL", "-Infinity");
f.checkScalarApprox("CAST('-Infinity' AS FLOAT)",
"FLOAT NOT NULL", "-Infinity");
// Note: IEEE 754 specifies that there are several types of NaN: quiet and signaling.
// There is however only one way to write them.
// But when compared for equality they may not match.
f.checkScalarApprox("CAST('NaN' AS REAL)",
"REAL NOT NULL", "NaN");
f.checkScalarApprox("CAST('NaN' AS DOUBLE)",
"DOUBLE NOT NULL", "NaN");
f.checkScalarApprox("CAST('NaN' AS FLOAT)",
"FLOAT NOT NULL", "NaN");
// [CALCITE-6059] Optimizer does not correctly handle
// special floating point value -0.0E0
// The matcher is(-0.0d) checks the value bit-exactly:
// Double.equals distinguishes -0.0 from 0.0.
f.checkScalarApprox("CAST('-0E0' AS REAL)",
"REAL NOT NULL", is(-0.0d));
f.checkScalarApprox("CAST('-0E0' AS DOUBLE)",
"DOUBLE NOT NULL", is(-0.0d));
f.checkScalarApprox("CAST('-0E0' AS FLOAT)",
"FLOAT NOT NULL", is(-0.0d));
f.checkScalarApprox("CAST('0E0' AS REAL)",
"REAL NOT NULL", is(0.0d));
f.checkScalarApprox("CAST('0E0' AS DOUBLE)",
"DOUBLE NOT NULL", is(0.0d));
f.checkScalarApprox("CAST('0E0' AS FLOAT)",
"FLOAT NOT NULL", is(0.0d));
// Casting an approximate numeric to VARCHAR uses E notation,
// and must preserve the sign of a negative zero
f.checkString("CAST(CAST('-0E0' AS REAL) AS VARCHAR)",
"-0E0", "VARCHAR NOT NULL");
f.checkString("CAST(CAST('-0E0' AS DOUBLE) AS VARCHAR)",
"-0E0", "VARCHAR NOT NULL");
f.checkString("CAST(CAST('0E0' AS REAL) AS VARCHAR)",
"0E0", "VARCHAR NOT NULL");
f.checkString("CAST(CAST('0E0' AS DOUBLE) AS VARCHAR)",
"0E0", "VARCHAR NOT NULL");
// A nullable value is boxed at runtime; formatting must not depend
// on nullability. RAND() prevents constant folding, so the CAST
// executes at runtime on the boxed value.
f.checkString("CAST(CASE WHEN RAND() >= 0 THEN 0.0E0 ELSE NULL END"
+ " AS VARCHAR)",
"0E0", "VARCHAR");
// An array element is a boxed Double in the generated code
f.checkString("CAST(ARRAY[-0.0E0][1] AS VARCHAR)",
"-0E0", "VARCHAR");
// 1/-0.0 = -Infinity: proves that the sign of the zero
// survives arithmetic at runtime.
f.checkScalarApprox("1E0 / CAST('-0E0' AS REAL)",
"DOUBLE NOT NULL", "-Infinity");
f.checkScalarApprox("1E0 / CAST('-0E0' AS DOUBLE)",
"DOUBLE NOT NULL", "-Infinity");
f.checkScalarApprox("1E0 / CAST('0E0' AS DOUBLE)",
"DOUBLE NOT NULL", "Infinity");
// A negative zero written as a literal, rather than computed by a CAST
f.checkScalarApprox("1E0 / -0.0E0",
"DOUBLE NOT NULL", "-Infinity");
f.checkString("CAST(-0.0E0 AS VARCHAR)",
"-0E0", "VARCHAR NOT NULL");
// In comparisons -0.0 is equal to 0.0, so casting either to
// BOOLEAN yields FALSE
f.checkBoolean("CAST(CAST('-0E0' AS DOUBLE) AS BOOLEAN)", false);
f.checkBoolean("CAST(CAST('-0E0' AS REAL) AS BOOLEAN)", false);
f.checkBoolean("CAST(-0.0E0 AS BOOLEAN)", false);
}

@Test void testBetween() {
final SqlOperatorFixture f = fixture();
f.setFor(SqlStdOperatorTable.BETWEEN, VmName.EXPAND);
Expand Down