From 4cafacf90b6dfbed3d998e59fe3afd645d0b107c Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Wed, 20 Mar 2024 14:15:40 -0700 Subject: [PATCH] fix ZAM "cat" of doubles/times to include trailing ".0" per normal BiF behavior --- src/Desc.cc | 7 +------ src/Val.cc | 9 ++------- src/script_opt/ZAM/BuiltInSupport.cc | 12 ++++++++++-- src/util.cc | 26 ++++++++++++++++++++++++++ src/util.h | 9 +++++++++ 5 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/Desc.cc b/src/Desc.cc index 27660dce97..58b8d4d294 100644 --- a/src/Desc.cc +++ b/src/Desc.cc @@ -143,12 +143,7 @@ void ODesc::Add(double d, bool no_exp) { Add(tmp); - auto approx_equal = [](double a, double b, double tolerance = 1e-6) -> bool { - auto v = a - b; - return v < 0 ? -v < tolerance : v < tolerance; - }; - - if ( approx_equal(d, nearbyint(d), 1e-9) && std::isfinite(d) && ! strchr(tmp, 'e') ) + if ( util::approx_equal(d, nearbyint(d), 1e-9) && std::isfinite(d) && ! strchr(tmp, 'e') ) // disambiguate from integer Add(".0"); } diff --git a/src/Val.cc b/src/Val.cc index ac3fd5fd91..25778ba577 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -534,11 +534,6 @@ void IntervalVal::ValDescribe(ODesc* d) const { bool did_one = false; constexpr auto last_idx = units.size() - 1; - auto approx_equal = [](double a, double b, double tolerance = 1e-6) -> bool { - auto v = a - b; - return v < 0 ? -v < tolerance : v < tolerance; - }; - for ( size_t i = 0; i < units.size(); ++i ) { auto unit = units[i].first; auto word = units[i].second; @@ -547,7 +542,7 @@ void IntervalVal::ValDescribe(ODesc* d) const { if ( i == last_idx ) { to_print = v / unit; - if ( approx_equal(to_print, 0) ) { + if ( util::approx_equal(to_print, 0, 1e-6) ) { if ( ! did_one ) d->Add("0 secs"); @@ -571,7 +566,7 @@ void IntervalVal::ValDescribe(ODesc* d) const { d->SP(); d->Add(word); - if ( ! approx_equal(to_print, 1) && ! approx_equal(to_print, -1) ) + if ( ! util::approx_equal(to_print, 1, 1e-6) && ! util::approx_equal(to_print, -1, 1e-6) ) d->Add("s"); did_one = true; diff --git a/src/script_opt/ZAM/BuiltInSupport.cc b/src/script_opt/ZAM/BuiltInSupport.cc index 84903ac95a..4bc833814d 100644 --- a/src/script_opt/ZAM/BuiltInSupport.cc +++ b/src/script_opt/ZAM/BuiltInSupport.cc @@ -68,10 +68,18 @@ void FixedCatArg::RenderInto(ZVal* zframe, int slot, char*& res) { break; case TYPE_DOUBLE: - case TYPE_TIME: - n = modp_dtoa2(z.AsDouble(), res, 6); + case TYPE_TIME: { + auto d = z.AsDouble(); + n = modp_dtoa2(d, res, 6); res += n; + + if ( util::approx_equal(d, nearbyint(d), 1e-9) && std::isfinite(d) && ! strchr(tmp, 'e') ) { + // disambiguate from integer + *(res++) = '.'; + *(res++) = '0'; + } break; + } case TYPE_PATTERN: text = z.AsPattern()->AsPattern()->PatternText(); diff --git a/src/util.cc b/src/util.cc index 0b4fc1ef5f..f20fab0453 100644 --- a/src/util.cc +++ b/src/util.cc @@ -2550,6 +2550,32 @@ TEST_CASE("util split") { } } +TEST_CASE("util approx_equal") { + CHECK(approx_equal(47.0, 47.0) == true); + CHECK(approx_equal(47.0, -47.0) == false); + CHECK(approx_equal(47.00001, 47.00002) == false); + CHECK(approx_equal(47.00001, 47.00002, 1e-5) == true); + CHECK(approx_equal(47.0, -47.0, 1e2) == true); + CHECK(approx_equal(47.0, -47.0, 94 + 1e-10) == true); + CHECK(approx_equal(47.0, -47.0, 94) == false); + + constexpr auto inf = std::numeric_limits::infinity(); + CHECK_FALSE(approx_equal(inf, inf)); + CHECK_FALSE(approx_equal(-inf, inf)); + CHECK_FALSE(approx_equal(inf, -inf)); + CHECK_FALSE(approx_equal(inf, inf, inf)); + + constexpr auto qnan = std::numeric_limits::quiet_NaN(); // There's also `signaling_NaN`. + CHECK_FALSE(approx_equal(qnan, qnan)); + CHECK_FALSE(approx_equal(-qnan, qnan)); + CHECK_FALSE(approx_equal(qnan, -qnan)); +} + +/** + * Returns whether two double values are approximately equal within some tolerance value. + */ +bool approx_equal(double a, double b, double tolerance) { return std::abs(a - b) < std::abs(tolerance); } + } // namespace zeek::util extern "C" void out_of_memory(const char* where) { diff --git a/src/util.h b/src/util.h index 3a69293ad2..f261506310 100644 --- a/src/util.h +++ b/src/util.h @@ -571,6 +571,15 @@ std::string json_escape_utf8(const std::string& val, bool escape_printable_contr */ std::string json_escape_utf8(const char* val, size_t val_size, bool escape_printable_controls = true); +/** + * Checks for values that are approximately equal. + * @param a first value to compare + * @param b second value to compare + * @param tolerance how close they need to be to deem them "approximately equal" + * @return true if `a` is within the given tolerance of `b`, false otherwise + */ +bool approx_equal(double a, double b, double tolerance = std::numeric_limits::epsilon()); + /** * Splits a string at all occurrences of a delimiter. Successive occurrences * of the delimiter will be split into multiple pieces.