This is an automated email from the ASF dual-hosted git repository.

csun5285 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 2e1d44a6b8f [fix](storage) fix DOUBLE/FLOAT zone-map  (#65302)
2e1d44a6b8f is described below

commit 2e1d44a6b8faccd6b903be871a27b98147b571d7
Author: Chenyang Sun <[email protected]>
AuthorDate: Wed Jul 15 16:40:49 2026 +0800

    [fix](storage) fix DOUBLE/FLOAT zone-map  (#65302)
    
    
    Zone-map DOUBLE/FLOAT min/max were formatted with digits10+1 (16g/7g,
    #54760), which fails to round-trip finite extremes — ±DBL_MAX overflows
    to ±inf on readback, ±FLT_MAX loses precision — corrupting the zone map.
---
 .../data_type_serde/data_type_number_serde.cpp     |  16 +-
 .../data_type_serde_number_test.cpp                |  51 ++
 be/test/storage/olap_type_test.cpp                 | 518 ++++++---------------
 be/test/storage/segment/zone_map_index_test.cpp    |  80 +++-
 4 files changed, 279 insertions(+), 386 deletions(-)

diff --git a/be/src/core/data_type_serde/data_type_number_serde.cpp 
b/be/src/core/data_type_serde/data_type_number_serde.cpp
index 3900f25a6d6..55b156b01f9 100644
--- a/be/src/core/data_type_serde/data_type_number_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_number_serde.cpp
@@ -978,8 +978,8 @@ Status DataTypeNumberSerDe<T>::from_string(StringRef& str, 
IColumn& column,
 // Format by type:
 //   - BOOLEAN:  "0" or "1" (via snprintf "%d")
 //   - TINYINT/SMALLINT/INT/BIGINT: standard integer string, e.g. "42", "-100"
-//   - FLOAT:    fmt::format("{:.7g}", value), e.g. "3.14", "NaN", "Infinity"
-//   - DOUBLE:   fmt::format("{:.16g}", value), e.g. "3.141592653589793"
+//   - FLOAT:    fmt::format("{}", value) for finite values, e.g. "3.14"
+//   - DOUBLE:   fmt::format("{}", value) for finite values, e.g. 
"3.141592653589793"
 //   - LARGEINT: fmt::format("{}", value), e.g. 
"170141183460469231731687303715884105727"
 //
 // Examples:
@@ -992,8 +992,18 @@ std::string DataTypeNumberSerDe<T>::to_olap_string(const 
Field& field) const {
         char buf[8] = {'\0'};
         snprintf(buf, sizeof(buf), "%d", field.get<T>());
         return std::string(buf);
+    } else if constexpr (T == TYPE_FLOAT || T == TYPE_DOUBLE) {
+        auto v = field.get<T>();
+        // inf/nan are stored as zone-map flags, not in min/max strings; route 
them
+        // through CastToString to keep the "Infinity"/"NaN" spelling used 
elsewhere.
+        if (std::isinf(v) || std::isnan(v)) {
+            return CastToString::from_number(v);
+        }
+        // CastToString uses digits10 + 1 significant digits, which may lose 
precision.
+        // ZoneMap bounds must round-trip exactly, so use fmt's shortest 
round-trippable form.
+        return fmt::format("{}", v);
     } else if constexpr (T == TYPE_TINYINT || T == TYPE_SMALLINT || T == 
TYPE_INT ||
-                         T == TYPE_BIGINT || T == TYPE_FLOAT || T == 
TYPE_DOUBLE) {
+                         T == TYPE_BIGINT) {
         return CastToString::from_number(field.get<T>());
     } else if constexpr (T == TYPE_LARGEINT) {
         auto value = field.get<T>();
diff --git a/be/test/core/data_type_serde/data_type_serde_number_test.cpp 
b/be/test/core/data_type_serde/data_type_serde_number_test.cpp
index dc9099214b5..b1050d7119b 100644
--- a/be/test/core/data_type_serde/data_type_serde_number_test.cpp
+++ b/be/test/core/data_type_serde/data_type_serde_number_test.cpp
@@ -29,6 +29,7 @@
 #include <type_traits>
 
 #include "common/config.h"
+#include "common/status.h"
 #include "core/assert_cast.h"
 #include "core/column/column.h"
 #include "core/data_type/common_data_type_serder_test.h"
@@ -38,6 +39,7 @@
 #include "core/data_type_serde/data_type_date_or_datetime_serde.h"
 #include "core/data_type_serde/data_type_datetimev2_serde.h"
 #include "core/data_type_serde/data_type_datev2_serde.h"
+#include "core/field.h"
 #include "core/types.h"
 #include "testutil/test_util.h"
 #include "util/slice.h"
@@ -618,4 +620,53 @@ TEST_F(DataTypeNumberSerDeTest, 
RowStoreDateTimeJsonbWidth) {
             });
 }
 
+// to_olap_string / from_zonemap_string must round-trip finite floating-point
+// extremes (±DBL_MAX / ±FLT_MAX). The old digits10+1 (16g/7g) formatter 
rounded
+// DBL_MAX up to 1.797693134862316e+308 — larger than the largest finite 
double —
+// so from_olap_string parsed it to ±inf and rejected it, and the zone-map 
min/max
+// never got materialized.
+TEST_F(DataTypeNumberSerDeTest, OlapStringRoundTripFloatExtremes) {
+    auto check_double = [&](double v) {
+        Field field = Field::create_field<TYPE_DOUBLE>(v);
+        std::string s = serde_float64->to_olap_string(field);
+        Field back;
+        Status st = serde_float64->from_zonemap_string(s, back);
+        ASSERT_TRUE(st.ok()) << "double round-trip failed: v=" << v << " 
str='" << s << "'";
+        EXPECT_EQ(back.get<TYPE_DOUBLE>(), v) << "str='" << s << "'";
+    };
+    auto check_float = [&](float v) {
+        Field field = Field::create_field<TYPE_FLOAT>(v);
+        std::string s = serde_float32->to_olap_string(field);
+        Field back;
+        Status st = serde_float32->from_zonemap_string(s, back);
+        ASSERT_TRUE(st.ok()) << "float round-trip failed: v=" << v << " str='" 
<< s << "'";
+        EXPECT_EQ(back.get<TYPE_FLOAT>(), v) << "str='" << s << "'";
+    };
+
+    // finite extremes — these broke before the fix
+    check_double(std::numeric_limits<double>::max());    // 
1.7976931348623157e308
+    check_double(std::numeric_limits<double>::lowest()); // 
-1.7976931348623157e308
+    check_float(std::numeric_limits<float>::max());
+    check_float(std::numeric_limits<float>::lowest());
+
+    // neighbors of extremes + precision-sensitive values — also broke before
+    // the fix (digits10+1 is short of max_digits10)
+    check_double(std::nextafter(std::numeric_limits<double>::max(),
+                                0.0));                // 1 ULP below DBL_MAX, 
16g -> inf
+    check_double(std::numeric_limits<double>::min()); // smallest normal, 16g 
loses precision
+    check_double(2.0000000164243876);                 // ordinary double 
needing 17 sig digits
+    check_float(std::nextafter(std::numeric_limits<float>::max(), 0.0f)); // 1 
ULP below FLT_MAX
+    check_float(std::numeric_limits<float>::min());                       // 
smallest normal float
+
+    // ordinary values must still round-trip exactly
+    check_double(0.0);
+    check_double(-0.0);
+    check_double(1.0);
+    check_double(3.141592653589793);
+    check_double(1e300);
+    check_double(-1e308);
+    check_float(3.14f);
+    check_float(0.0f);
+}
+
 } // namespace doris
diff --git a/be/test/storage/olap_type_test.cpp 
b/be/test/storage/olap_type_test.cpp
index 741f75bf47b..3f456b47e68 100644
--- a/be/test/storage/olap_type_test.cpp
+++ b/be/test/storage/olap_type_test.cpp
@@ -240,160 +240,53 @@ TEST_F(OlapTypeTest, deser_double_old) {
     input_file.close();
 }
 TEST_F(OlapTypeTest, ser_deser_float) {
+    // to_olap_string uses the shortest round-trippable form; the expected 
strings
+    // below are that shortest representation for each value.
     std::vector<std::pair<float, std::string>> normal_input_values = {
-            {
-                    1.230F,
-                    "1.23", // trailing zero of fractional part is not printed
-            },
-            {
-                    123.456000F,
-                    "123.456",
-            },
-            {
-                    123.000F,
-                    "123", // decimal point is printed if fractional part is 
zero
-            },
-            {
-                    1234567.000F,
-                    "1234567",
-            },
-            // 7 > e >= -4: decimal format.
-            // e < -4 or e >= 7: scientific format.
-            {
-                    123456.12345F,
-                    "123456.1",
-            },
-            {
-                    1234567.12345F,
-                    "1234567",
-            },
-            {
-                    12345678.12345F,
-                    "1.234568e+07",
-            },
-            {
-                    123456789.12345F,
-                    "1.234568e+08",
-            },
-            {
-                    1234567890000.12345F,
-                    "1.234568e+12",
-            },
-            {
-                    0.33F,
-                    "0.33",
-            },
-            {
-                    123.456F,
-                    "123.456",
-            },
-            {
-                    123.456789F,
-                    "123.4568",
-            },
-            {
-                    123.456789123F,
-                    "123.4568",
-            },
-            {
-                    123456.123456789F,
-                    "123456.1",
-            },
-            {
-                    1234567.123456789F,
-                    "1234567",
-            },
-            {
-                    987654336.0F,
-                    "9.876543e+08",
-            },
-            {
-                    16777216.0F,
-                    "1.677722e+07",
-            },
-            {
-                    0.000123456F,
-                    "0.000123456",
-            },
-            {
-                    0.0001234567F,
-                    "0.0001234567",
-            },
-            {
-                    0.00012345678F,
-                    "0.0001234568",
-            },
-            {
-                    0.0000123456F,
-                    "1.23456e-05", // e < -4
-            },
-            {
-                    0.00001234567F,
-                    "1.234567e-05", // e < -4
-            },
-            {
-                    0.0000123456789F,
-                    "1.234568e-05",
-            },
-            {
-                    0.000000000000001234567F,
-                    "1.234567e-15",
-            },
-            {
-                    0.000000000000001234567890123456F,
-                    "1.234568e-15",
-            },
-            {
-                    0.1234567F,
-                    "0.1234567",
-            },
-            {
-                    0.123456789F,
-                    "0.1234568",
-            },
-
-            {
-                    1234567890123456.12345F,
-                    "1.234568e+15",
-            },
-            {
-                    12345678901234567.12345F,
-                    "1.234568e+16",
-            }};
-    for (int i = 1; i < 10; ++i) {
-        normal_input_values.emplace_back(i * 0.0000001F, fmt::format("{}e-07", 
i));
-        normal_input_values.emplace_back(i * 0.000000001F, 
fmt::format("{}e-09", i));
-    }
+            {1.230F, "1.23"},
+            {123.456000F, "123.456"},
+            {123.000F, "123"},
+            {1234567.000F, "1234567"},
+            {123456.12345F, "123456.125"},
+            {1234567.12345F, "1234567.1"},
+            {12345678.12345F, "12345678"},
+            {123456789.12345F, "123456790"},
+            {1234567890000.12345F, "1234568000000"},
+            {0.33F, "0.33"},
+            {123.456F, "123.456"},
+            {123.456789F, "123.45679"},
+            {123.456789123F, "123.45679"},
+            {123456.123456789F, "123456.125"},
+            {1234567.123456789F, "1234567.1"},
+            {987654336.0F, "987654340"},
+            {16777216.0F, "16777216"},
+            {0.000123456F, "0.000123456"},
+            {0.0001234567F, "0.0001234567"},
+            {0.00012345678F, "0.00012345678"},
+            {0.0000123456F, "1.23456e-05"},
+            {0.00001234567F, "1.234567e-05"},
+            {0.0000123456789F, "1.2345679e-05"},
+            {0.000000000000001234567F, "1.234567e-15"},
+            {0.000000000000001234567890123456F, "1.2345679e-15"},
+            {0.1234567F, "0.1234567"},
+            {0.123456789F, "0.12345679"},
+            {1234567890123456.12345F, "1234568000000000"},
+            {12345678901234567.12345F, "1.2345678e+16"}};
     std::vector<std::pair<float, std::string>> test_input_values;
     for (const auto& [float_value, expected_str] : normal_input_values) {
         test_input_values.emplace_back(float_value, expected_str);
         test_input_values.emplace_back(-float_value, fmt::format("-{}", 
expected_str));
     }
     std::vector<std::pair<float, std::string>> special_input_values = {
-            {
-                    0.0,
-                    "0",
-            },
-            {
-                    -0.0,
-                    "-0",
-            },
-            {std::numeric_limits<float>::min(), "1.175494e-38"},
-            {std::numeric_limits<float>::lowest(), "-3.402823e+38"},
-            {std::numeric_limits<float>::denorm_min(), "1.401298e-45"},
-            {std::numeric_limits<float>::max(), "3.402823e+38"},
-            {
-                    std::numeric_limits<float>::infinity(),
-                    "Infinity",
-            },
-            {
-                    -std::numeric_limits<float>::infinity(),
-                    "-Infinity",
-            },
-            {
-                    std::numeric_limits<float>::quiet_NaN(),
-                    "NaN",
-            }};
+            {0.0, "0"},
+            {-0.0, "-0"},
+            {std::numeric_limits<float>::min(), "1.1754944e-38"},
+            {std::numeric_limits<float>::lowest(), "-3.4028235e+38"},
+            {std::numeric_limits<float>::denorm_min(), "1e-45"},
+            {std::numeric_limits<float>::max(), "3.4028235e+38"},
+            {std::numeric_limits<float>::infinity(), "Infinity"},
+            {-std::numeric_limits<float>::infinity(), "-Infinity"},
+            {std::numeric_limits<float>::quiet_NaN(), "NaN"}};
     test_input_values.insert(test_input_values.end(), 
special_input_values.begin(),
                              special_input_values.end());
     auto data_type_ptr = 
DataTypeFactory::instance().create_data_type(TYPE_FLOAT, false);
@@ -401,7 +294,8 @@ TEST_F(OlapTypeTest, ser_deser_float) {
     for (const auto& [float_value, expected_str] : test_input_values) {
         auto field = Field::create_field<TYPE_FLOAT>(float_value);
         auto result_str = data_type_serde->to_olap_string(field);
-        EXPECT_EQ(result_str, expected_str);
+        EXPECT_EQ(result_str, expected_str)
+                << "Float to_olap_string mismatch for " << 
fmt::format("{:.9g}", float_value);
         Field restored_field;
         auto status = data_type_serde->from_fe_string(result_str, 
restored_field);
         // from_fe_string rejects NaN/Infinity strings
@@ -410,193 +304,67 @@ TEST_F(OlapTypeTest, ser_deser_float) {
             continue;
         }
         EXPECT_TRUE(status.ok()) << status.to_string();
-        float deser_float_value = restored_field.get<TYPE_FLOAT>();
-        float diff_ratio = std::abs(deser_float_value - float_value) / 
abs(float_value);
-        EXPECT_TRUE((float_value == 0 && deser_float_value == 0) || diff_ratio 
< 1e-6)
-                << "expected float value: " << fmt::format("{:.9g}", 
float_value)
-                << ", expected float str: " << expected_str
-                << ", deser float value: " << fmt::format("{:.9g}", 
deser_float_value)
-                << ", diff_ratio: " << fmt::format("{:.9g}", diff_ratio);
+        // Shortest form round-trips exactly.
+        EXPECT_EQ(restored_field.get<TYPE_FLOAT>(), float_value)
+                << "Float round-trip mismatch for '" << result_str << "'";
     }
 }
 TEST_F(OlapTypeTest, ser_deser_double) {
+    // to_olap_string uses the shortest round-trippable form; the expected 
strings
+    // below are that shortest representation for each value.
     std::vector<std::pair<double, std::string>> normal_input_values = {
-            {
-                    1.230,
-                    "1.23", // trailing zero of fractional part is not printed
-            },
-            {
-                    123.456000,
-                    "123.456",
-            },
-            {
-                    123.000,
-                    "123", // decimal point is printed if fractional part is 
zero
-            },
-            {
-                    1234567.000,
-                    "1234567",
-            },
-            // 16 > e >= -4: decimal format.
-            // e < -4 or e >= 16: scientific format.
-            {
-                    123456.12345,
-                    "123456.12345",
-            },
-            {
-                    1234567.12345,
-                    "1234567.12345",
-            },
-            {
-                    12345678.12345,
-                    "12345678.12345",
-            },
-            {
-                    123456789.12345,
-                    "123456789.12345",
-            },
-            {
-                    1234567890000.12345,
-                    "1234567890000.124",
-            },
-            {
-                    0.33,
-                    "0.33",
-            },
-            {
-                    123.456,
-                    "123.456",
-            },
-            {
-                    123.456789,
-                    "123.456789",
-            },
-            {
-                    123.456789123,
-                    "123.456789123",
-            },
-            {
-                    123456.123456789,
-                    "123456.123456789",
-            },
-            {
-                    1234567.123456789,
-                    "1234567.123456789",
-            },
-            {
-                    987654336.0,
-                    "987654336",
-            },
-            {
-                    16777216.0,
-                    "16777216",
-            },
-            {
-                    0.000123456,
-                    "0.000123456",
-            },
-            {
-                    0.0001234567,
-                    "0.0001234567",
-            },
-            {
-                    0.00012345678,
-                    "0.00012345678",
-            },
-            {
-                    0.0000123456,
-                    "1.23456e-05", // e < -4
-            },
-            {
-                    0.00001234567,
-                    "1.234567e-05", // e < -4
-            },
-            {
-                    0.0000123456789,
-                    "1.23456789e-05",
-            },
-            {
-                    0.000000000000001234567,
-                    "1.234567e-15",
-            },
-            {
-                    0.000000000000001234567890123456,
-                    "1.234567890123456e-15",
-            },
-            {
-                    0.1234567,
-                    "0.1234567",
-            },
-            {
-                    0.123456789,
-                    "0.123456789",
-            },
-
-            {
-                    1234567890123456.12345,
-                    "1234567890123456",
-            },
-            {
-                    12345678901234567.12345,
-                    "1.234567890123457e+16",
-            },
-            {
-                    123456789012345678.12345,
-                    "1.234567890123457e+17",
-            }};
-    for (int i = 1; i < 10; ++i) {
-        if (i == 7) {
-            normal_input_values.emplace_back(i * 0.0000000000000001, 
"6.999999999999999e-16");
-            continue;
-        }
-        normal_input_values.emplace_back(i * 0.0000000000000001, 
fmt::format("{}e-16", i));
-    }
+            {1.230, "1.23"},
+            {123.456000, "123.456"},
+            {123.000, "123"},
+            {1234567.000, "1234567"},
+            {123456.12345, "123456.12345"},
+            {1234567.12345, "1234567.12345"},
+            {12345678.12345, "12345678.12345"},
+            {123456789.12345, "123456789.12345"},
+            {1234567890000.12345, "1234567890000.1235"},
+            {0.33, "0.33"},
+            {123.456, "123.456"},
+            {123.456789, "123.456789"},
+            {123.456789123, "123.456789123"},
+            {123456.123456789, "123456.123456789"},
+            {1234567.123456789, "1234567.123456789"},
+            {987654336.0, "987654336"},
+            {16777216.0, "16777216"},
+            {0.000123456, "0.000123456"},
+            {0.0001234567, "0.0001234567"},
+            {0.00012345678, "0.00012345678"},
+            {0.0000123456, "1.23456e-05"},
+            {0.00001234567, "1.234567e-05"},
+            {0.0000123456789, "1.23456789e-05"},
+            {0.000000000000001234567, "1.234567e-15"},
+            {0.000000000000001234567890123456, "1.234567890123456e-15"},
+            {0.1234567, "0.1234567"},
+            {0.123456789, "0.123456789"},
+            {1234567890123456.12345, "1234567890123456"},
+            {12345678901234567.12345, "1.2345678901234568e+16"},
+            {123456789012345678.12345, "1.2345678901234568e+17"}};
     std::vector<std::pair<double, std::string>> test_input_values;
     for (const auto& [float_value, expected_str] : normal_input_values) {
         test_input_values.emplace_back(float_value, expected_str);
         test_input_values.emplace_back(-float_value, fmt::format("-{}", 
expected_str));
     }
     std::vector<std::pair<double, std::string>> special_input_values = {
-            {
-                    0.0,
-                    "0",
-            },
-            {
-                    -0.0,
-                    "-0",
-            },
-            {std::numeric_limits<float>::min(), "1.175494350822288e-38"},
-            {std::numeric_limits<float>::lowest(), "-3.402823466385289e+38"},
+            {0.0, "0"},
+            {-0.0, "-0"},
+            {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<double>::infinity(),
-                    "Infinity",
-            },
-            {
-                    -std::numeric_limits<double>::infinity(),
-                    "-Infinity",
-            },
-            {
-                    std::numeric_limits<double>::quiet_NaN(),
-                    "NaN",
-            },
-            {
-                    std::numeric_limits<float>::infinity(),
-                    "Infinity",
-            },
-            {
-                    -std::numeric_limits<float>::infinity(),
-                    "-Infinity",
-            },
-            {
-                    std::numeric_limits<float>::quiet_NaN(),
-                    "NaN",
-            }};
+            {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"},
+            {-std::numeric_limits<double>::infinity(), "-Infinity"},
+            {std::numeric_limits<double>::quiet_NaN(), "NaN"},
+            {std::numeric_limits<float>::infinity(), "Infinity"},
+            {-std::numeric_limits<float>::infinity(), "-Infinity"},
+            {std::numeric_limits<float>::quiet_NaN(), "NaN"}};
     test_input_values.insert(test_input_values.end(), 
special_input_values.begin(),
                              special_input_values.end());
     auto data_type_ptr = 
DataTypeFactory::instance().create_data_type(TYPE_DOUBLE, false);
@@ -604,25 +372,19 @@ TEST_F(OlapTypeTest, ser_deser_double) {
     for (const auto& [float_value, expected_str] : test_input_values) {
         auto field = Field::create_field<TYPE_DOUBLE>(float_value);
         auto result_str = data_type_serde->to_olap_string(field);
-        EXPECT_EQ(result_str, expected_str);
+        EXPECT_EQ(result_str, expected_str)
+                << "Double to_olap_string mismatch for " << 
fmt::format("{:.17g}", float_value);
         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;
         }
         EXPECT_TRUE(status.ok()) << status.to_string();
-        double deser_float_value = restored_field.get<TYPE_DOUBLE>();
-        double diff_ratio = std::abs(deser_float_value - float_value) / 
abs(float_value);
-        EXPECT_TRUE((float_value == 0 && deser_float_value == 0) || diff_ratio 
< 1e-15)
-                << "expected double value: " << fmt::format("{:.17g}", 
float_value)
-                << ", expected double str: " << expected_str
-                << ", deser double value: " << fmt::format("{:.17g}", 
deser_float_value)
-                << ", diff_ratio: " << fmt::format("{:.17g}", diff_ratio);
+        // Shortest form round-trips exactly.
+        EXPECT_EQ(restored_field.get<TYPE_DOUBLE>(), float_value)
+                << "Double round-trip mismatch for '" << result_str << "'";
     }
 }
 
