diff --git a/source/detail/numeric_utils.hpp b/source/detail/numeric_utils.hpp
index 1ae25abd..737992f5 100644
--- a/source/detail/numeric_utils.hpp
+++ b/source/detail/numeric_utils.hpp
@@ -73,32 +73,37 @@ constexpr Number max(Number lval, Number rval)
///
/// Floating point equality requires a bit of fuzzingdue to the imprecise nature of fp calculation
///
-template
-constexpr bool float_equals(const LNumber &lhs, const RNumber &rhs)
+template // parameter types (deduced)
+constexpr bool
+float_equals(const LNumber &lhs, const RNumber &rhs,
+ int epsilon_scale = 100) // scale the "fuzzy" equality. Higher value gives a more tolerant comparison
{
- static_assert(!std::is_integral::value && !std::is_integral::value,
+ static_assert(std::is_floating_point::value || std::is_floating_point::value,
"Using this function with two integers is just wasting time. Use ==");
+ static_assert(std::is_floating_point::value,
+ "Cannot extract epsilon from a number that isn't a floating point type");
// NANs always compare false with themselves
- if ((lhs != lhs) || (rhs != rhs))
+ if ((lhs != lhs) || (rhs != rhs)) // std::isnan isn't constexpr
{
return false;
}
// a type that lhs and rhs can agree on
using common_t = std::common_type_t;
- // The lower precision epsilon.
- // In comparison between different types, the lower precision type must be used for epsilon
- constexpr common_t epsilon = detail::max(std::numeric_limits::epsilon(), std::numeric_limits::epsilon());
- // 100 * epsilon selected as an arbitrary range
- constexpr common_t fuzz = 100 * epsilon;
+ // epsilon type defaults to float because even if both args are a higher precision type
+ // either or both could have been promoted by prior operations
+ // if a higher precision is required, the template type can be changed
+ constexpr common_t epsilon = std::numeric_limits::epsilon();
// the "epsilon" then needs to be scaled into the comparison range
// epsilon for numeric_limits is valid when abs(x) <1.0, scaling only needs to be upwards
// in particular, this prevents a lhs of 0 from requiring an exact comparison
- common_t scaled_fuzz = fuzz * max(xlnt::detail::abs(lhs), common_t{1});
+ // additionally, a scale factor is applied.
+ common_t scaled_fuzz = epsilon_scale * epsilon * max(xlnt::detail::abs(lhs), common_t{1});
return ((lhs + scaled_fuzz) >= rhs) && ((rhs + scaled_fuzz) >= lhs);
}
-//static_assert(0.1 != 0.1f, "Built in equality fails");
-//static_assert(float_equals(0.1, 0.1f), "fuzzy equality allows comparison between double and float");
+static_assert(0.1 != 0.1f, "Built in equality fails when comparing float and double due to imprecision");
+static_assert(float_equals(0.1, 0.1f), "fuzzy equality allows comparison between double and float");
} // namespace detail
} // namespace xlnt