diff --git a/asl/base/float.hpp b/asl/base/float.hpp index 89a9f06..3699935 100644 --- a/asl/base/float.hpp +++ b/asl/base/float.hpp @@ -12,9 +12,36 @@ using float64_t = double; namespace asl { -template constexpr T infinity() { return __builtin_inf(); } +template struct float_traits {}; -template constexpr T nan() { return static_cast(__builtin_nanf("")); } +#define ASL_FLOAT_TRAITS(T, INF, NAN, EPS, SMALLEST) \ + template<> struct float_traits \ + { \ + static constexpr T kInfinity{__builtin_bit_cast(T, INF)}; \ + static constexpr T kNaN{__builtin_bit_cast(T, NAN)}; \ + static constexpr T kEpsilon{EPS}; \ + static constexpr T kSmallest{__builtin_bit_cast(T, SMALLEST)}; \ + }; + +ASL_FLOAT_TRAITS( + float32_t, + 0x7F800000, + 0x7FC00000, + __builtin_bit_cast(float32_t, 0x3F800001) - float32_t{1}, + 0x00800000 +); + +ASL_FLOAT_TRAITS( + float64_t, + 0x7FF0000000000000, + 0x7FF8000000000000, + __builtin_bit_cast(float64_t, 0x3FF0000000000001) - float64_t{1}, + 0x0010000000000000 +); + +template constexpr T infinity() { return float_traits::kInfinity; } + +template constexpr T nan() { return float_traits::kNaN; } template constexpr bool is_infinity(T f) { return __builtin_isinf(f); } diff --git a/asl/base/integers.hpp b/asl/base/integers.hpp index 6bc4c34..4e481dc 100644 --- a/asl/base/integers.hpp +++ b/asl/base/integers.hpp @@ -42,11 +42,11 @@ enum class byte : uint8_t {}; template struct integer_traits {}; -#define ASL_INTEGER_TRAITS(T, MIN, MAX) \ - template<> struct integer_traits \ - { \ - static constexpr T kMin = MIN; \ - static constexpr T kMax = MAX; \ +#define ASL_INTEGER_TRAITS(T, MIN, MAX) \ + template<> struct integer_traits \ + { \ + static constexpr T kMin{static_cast(MIN)}; \ + static constexpr T kMax{static_cast(MAX)}; \ } ASL_INTEGER_TRAITS(uint8_t, 0, 0xff); diff --git a/asl/base/numeric.hpp b/asl/base/numeric.hpp index 8d3b8ef..e99213c 100644 --- a/asl/base/numeric.hpp +++ b/asl/base/numeric.hpp @@ -5,6 +5,7 @@ #pragma once #include "asl/base/integers.hpp" +#include "asl/base/float.hpp" #include "asl/base/bit.hpp" #include "asl/base/meta.hpp" #include "asl/base/assert.hpp" @@ -54,5 +55,47 @@ constexpr T clamp(T x, T a, T b) return min(max(x, a), b); } +constexpr float32_t abs(float32_t x) { return __builtin_fabsf(x); } +constexpr float64_t abs(float64_t x) { return __builtin_fabs(x); } + +template +bool are_nearly_equal(T a, T b) +{ + // This is a fast path for identical values and correctly handles + // the case where +0.0 == -0.0. + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wfloat-equal" + if (a == b) { return true; } + #pragma clang diagnostic pop + + // NaNs are never equal to anything, including themselves. + if (is_nan(a) || is_nan(b)) { return false; } + + // Infinities are only equal if they are identical (which is handled by the + // `a == b` check above). If one is infinity and the other is not, they + // are not equal. The relative comparison below would fail with infinities. + if (is_infinity(a) || is_infinity(b)) { return false; } + + static constexpr T kMin = float_traits::kSmallest; + static constexpr T kEps = float_traits::kEpsilon; + + const T abs_a = abs(a); + const T abs_b = abs(b); + const T abs_diff = abs(a - b); + + // The relative error comparison (`|a-b| <= ε * max(|a|, |b|)`) breaks + // down when `a` and `b` are near zero. In this case, we switch to an + // absolute error comparison. `DBL_MIN` is the smallest positive + // normalized double, so it's a good threshold for this check. + if (abs_a < kMin || abs_b < kMin) + { + return abs_diff < kEps; + } + + // For all other cases, we use the standard relative error formula. + // The error is scaled by the magnitude of the numbers. + return abs_diff <= kEps * max(abs_a, abs_b); +} + } // namespace asl diff --git a/asl/containers/chunked_buffer_tests.cpp b/asl/containers/chunked_buffer_tests.cpp index ba29bfb..c3c72b2 100644 --- a/asl/containers/chunked_buffer_tests.cpp +++ b/asl/containers/chunked_buffer_tests.cpp @@ -155,8 +155,8 @@ ASL_TEST(pop) ASL_TEST(pop_destruct) { + bool d[3]{}; asl::chunked_buffer b; - bool d[3]; b.push(&d[0]); b.push(&d[1]); diff --git a/asl/formatting/format.hpp b/asl/formatting/format.hpp index 9323a57..54938b4 100644 --- a/asl/formatting/format.hpp +++ b/asl/formatting/format.hpp @@ -5,6 +5,7 @@ #pragma once #include "asl/base/integers.hpp" +#include "asl/base/float.hpp" #include "asl/base/meta.hpp" #include "asl/io/writer.hpp" #include "asl/types/span.hpp" @@ -93,8 +94,8 @@ inline void AslFormat(Formatter& f, string_view sv) f.write(sv); } -void AslFormat(Formatter& f, float); -void AslFormat(Formatter& f, double); +void AslFormat(Formatter& f, float32_t); +void AslFormat(Formatter& f, float64_t); void AslFormat(Formatter& f, bool); diff --git a/asl/formatting/format_float.cpp b/asl/formatting/format_float.cpp index 1d0375a..5c1baba 100644 --- a/asl/formatting/format_float.cpp +++ b/asl/formatting/format_float.cpp @@ -6,9 +6,12 @@ #include "asl/base/float.hpp" #include "asl/base/numeric.hpp" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" #define JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED 0 #define JKJ_STATIC_DATA_SECTION_DEFINED 0 #include +#pragma clang diagnostic pop static constexpr isize_t kZeroCount = 100; static constexpr char kZeros[kZeroCount] = { @@ -24,12 +27,12 @@ static constexpr char kZeros[kZeroCount] = { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', }; -static constexpr bool is_zero(float x) +static constexpr bool is_zero(float32_t x) { return (asl::bit_cast(x) & 0x7fff'ffffU) == 0; } -static constexpr bool is_zero(double x) +static constexpr bool is_zero(float64_t x) { return (asl::bit_cast(x) & 0x7fff'ffff'ffff'ffffULL) == 0; } @@ -102,12 +105,12 @@ static void format_float(asl::Formatter& f, T value) } } -void asl::AslFormat(Formatter& f, float value) +void asl::AslFormat(Formatter& f, float32_t value) { format_float(f, value); } -void asl::AslFormat(Formatter& f, double value) +void asl::AslFormat(Formatter& f, float64_t value) { format_float(f, value); } diff --git a/asl/formatting/format_tests.cpp b/asl/formatting/format_tests.cpp index 0e83ee1..0a1c4c5 100644 --- a/asl/formatting/format_tests.cpp +++ b/asl/formatting/format_tests.cpp @@ -79,7 +79,7 @@ ASL_TEST(format_floats) s = asl::format_to_string("{}", 123e-8); ASL_TEST_EXPECT(s == "0.00000123"_sv); - s = asl::format_to_string("{} {}", asl::infinity(), -asl::infinity()); + s = asl::format_to_string("{} {}", asl::infinity(), -asl::infinity()); ASL_TEST_EXPECT(s == "Infinity -Infinity"_sv); s = asl::format_to_string("{}", asl::nan()); diff --git a/asl/handle_pool/dense_handle_pool.hpp b/asl/handle_pool/dense_handle_pool.hpp index 34cd04f..b6ce25d 100644 --- a/asl/handle_pool/dense_handle_pool.hpp +++ b/asl/handle_pool/dense_handle_pool.hpp @@ -35,8 +35,8 @@ class DenseHandlePool T obj; template - explicit Slot(ThisIndexPool::handle h, Args&&... args) - : h{h} + explicit Slot(ThisIndexPool::handle h_, Args&&... args) + : h{h_} , obj(std::forward(args)...) {} }; diff --git a/asl/handle_pool/index_pool.hpp b/asl/handle_pool/index_pool.hpp index ffcd863..f6ff134 100644 --- a/asl/handle_pool/index_pool.hpp +++ b/asl/handle_pool/index_pool.hpp @@ -252,7 +252,8 @@ public: [[nodiscard]] bool is_full() const { - return m_first_available.is_null() && m_slots.size() > config::kMaxIndex; + return m_first_available.is_null() + && static_cast(m_slots.size()) > config::kMaxIndex; } option acquire() diff --git a/asl/strings/parse_number.cpp b/asl/strings/parse_number.cpp index 3b6b689..b3acd00 100644 --- a/asl/strings/parse_number.cpp +++ b/asl/strings/parse_number.cpp @@ -12,14 +12,14 @@ bool parse_double_impl(const char** begin, const char* end, double*); } // namespace asl -asl::status_or> asl::parse_float(asl::string_view sv) +asl::status_or> asl::parse_float32(asl::string_view sv) { const auto* begin = sv.data(); // NOLINTNEXTLINE(*-pointer-arithmetic) const auto* end = begin + sv.size(); - if (float value{}; parse_float_impl(&begin, end, &value)) + if (float32_t value{}; parse_float_impl(&begin, end, &value)) { - return parse_number_result{ + return parse_number_result{ .value = value, .remaining = string_view{begin, end}, }; @@ -27,14 +27,14 @@ asl::status_or> asl::parse_float(asl::string_vie return invalid_argument_error(); } -asl::status_or> asl::parse_double(asl::string_view sv) +asl::status_or> asl::parse_float64(asl::string_view sv) { const auto* begin = sv.data(); // NOLINTNEXTLINE(*-pointer-arithmetic) const auto* end = begin + sv.size(); - if (float value{}; parse_float_impl(&begin, end, &value)) + if (float64_t value{}; parse_double_impl(&begin, end, &value)) { - return parse_number_result{ + return parse_number_result{ .value = value, .remaining = string_view{begin, end}, }; @@ -96,7 +96,7 @@ asl::status_or> parse_integer(asl::string_view sv, i if (asl::is_signed_integer && is_negative) { - digit = static_cast(-digit); + digit = static_cast(-digit); } if (__builtin_add_overflow(value, static_cast(digit), &value)) diff --git a/asl/strings/parse_number.hpp b/asl/strings/parse_number.hpp index fae0841..8802bbb 100644 --- a/asl/strings/parse_number.hpp +++ b/asl/strings/parse_number.hpp @@ -6,6 +6,7 @@ #include "asl/types/status_or.hpp" #include "asl/strings/string_view.hpp" +#include "asl/base/float.hpp" namespace asl { @@ -17,8 +18,8 @@ struct parse_number_result string_view remaining; }; -status_or> parse_float(string_view); -status_or> parse_double(string_view); +status_or> parse_float32(string_view); +status_or> parse_float64(string_view); status_or> parse_uint8(string_view, int base = 10); status_or> parse_uint16(string_view, int base = 10); diff --git a/asl/strings/parse_number_float.cpp b/asl/strings/parse_number_float.cpp index 4568278..493435d 100644 --- a/asl/strings/parse_number_float.cpp +++ b/asl/strings/parse_number_float.cpp @@ -2,13 +2,18 @@ // // SPDX-License-Identifier: BSD-3-Clause +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" #include +#pragma clang diagnostic pop // We need to isolate fast_float.h completely from asl // because it conflicts with our redefinitions of things // from the STL. In this case it's operator new, but there // might be other conflicts. +#pragma clang diagnostic ignored "-Wmissing-prototypes" + namespace asl { diff --git a/asl/strings/parse_number_tests.cpp b/asl/strings/parse_number_tests.cpp index df759e9..a33f703 100644 --- a/asl/strings/parse_number_tests.cpp +++ b/asl/strings/parse_number_tests.cpp @@ -3,38 +3,36 @@ // SPDX-License-Identifier: BSD-3-Clause #include "asl/strings/parse_number.hpp" +#include "asl/base/numeric.hpp" #include "asl/testing/testing.hpp" -// @Todo Once we have an equivalent of std::numeric_limits, -// properly compare floating point values in these tests. - ASL_TEST(parse_float_error) { const asl::string_view sv = "this is not a number lmao"; - auto res = asl::parse_float(sv); + auto res = asl::parse_float32(sv); ASL_TEST_EXPECT(!res.ok()); } ASL_TEST(parse_float_empty) { const asl::string_view sv = ""; - auto res = asl::parse_float(sv); + auto res = asl::parse_float32(sv); ASL_TEST_EXPECT(!res.ok()); } ASL_TEST(parse_float_simple) { const asl::string_view sv = "3.1415"; - auto res = asl::parse_float(sv); + auto res = asl::parse_float32(sv); ASL_TEST_EXPECT(res.ok()); - ASL_TEST_EXPECT(res.value().value == 3.1415F); + ASL_TEST_EXPECT(asl::are_nearly_equal(res.value().value, 3.1415F)); ASL_TEST_EXPECT(res.value().remaining.size() == 0); } ASL_TEST(parse_float_integer) { const asl::string_view sv = "31415"; - auto res = asl::parse_float(sv); + auto res = asl::parse_float32(sv); ASL_TEST_EXPECT(res.ok()); ASL_TEST_EXPECT(res.value().value == 31415.0F); ASL_TEST_EXPECT(res.value().remaining.size() == 0); @@ -43,18 +41,18 @@ ASL_TEST(parse_float_integer) ASL_TEST(parse_float_scientific) { const asl::string_view sv = "314.15e-2"; - auto res = asl::parse_float(sv); + auto res = asl::parse_float32(sv); ASL_TEST_EXPECT(res.ok()); - ASL_TEST_EXPECT(res.value().value == 3.1415F); + ASL_TEST_EXPECT(asl::are_nearly_equal(res.value().value, 3.1415F)); ASL_TEST_EXPECT(res.value().remaining.size() == 0); } ASL_TEST(parse_float_suffix) { const asl::string_view sv = "3.1415 yoyoyo"; - auto res = asl::parse_float(sv); + auto res = asl::parse_float32(sv); ASL_TEST_EXPECT(res.ok()); - ASL_TEST_EXPECT(res.value().value == 3.1415F); + ASL_TEST_EXPECT(asl::are_nearly_equal(res.value().value, 3.1415F)); ASL_TEST_EXPECT(res.value().remaining == " yoyoyo"); }