@@ -973,8 +735,7 @@ TEST_F(OlapTypeTest, ser_deser_float_olap_string) {
         auto status = serde->from_zonemap_string(result_str, restored_field);
         EXPECT_TRUE(status.ok()) << status.to_string();
         float restored_val = restored_field.get<TYPE_FLOAT>();
-        float diff = std::abs(restored_val - val);
-        EXPECT_TRUE(val == 0 ? restored_val == 0 : diff / std::abs(val) < 1e-6)
+        EXPECT_EQ(restored_val, val)
                 << "Float round-trip: expected " << val << ", got " << 
restored_val;
     }
 
@@ -1007,12 +768,9 @@ TEST_F(OlapTypeTest, ser_deser_float_olap_string) {
 }
 
 // ---------------------------------------------------------------------------
-// Double: same pattern as Float.
-//   The expected strings in this case follow current serializer behavior.
-//   Note: for DBL_MAX/lowest, current formatting rounds to a boundary string 
that
-//   is rejected by from_zonemap_string (parsed as Infinity), so these two 
values
-//   are validated for to_olap_string only.
-//   NaN/Inf same behavior: to_olap_string works, from_zonemap_string rejects.
+// Double: same pattern as Float. The shortest form round-trips every finite
+// value, including DBL_MAX/lowest. NaN/Inf are tracked via zone-map flags, so
+// to_olap_string emits them but from_zonemap_string rejects the string.
 // ---------------------------------------------------------------------------
 TEST_F(OlapTypeTest, ser_deser_double_olap_string) {
     auto data_type_ptr = 
DataTypeFactory::instance().create_data_type(TYPE_DOUBLE, false);
@@ -1026,8 +784,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) {
@@ -1039,19 +797,9 @@ 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);
-        EXPECT_TRUE(val == 0 ? restored_val == 0 : diff / std::abs(val) < 
1e-15)
+        EXPECT_EQ(restored_val, val)
                 << "Double round-trip: expected " << val << ", got " << 
