Skip to content
Closed
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
5 changes: 3 additions & 2 deletions be/src/exprs/function/cast/cast_to_string.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ struct CastToString {
end = fmt::format_to(buffer, FMT_COMPILE("{:.{}g}"), value,
std::numeric_limits<float>::digits10 + 1);
} else {
end = fmt::format_to(buffer, FMT_COMPILE("{:.{}g}"), value,
std::numeric_limits<double>::digits10 + 1);
// shortest round-trip, fixed-precision %g would expose IEEE-754
// residual error e.g. round(23900/293, 2) -> "81.56999999999999".
end = fmt::format_to(buffer, FMT_COMPILE("{}"), value);
Comment thread
xy720 marked this conversation as resolved.
Comment thread
xy720 marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current head updates the direct cast and storage expectations, but this helper is also the normal text serialization path for DOUBLE results: DataTypeNumberSerDe::to_string()/write_column_to_mysql_text() call CastToString::push_number, and array/map serializers delegate to their nested numeric serde. Several generated result files for those paths still contain the old edge spellings, so they will fail after this formatter change. Examples I found include regression-test/data/export_p0/test_export_data_types.out (4.940656458412465e-324, 1.797693134862316e+308), the outfile array/map outputs under regression-test/data/export_p0/outfile/{csv,parquet}/ and regression-test/data/export_p0/test_outfile_orc_*, and regression-test/data/nereids_function_p0/scalar_function/Array1.out with old DOUBLE min/max strings. With this line those paths now emit values such as 5e-324, 2.2250738585072014e-308, and 1.7976931348623157e+308. Please regenerate/update all affected result files. This is distinct from the existing test_cast_to_string_from_float.out thread because these are ordinary result/outfile/complex-type serialization paths, not the explicit cast suite already discussed.

}
}
*end = '\0';
Expand Down
20 changes: 10 additions & 10 deletions be/test/exprs/function/cast/cast_to_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ TEST_F(FunctionCastToStringTest, from_double) {
},
{
1234567890000.12345,
"1234567890000.124",
"1234567890000.1235",
},
{
0.33,
Expand Down Expand Up @@ -309,20 +309,20 @@ TEST_F(FunctionCastToStringTest, from_double) {
},
{
12345678901234567.12345,
"1.234567890123457e+16",
"1.2345678901234568e+16",
},
{
123456789012345678.12345,
"1.234567890123457e+17",
"1.2345678901234568e+17",
},
{std::numeric_limits<float>::min(), "1.175494350822288e-38"},
{std::numeric_limits<float>::lowest(), "-3.402823466385289e+38"},
{std::numeric_limits<float>::min(), "1.1754943508222875e-38"},
{std::numeric_limits<float>::lowest(), "-3.4028234663852886e+38"},
{std::numeric_limits<float>::denorm_min(), "1.401298464324817e-45"},
{std::numeric_limits<float>::max(), "3.402823466385289e+38"},
{std::numeric_limits<double>::min(), "2.225073858507201e-308"},
{std::numeric_limits<double>::lowest(), "-1.797693134862316e+308"},
{std::numeric_limits<double>::denorm_min(), "4.940656458412465e-324"},
{std::numeric_limits<double>::max(), "1.797693134862316e+308"},
{std::numeric_limits<float>::max(), "3.4028234663852886e+38"},
{std::numeric_limits<double>::min(), "2.2250738585072014e-308"},
{std::numeric_limits<double>::lowest(), "-1.7976931348623157e+308"},
{std::numeric_limits<double>::denorm_min(), "5e-324"},
{std::numeric_limits<double>::max(), "1.7976931348623157e+308"},
{
std::numeric_limits<float>::infinity(),
"Infinity",
Expand Down
6 changes: 3 additions & 3 deletions be/test/gutil/strings/numbers_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@ TEST_F(NumbersTest, test_double_to_buffer) {

double v6 = std::numeric_limits<double>::max();
len2 = CastToString::_fast_to_buffer(v6, buffer2);
EXPECT_EQ(std::string("1.797693134862316e+308"), std::string(buffer2, len2));
EXPECT_EQ(std::string("1.7976931348623157e+308"), std::string(buffer2, len2));

double v7 = std::numeric_limits<double>::min();
len2 = CastToString::_fast_to_buffer(v7, buffer2);
EXPECT_EQ(std::string("2.225073858507201e-308"), std::string(buffer2, len2));
EXPECT_EQ(std::string("2.2250738585072014e-308"), std::string(buffer2, len2));

double v8 = 0 - std::numeric_limits<double>::max();
len2 = CastToString::_fast_to_buffer(v8, buffer2);
EXPECT_EQ(std::string("-1.797693134862316e+308"), std::string(buffer2, len2));
EXPECT_EQ(std::string("-1.7976931348623157e+308"), std::string(buffer2, len2));
}

/*
Expand Down
52 changes: 20 additions & 32 deletions be/test/storage/olap_type_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ TEST_F(OlapTypeTest, ser_deser_double) {
},
{
1234567890000.12345,
"1234567890000.124",
"1234567890000.1235",
},
{
0.33,
Expand Down Expand Up @@ -538,11 +538,11 @@ TEST_F(OlapTypeTest, ser_deser_double) {
},
{
12345678901234567.12345,
"1.234567890123457e+16",
"1.2345678901234568e+16",
},
{
123456789012345678.12345,
"1.234567890123457e+17",
"1.2345678901234568e+17",
}};
for (int i = 1; i < 10; ++i) {
if (i == 7) {
Expand All @@ -565,14 +565,14 @@ TEST_F(OlapTypeTest, ser_deser_double) {
-0.0,
"-0",
},
{std::numeric_limits<float>::min(), "1.175494350822288e-38"},
{std::numeric_limits<float>::lowest(), "-3.402823466385289e+38"},
{std::numeric_limits<float>::min(), "1.1754943508222875e-38"},
{std::numeric_limits<float>::lowest(), "-3.4028234663852886e+38"},
{std::numeric_limits<float>::denorm_min(), "1.401298464324817e-45"},
{std::numeric_limits<float>::max(), "3.402823466385289e+38"},
{std::numeric_limits<double>::min(), "2.225073858507201e-308"},
{std::numeric_limits<double>::lowest(), "-1.797693134862316e+308"},
{std::numeric_limits<double>::denorm_min(), "4.940656458412465e-324"},
{std::numeric_limits<double>::max(), "1.797693134862316e+308"},
{std::numeric_limits<float>::max(), "3.4028234663852886e+38"},
{std::numeric_limits<double>::min(), "2.2250738585072014e-308"},
{std::numeric_limits<double>::lowest(), "-1.7976931348623157e+308"},
{std::numeric_limits<double>::denorm_min(), "5e-324"},
{std::numeric_limits<double>::max(), "1.7976931348623157e+308"},
{
std::numeric_limits<double>::infinity(),
"Infinity",
Expand Down Expand Up @@ -607,11 +607,8 @@ TEST_F(OlapTypeTest, ser_deser_double) {
EXPECT_EQ(result_str, expected_str);
Field restored_field;
auto status = data_type_serde->from_fe_string(result_str, restored_field);
// from_fe_string rejects NaN/Infinity strings, and also rejects
// double::max()/lowest() whose string representation parses to Infinity
if (std::isnan(float_value) || std::isinf(float_value) ||
float_value == std::numeric_limits<double>::max() ||
float_value == std::numeric_limits<double>::lowest()) {
// from_fe_string rejects NaN/Infinity strings.
if (std::isnan(float_value) || std::isinf(float_value)) {
EXPECT_FALSE(status.ok());
continue;
}
Expand Down Expand Up @@ -1026,8 +1023,8 @@ TEST_F(OlapTypeTest, ser_deser_double_olap_string) {
{0.001, "0.001"},
{1234567890123456.0, "1234567890123456"},
{1e-100, "1e-100"},
{std::numeric_limits<double>::lowest(), "-1.797693134862316e+308"},
{std::numeric_limits<double>::max(), "1.797693134862316e+308"},
{std::numeric_limits<double>::lowest(), "-1.7976931348623157e+308"},
{std::numeric_limits<double>::max(), "1.7976931348623157e+308"},
};

for (const auto& [val, expected_str] : normal_cases) {
Expand All @@ -1039,15 +1036,6 @@ TEST_F(OlapTypeTest, ser_deser_double_olap_string) {
// Round-trip
Field restored_field;
auto status = serde->from_zonemap_string(result_str, restored_field);
if (val == std::numeric_limits<double>::lowest() ||
val == std::numeric_limits<double>::max()) {
EXPECT_FALSE(status.ok());
EXPECT_NE(status.to_string().find("NaN/Infinity not allowed in olap string"),
std::string::npos)
<< status.to_string();
continue;
}

EXPECT_TRUE(status.ok()) << status.to_string();
double restored_val = restored_field.get<TYPE_DOUBLE>();
double diff = std::abs(restored_val - val);
Expand Down Expand Up @@ -1832,12 +1820,12 @@ TEST_F(OlapTypeTest, double_type) {
{std::numeric_limits<double>::quiet_NaN(), "NaN", "NaN"},
{std::numeric_limits<double>::infinity(), "Infinity", "Infinity"},
{-std::numeric_limits<double>::infinity(), "-Infinity", "-Infinity"},
{std::numeric_limits<double>::max(), "1.797693134862316e+308",
"1.797693134862316e+308"},
{std::numeric_limits<double>::lowest(), "-1.797693134862316e+308",
"-1.797693134862316e+308"},
{std::numeric_limits<double>::min(), "2.225073858507201e-308",
"2.225073858507201e-308"},
{std::numeric_limits<double>::max(), "1.7976931348623157e+308",
"1.7976931348623157e+308"},
{std::numeric_limits<double>::lowest(), "-1.7976931348623157e+308",
"-1.7976931348623157e+308"},
{std::numeric_limits<double>::min(), "2.2250738585072014e-308",
"2.2250738585072014e-308"},
};

for (auto& tc : test_cases) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,22 @@
package org.apache.doris.common;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

/**
* Util class for float/double to string.
*/
public class FractionalFormat {

Comment thread
xy720 marked this conversation as resolved.
// FLT_DIG + 1; precision <= this value means float-equivalent rendering.
Comment thread
xy720 marked this conversation as resolved.
private static final int FLOAT_MAX_SIGNIFICANT_DIGITS = 7;

/**
* Get string of double/float value for cast to string and output to mysql.
*
* @param value The double/float value.
* @param precision precision
* @param precision precision (use {@code <= FLOAT_MAX_SIGNIFICANT_DIGITS} for float).
* @param sciFormat format for string with scientific form.
* @return string value.
*/
Expand All @@ -46,6 +50,13 @@ public static String getFormatStringValue(double value, int precision, String sc
if (Double.compare(value, -0.0) == 0) {
return "-0";
}
if (precision <= FLOAT_MAX_SIGNIFICANT_DIGITS) {
return formatFixedPrecision(value, precision, sciFormat);
}
return formatShortest(value, precision);
}

private static String formatFixedPrecision(double value, int precision, String sciFormat) {
int expLower = -4;
int exponent = (int) Math.floor(Math.log10(Math.abs(value)));
if (exponent < precision && exponent >= expLower) {
Expand All @@ -59,9 +70,69 @@ public static String getFormatStringValue(double value, int precision, String sc
}
}
return result;
}
return String.format(sciFormat, value).replaceAll("(\\.\\d*?[1-9])0*E", "$1E")
.replaceAll("\\.0*E", "E").replaceAll("E", "e");
}

private static String formatShortest(double value, int precision) {
// shortest round-trip; new BigDecimal(double) would capture the exact
// IEEE-754 value and setScale(16,HALF_UP) would expose its tail,
Comment thread
xy720 marked this conversation as resolved.
// e.g. round(23900/293, 2) -> "81.56999999999999".
BigDecimal bd = new BigDecimal(Double.toString(value)).stripTrailingZeros();
bd = trimToShortestRoundTrip(value, bd);
int expLower = -4;
int exponent = (int) Math.floor(Math.log10(Math.abs(value)));
if (exponent < precision && exponent >= expLower) {
String result = bd.toPlainString();
if (result.contains(".")) {
result = result.replaceAll("0+$", "");
if (result.endsWith(".")) {
result = result.substring(0, result.length() - 1);
}
}
return result;
}
return formatScientific(bd);
}

private static BigDecimal trimToShortestRoundTrip(double value, BigDecimal bd) {
while (bd.precision() > 1) {
BigDecimal shorter = bd.round(new MathContext(bd.precision() - 1, RoundingMode.HALF_EVEN))
.stripTrailingZeros();
if (Double.compare(Double.parseDouble(shorter.toString()), value) != 0) {
break;
}
bd = shorter;
}
return bd;
}

/** Format the BigDecimal as "<sig>.<digits>e[+|-]NN" with at least two exponent digits. */
private static String formatScientific(BigDecimal bd) {
String unscaled = bd.unscaledValue().abs().toString();
int sig = unscaled.length();
int exp = (sig - 1) - bd.scale();
StringBuilder sb = new StringBuilder(sig + 6);
if (bd.signum() < 0) {
sb.append('-');
}
sb.append(unscaled.charAt(0));
if (sig > 1) {
sb.append('.');
sb.append(unscaled, 1, sig);
}
sb.append('e');
if (exp >= 0) {
sb.append('+');
} else {
return String.format(sciFormat, value).replaceAll("(\\.\\d*?[1-9])0*E", "$1E")
.replaceAll("\\.0*E", "E").replaceAll("E", "e");
sb.append('-');
exp = -exp;
}
if (exp < 10) {
sb.append('0');
}
sb.append(exp);
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ public void testDoubleGetStringValue() {
Assertions.assertEquals("-Infinity", new FloatLiteral(Double.NEGATIVE_INFINITY, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("NaN", new FloatLiteral(Double.NaN, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("1234567890123456", new FloatLiteral(1234567890123456.12345, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("1.234567890123457e+16", new FloatLiteral(12345678901234567.12345, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("0.0001234567890123457", new FloatLiteral(0.0001234567890123456789, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("1.2345678901234568e+16", new FloatLiteral(12345678901234567.12345, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("0.00012345678901234567", new FloatLiteral(0.0001234567890123456789, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("1.234567890123456e-15", new FloatLiteral(0.000000000000001234567890123456, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("123.456", new FloatLiteral(123.456000, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("123", new FloatLiteral(123.000, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("-1234567890123456", new FloatLiteral(-1234567890123456.12345, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("-1.234567890123457e+16", new FloatLiteral(-12345678901234567.12345, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("-0.0001234567890123457", new FloatLiteral(-0.0001234567890123456789, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("-1.2345678901234568e+16", new FloatLiteral(-12345678901234567.12345, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("-0.00012345678901234567", new FloatLiteral(-0.0001234567890123456789, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("-1.234567890123456e-15", new FloatLiteral(-0.000000000000001234567890123456, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("-123.456", new FloatLiteral(-123.456000, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Assertions.assertEquals("-123", new FloatLiteral(-123.000, Type.DOUBLE).accept(ExprToStringValueVisitor.INSTANCE, StringValueContext.forQuery(FormatOptions.getDefault())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,14 +352,14 @@ public void testDoubleGetStringValueFor() {
Assertions.assertEquals("-Infinity", ((StringLiteral) (new DoubleLiteral(Double.NEGATIVE_INFINITY).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("NaN", ((StringLiteral) (new DoubleLiteral(Double.NaN).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("1234567890123456", ((StringLiteral) (new DoubleLiteral(1234567890123456.12345).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("1.234567890123457e+16", ((StringLiteral) (new DoubleLiteral(12345678901234567.12345).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("0.0001234567890123457", ((StringLiteral) (new DoubleLiteral(0.0001234567890123456789).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("1.2345678901234568e+16", ((StringLiteral) (new DoubleLiteral(12345678901234567.12345).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("0.00012345678901234567", ((StringLiteral) (new DoubleLiteral(0.0001234567890123456789).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("1.234567890123456e-15", ((StringLiteral) (new DoubleLiteral(0.000000000000001234567890123456).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("123.456", ((StringLiteral) (new DoubleLiteral(123.456000).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("123", ((StringLiteral) (new DoubleLiteral(123.000).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("-1234567890123456", ((StringLiteral) (new DoubleLiteral(-1234567890123456.12345).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("-1.234567890123457e+16", ((StringLiteral) (new DoubleLiteral(-12345678901234567.12345).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("-0.0001234567890123457", ((StringLiteral) (new DoubleLiteral(-0.0001234567890123456789).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("-1.2345678901234568e+16", ((StringLiteral) (new DoubleLiteral(-12345678901234567.12345).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("-0.00012345678901234567", ((StringLiteral) (new DoubleLiteral(-0.0001234567890123456789).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("-1.234567890123456e-15", ((StringLiteral) (new DoubleLiteral(-0.000000000000001234567890123456).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("-123.456", ((StringLiteral) (new DoubleLiteral(-123.456000).uncheckedCastTo(StringType.INSTANCE))).getValue());
Assertions.assertEquals("-123", ((StringLiteral) (new DoubleLiteral(-123.000).uncheckedCastTo(StringType.INSTANCE))).getValue());
Expand Down
Loading
Loading