restored_val;
     }
 
@@ -1794,18 +1542,31 @@ TEST_F(OlapTypeTest, float_type) {
             {std::numeric_limits<float>::quiet_NaN(), "NaN", "NaN"},
             {std::numeric_limits<float>::infinity(), "Infinity", "Infinity"},
             {-std::numeric_limits<float>::infinity(), "-Infinity", 
"-Infinity"},
-            {std::numeric_limits<float>::max(), "3.402823e+38", 
"3.402823e+38"},
-            {std::numeric_limits<float>::lowest(), "-3.402823e+38", 
"-3.402823e+38"},
-            {std::numeric_limits<float>::min(), "1.175494e-38", 
"1.175494e-38"},
-            {std::numeric_limits<float>::denorm_min(), "1.401298e-45", 
"1.401298e-45"},
+            {std::numeric_limits<float>::max(), "3.4028235e+38", 
"3.4028235e+38"},
+            {std::numeric_limits<float>::lowest(), "-3.4028235e+38", 
"-3.4028235e+38"},
+            {std::numeric_limits<float>::min(), "1.1754944e-38", 
"1.1754944e-38"},
+            {std::numeric_limits<float>::denorm_min(), "1e-45", "1e-45"},
     };
 
     for (auto& tc : test_cases) {
         auto field = Field::create_field<TYPE_FLOAT>(tc.value);
         std::string serde_str = serde->to_olap_string(field);
 
-        EXPECT_EQ(tc.expected_serde, serde_str)
-                << "serde mismatch for FLOAT expected='" << tc.expected << "'";
+        // to_olap_string uses the shortest round-trippable form.
+        EXPECT_EQ(tc.expected_serde, serde_str) << "serde mismatch for FLOAT=" 
<< tc.expected;
+
+        // It must also round-trip back to the same value via 
from_zonemap_string.
+        Field rf;
+        auto st = serde->from_zonemap_string(serde_str, rf);
+        if (std::isfinite(tc.value)) {
+            EXPECT_TRUE(st.ok()) << "FLOAT to_olap_string='" << serde_str
+                                 << "': " << st.to_string();
+            EXPECT_EQ(rf.get<TYPE_FLOAT>(), tc.value)
+                    << "FLOAT round-trip mismatch, serde='" << serde_str << 
"'";
+        } else {
+            // NaN/Inf are tracked via zone-map flags; from_zonemap_string 
rejects the string.
+            EXPECT_FALSE(st.ok()) << "FLOAT to_olap_string='" << serde_str << 
"'";
+        }
     }
 }
 
@@ -1832,20 +1593,33 @@ 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) {
         auto field = Field::create_field<TYPE_DOUBLE>(tc.value);
         std::string serde_str = serde->to_olap_string(field);
 
-        EXPECT_EQ(tc.expected_serde, serde_str)
-                << "serde mismatch for DOUBLE expected='" << tc.expected << 
"'";
+        // to_olap_string uses the shortest round-trippable form.
+        EXPECT_EQ(tc.expected_serde, serde_str) << "serde mismatch for 
DOUBLE=" << tc.expected;
+
+        // It must also round-trip back to the same value via 
from_zonemap_string.
+        Field rf;
+        auto st = serde->from_zonemap_string(serde_str, rf);
+        if (std::isfinite(tc.value)) {
+            EXPECT_TRUE(st.ok()) << "DOUBLE to_olap_string='" << serde_str
+                                 << "': " << st.to_string();
+            EXPECT_EQ(rf.get<TYPE_DOUBLE>(), tc.value)
+                    << "DOUBLE round-trip mismatch, serde='" << serde_str << 
"'";
+        } else {
+            // NaN/Inf are tracked via zone-map flags; from_zonemap_string 
rejects the string.
+            EXPECT_FALSE(st.ok()) << "DOUBLE to_olap_string='" << serde_str << 
"'";
+        }
     }
 }
 
diff --git a/be/test/storage/segment/zone_map_index_test.cpp 
b/be/test/storage/segment/zone_map_index_test.cpp
index 51f8c068947..db4b692b1f1 100644
--- a/be/test/storage/segment/zone_map_index_test.cpp
+++ b/be/test/storage/segment/zone_map_index_test.cpp
@@ -762,9 +762,8 @@ TEST_F(ColumnZoneMapTest, NormalTestFloatPage) {
     EXPECT_TRUE(fs->open_file(filename, &file_reader).ok());
 
     auto segment_zone_map = index_meta.zone_map_index().segment_zone_map();
-    EXPECT_EQ(CastToString::from_number(std::numeric_limits<float>::lowest()),
-              segment_zone_map.min());
-    EXPECT_EQ(CastToString::from_number(std::numeric_limits<float>::max()), 
segment_zone_map.max());
+    EXPECT_EQ("-3.4028235e+38", segment_zone_map.min());
+    EXPECT_EQ("3.4028235e+38", segment_zone_map.max());
     EXPECT_EQ(true, segment_zone_map.has_null());
     EXPECT_EQ(true, segment_zone_map.has_not_null());
     EXPECT_EQ(true, segment_zone_map.has_positive_inf());
@@ -778,8 +777,8 @@ TEST_F(ColumnZoneMapTest, NormalTestFloatPage) {
     const std::vector<ZoneMapPB>& zone_maps = column_zone_map.page_zone_maps();
     EXPECT_EQ(3, zone_maps.size());
 
-    EXPECT_EQ(CastToString::from_number(std::numeric_limits<float>::lowest()), 
zone_maps[0].min());
-    EXPECT_EQ(CastToString::from_number(std::numeric_limits<float>::max()), 
zone_maps[0].max());
+    EXPECT_EQ("-3.4028235e+38", zone_maps[0].min());
+    EXPECT_EQ("3.4028235e+38", zone_maps[0].max());
     EXPECT_EQ(false, zone_maps[0].has_null());
     EXPECT_EQ(true, zone_maps[0].has_not_null());
     EXPECT_EQ(true, zone_maps[0].has_positive_inf());
@@ -851,10 +850,8 @@ TEST_F(ColumnZoneMapTest, NormalTestDoublePage) {
     EXPECT_TRUE(fs->open_file(filename, &file_reader).ok());
 
     auto segment_zone_map = index_meta.zone_map_index().segment_zone_map();
-    EXPECT_EQ(CastToString::from_number(std::numeric_limits<double>::lowest()),
-              segment_zone_map.min());
-    EXPECT_EQ(CastToString::from_number(std::numeric_limits<double>::max()),
-              segment_zone_map.max());
+    EXPECT_EQ("-1.7976931348623157e+308", segment_zone_map.min());
+    EXPECT_EQ("1.7976931348623157e+308", segment_zone_map.max());
     EXPECT_EQ(true, segment_zone_map.has_null());
     EXPECT_EQ(true, segment_zone_map.has_not_null());
     EXPECT_EQ(true, segment_zone_map.has_positive_inf());
@@ -868,8 +865,8 @@ TEST_F(ColumnZoneMapTest, NormalTestDoublePage) {
     const std::vector<ZoneMapPB>& zone_maps = column_zone_map.page_zone_maps();
     EXPECT_EQ(3, zone_maps.size());
 
-    
EXPECT_EQ(CastToString::from_number(std::numeric_limits<double>::lowest()), 
zone_maps[0].min());
-    EXPECT_EQ(CastToString::from_number(std::numeric_limits<double>::max()), 
zone_maps[0].max());
+    EXPECT_EQ("-1.7976931348623157e+308", zone_maps[0].min());
+    EXPECT_EQ("1.7976931348623157e+308", zone_maps[0].max());
     EXPECT_EQ(false, zone_maps[0].has_null());
     EXPECT_EQ(true, zone_maps[0].has_not_null());
     EXPECT_EQ(true, zone_maps[0].has_positive_inf());
@@ -885,6 +882,67 @@ TEST_F(ColumnZoneMapTest, NormalTestDoublePage) {
     EXPECT_EQ(false, zone_maps[2].has_not_null());
 }
 
+// A DOUBLE page whose min/max are exactly ±DBL_MAX (with NO ±inf/NaN in the
+// page) must round-trip through to_proto/from_proto. When min/max formatting
+// used digits10+1 (16g), DBL_MAX serialized as "1.797693134862316e+308" — 
larger
+// than the largest finite double — so from_olap_string parsed it to ±inf,
+// rejected it, and ZoneMap::from_proto returned non-OK, leaving the segment
+// without a usable zone map (field_types_compatible CHECK / silent prune).
+TEST_F(ColumnZoneMapTest, DoubleFiniteExtremesRoundTrip) {
+    auto column = create_float_column<FieldType::OLAP_FIELD_TYPE_DOUBLE>(0, 
true);
+    const TabletColumn* field = &(*column);
+    auto data_type_ptr = 
DataTypeFactory::instance().create_data_type(TYPE_DOUBLE, false);
+
+    std::unique_ptr<ZoneMapIndexWriter> builder;
+    ASSERT_TRUE(ZoneMapIndexWriter::create(data_type_ptr, field, 
builder).ok());
+    // Page holds ONLY finite values (incl. ±DBL_MAX) — no ±inf/NaN — so the
+    // has_*_inf / has_nan flags stay false and from_proto must rebuild min/max
+    // by parsing the stored strings (exactly the path that broke).
+    double values[] = {
+            std::numeric_limits<double>::lowest(), // -DBL_MAX
+            std::numeric_limits<double>::max(),    // +DBL_MAX
+            -1e308,
+            0.0,
+            3.141592653589793,
+    };
+    for (double v : values) {
+        builder->add_values(reinterpret_cast<const uint8_t*>(&v), 1);
+    }
+    ASSERT_TRUE(builder->flush().ok());
+
+    std::string file_path = kTestDir + "/double_finite_extremes";
+    io::FileWriterPtr file_writer;
+    ASSERT_TRUE(_fs->create_file(file_path, &file_writer).ok());
+    ColumnIndexMetaPB index_meta;
+    ASSERT_TRUE(builder->finish(file_writer.get(), &index_meta).ok());
+    ASSERT_TRUE(file_writer->close().ok());
+
+    const auto& seg_zm = index_meta.zone_map_index().segment_zone_map();
+    EXPECT_TRUE(seg_zm.has_not_null());
+    EXPECT_FALSE(seg_zm.has_positive_inf());
+    EXPECT_FALSE(seg_zm.has_negative_inf());
+    EXPECT_FALSE(seg_zm.has_nan());
+
+    ZoneMap zm;
+    ASSERT_TRUE(ZoneMap::from_proto(seg_zm, data_type_ptr, zm).ok())
+            << "±DBL_MAX zone map failed to deserialize";
+    EXPECT_EQ(zm.min_value.get<TYPE_DOUBLE>(), 
std::numeric_limits<double>::lowest());
+    EXPECT_EQ(zm.max_value.get<TYPE_DOUBLE>(), 
std::numeric_limits<double>::max());
+    EXPECT_FALSE(zm.has_positive_inf);
+    EXPECT_FALSE(zm.has_negative_inf);
+    EXPECT_FALSE(zm.has_nan);
+
+    io::FileReaderSPtr file_reader;
+    ASSERT_TRUE(_fs->open_file(file_path, &file_reader).ok());
+    ZoneMapIndexReader reader(file_reader, 
index_meta.zone_map_index().page_zone_maps());
+    ASSERT_TRUE(reader.load(true, false).ok());
+    ASSERT_EQ(reader.num_pages(), 1);
+    ZoneMap pzm;
+    ASSERT_TRUE(ZoneMap::from_proto(reader.page_zone_maps()[0], data_type_ptr, 
pzm).ok());
+    EXPECT_EQ(pzm.min_value.get<TYPE_DOUBLE>(), 
std::numeric_limits<double>::lowest());
+    EXPECT_EQ(pzm.max_value.get<TYPE_DOUBLE>(), 
std::numeric_limits<double>::max());
+}
+
 TabletColumnPtr create_timestamptz_column(int32_t id, bool is_nullable) {
     auto column = std::make_shared<TabletColumn>();
     column->_unique_id = id;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to