Skip to content

Commit

Permalink
Replace ULPs based compare with Epsilon based compare
Browse files Browse the repository at this point in the history
Epsilon based compare works with denormalized values unlike ULPs based compare. Also boost
realization does not contain UB.
  • Loading branch information
Popov-Dmitriy-Ivanovich committed Nov 3, 2023
1 parent 557cc1d commit c0c67a1
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 38 deletions.
48 changes: 12 additions & 36 deletions src/core/model/types/double_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,30 @@

#include <cassert>

#include <boost/math/special_functions/next.hpp>
#include <boost/math/special_functions/relative_difference.hpp>

#include "numeric_type.h"

namespace model {

namespace {
/*
Algorithm has been taken from
https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
*/
union DoubleBin {
DoubleBin(Double num = 0.0f) : float_representation(num) {}
// Portable extraction of components.
bool Negative() const {
return int_representation < 0;
}
model::Int int_representation;
model::Double float_representation;
};

bool AlmostEqualUlps(Double A, Double B, int maxUlpsDiff) {
DoubleBin uA(A);
DoubleBin uB(B);
// Different signs means they do not match.
if (uA.Negative() != uB.Negative()) {
// Check for equality to make sure +0==-0
if (A == B) return true;
return false;
}
// Find the difference in ULPs.
Int ulp_diff =
std::abs(uA.int_representation -
uB.int_representation); // Units in the Last Place the numbers differ by
if (ulp_diff <= maxUlpsDiff) return true;
return false;
}
} // namespace

class DoubleType final : public NumericType<Double> {
public:
DoubleType() noexcept : NumericType<Double>(TypeId::kDouble) {}

static const unsigned int kDefaultEpsCount = 5;

CompareResult Compare(std::byte const* l, std::byte const* r) const final {
return CompareEPS(l, r, kDefaultEpsCount);
}

static CompareResult CompareEPS(std::byte const* l, std::byte const* r,
unsigned int eps_count) {
Double l_val = GetValue(l);
Double r_val = GetValue(r);

if (AlmostEqualUlps(l_val, r_val, 10)) {
// if (std::abs(l_val - r_val) < std::numeric_limits<Double>::epsilon() *
// std::max(abs(l_val),abs(r_val))) {
if (boost::math::relative_difference(l_val, r_val) <
eps_count * std::numeric_limits<model::Double>::epsilon()) {
return CompareResult::kEqual;
}

Expand Down
2 changes: 0 additions & 2 deletions src/tests/test_ac_algorithm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ void AssertRanges(std::vector<std::string>& expected_ranges,
auto expected = std::unique_ptr<std::byte[]>(byte_ranges.col_pair.num_type->Allocate());
for (size_t i = 0; i < expected_ranges.size(); ++i) {
byte_ranges.col_pair.num_type->ValueFromStr(expected.get(), expected_ranges[i]);
EXPECT_EQ(byte_ranges.col_pair.num_type->ValueToString(expected.get()),
byte_ranges.col_pair.num_type->ValueToString(byte_ranges.ranges[i]));
EXPECT_EQ(byte_ranges.col_pair.num_type->Compare(expected.get(), byte_ranges.ranges[i]),
model::CompareResult::kEqual);
}
Expand Down
218 changes: 218 additions & 0 deletions src/tests/test_double_compare.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#include <boost/math/special_functions/next.hpp>
#include <gtest/gtest.h>

#include "types.h"

namespace tests {

class DoubleCompare : public ::testing::Test {
private:
std::unique_ptr<std::byte[]> double_min_ptr;
std::unique_ptr<std::byte[]> double_max_ptr;
std::unique_ptr<std::byte[]> result_ptr;

protected:
model::DoubleType double_type;
model::INumericType& double_type_ref = double_type;

std::byte* double_min;
std::byte* double_max;
std::byte* result;

void SetUp() override {
double_min_ptr.reset(double_type.MakeValue(std::numeric_limits<model::Double>().min()));
double_max_ptr.reset(double_type.MakeValue(std::numeric_limits<model::Double>().max()));
result_ptr.reset(double_type.MakeValue(0.0));
result = result_ptr.get();
double_min = double_min_ptr.get();
double_max = double_max_ptr.get();
}
};

TEST_F(DoubleCompare, AddMin) {
std::unique_ptr<std::byte[]> number_128_ptr(double_type.MakeValue(128.0)); // 2^7
std::unique_ptr<std::byte[]> number_100_ptr(double_type.MakeValue(100.0));
std::unique_ptr<std::byte[]> buff_ptr(double_type.MakeValue(0));
std::unique_ptr<std::byte[]> less_ptr(double_type.MakeValue(0));

std::byte* buff = buff_ptr.get();
std::byte* number_128 = number_128_ptr.get();
std::byte* number_100 = number_100_ptr.get();
std::byte* less = less_ptr.get();

double_type_ref.Mul(number_128, double_min, result);
double_type_ref.Mul(number_100, double_min, less);

for (int i = 0; i < 128; i++) {
double_type_ref.Add(buff, double_min, buff);
}

ASSERT_EQ(double_type_ref.Compare(buff, result), model::CompareResult::kEqual);
ASSERT_EQ(double_type_ref.Compare(less, buff), model::CompareResult::kLess);
}

TEST_F(DoubleCompare, DivMax) {
std::unique_ptr<std::byte[]> three_ptr(double_type.MakeValue(3.0));
std::unique_ptr<std::byte[]> three_power_20_ptr(double_type.MakeValue(3486784401.0));
std::unique_ptr<std::byte[]> expected_ptr(double_type.MakeValue(0.0));

std::byte* three = three_ptr.get();
std::byte* three_power_20 = three_power_20_ptr.get();
std::byte* expected = expected_ptr.get();

double_type_ref.Div(double_max, three_power_20, expected);
for (int i = 0; i < 20; i++) {
double_type_ref.Div(double_max, three, double_max);
}

ASSERT_EQ(double_type_ref.Compare(double_max, expected), model::CompareResult::kEqual);
}

TEST_F(DoubleCompare, ZeroEQMinDivMax) {
std::unique_ptr<std::byte[]> double_zero_ptr(double_type.MakeValue(0.0));
std::byte* double_zero = double_zero_ptr.get();

ASSERT_EQ(double_type_ref.Compare(double_zero, double_min), model::CompareResult::kEqual);
double_type_ref.Div(double_min, double_max, result);
ASSERT_EQ(double_type_ref.Compare(result, double_zero), model::CompareResult::kEqual);
}

TEST_F(DoubleCompare, ComprasionFromAcAlgorithm) {
std::unique_ptr<std::byte[]> seven_point_seven(double_type.MakeValue(7.7));
std::unique_ptr<std::byte[]> six_point_nine(double_type.MakeValue(6.9));
std::unique_ptr<std::byte[]> expect(double_type.MakeValue(14.6));

double_type_ref.Add(seven_point_seven.get(), six_point_nine.get(), result);
ASSERT_EQ(double_type_ref.Compare(result, expect.get()), model::CompareResult::kEqual);
}

TEST_F(DoubleCompare, AddNumbers) {
std::unique_ptr<std::byte[]> seven_seven(double_type.MakeValue(7.7));
std::unique_ptr<std::byte[]> eight_eight(double_type.MakeValue(8.8));
std::unique_ptr<std::byte[]> expect(double_type.MakeValue(16.5));

double_type_ref.Add(seven_seven.get(), eight_eight.get(), result);
ASSERT_EQ(double_type_ref.Compare(result, expect.get()), model::CompareResult::kEqual);

seven_seven.reset(double_type.MakeValue(777.777));
eight_eight.reset(double_type.MakeValue(888.888));
expect.reset(double_type.MakeValue(1666.665));
double_type_ref.Add(seven_seven.get(), eight_eight.get(), result);

ASSERT_EQ(double_type_ref.Compare(result, expect.get()), model::CompareResult::kEqual);
}

TEST_F(DoubleCompare, SubNumbers) {
std::unique_ptr<std::byte[]> seven_seven(double_type.MakeValue(7.7));
std::unique_ptr<std::byte[]> eight_eight(double_type.MakeValue(8.8));
std::unique_ptr<std::byte[]> expect(double_type.MakeValue(7.7 - 8.8));

double_type_ref.Sub(seven_seven.get(), eight_eight.get(), result);
ASSERT_EQ(double_type_ref.Compare(result, expect.get()), model::CompareResult::kEqual);

seven_seven.reset(double_type.MakeValue(777.777));
eight_eight.reset(double_type.MakeValue(888.888));
expect.reset(double_type.MakeValue(-111.111));
double_type_ref.Sub(seven_seven.get(), eight_eight.get(), result);
ASSERT_EQ(double_type_ref.Compare(result, expect.get()), model::CompareResult::kEqual);
}

TEST_F(DoubleCompare, MulNumbers) {
std::unique_ptr<std::byte[]> seven_seven(double_type.MakeValue(7.7));
std::unique_ptr<std::byte[]> eight_eight(double_type.MakeValue(8.8));
std::unique_ptr<std::byte[]> expect(double_type.MakeValue(7.7 * 8.8));

double_type_ref.Mul(seven_seven.get(), eight_eight.get(), result);
ASSERT_EQ(double_type_ref.Compare(result, expect.get()), model::CompareResult::kEqual);

seven_seven.reset(double_type.MakeValue(777.777));
eight_eight.reset(double_type.MakeValue(888.888));
expect.reset(double_type.MakeValue(691356.641976));
double_type_ref.Mul(seven_seven.get(), eight_eight.get(), result);
ASSERT_EQ(double_type_ref.Compare(result, expect.get()), model::CompareResult::kEqual);
}

TEST_F(DoubleCompare, DivNumbers) {
std::unique_ptr<std::byte[]> many_eight(double_type.MakeValue(8888.8888));
std::unique_ptr<std::byte[]> few_eight(double_type.MakeValue(8.8));
std::unique_ptr<std::byte[]> expect(double_type.MakeValue(8888.8888 / 8.8));

double_type_ref.Div(many_eight.get(), few_eight.get(), result);
ASSERT_EQ(double_type_ref.Compare(result, expect.get()), model::CompareResult::kEqual);

expect.reset(double_type.MakeValue(8.8 / 8888.8888));
double_type_ref.Div(few_eight.get(), many_eight.get(), result);
ASSERT_EQ(double_type_ref.Compare(result, expect.get()), model::CompareResult::kEqual);
}

TEST_F(DoubleCompare, TwoSmallNumbers) {
std::unique_ptr<std::byte[]> eighteen_zeroes_one(double_type.MakeValue(std::pow(10, -18)));
std::unique_ptr<std::byte[]> seventeen_zeroes_one(double_type.MakeValue(std::pow(10, -17)));

ASSERT_NE(double_type_ref.Compare(eighteen_zeroes_one.get(), seventeen_zeroes_one.get()),
model::CompareResult::kEqual);
}

TEST_F(DoubleCompare, EpsilonMin) {
std::unique_ptr<std::byte[]> epsilon(
double_type.MakeValue(std::numeric_limits<model::Double>::epsilon() / 2.0));
std::unique_ptr<std::byte[]> minimum(
double_type.MakeValue(std::numeric_limits<model::Double>::min()));

ASSERT_EQ(double_type_ref.Compare(epsilon.get(), minimum.get()),
model::CompareResult::kGreater);
}
TEST_F(DoubleCompare, LowFractionalNumber) {
std::unique_ptr<std::byte[]> ten_power_minus_2(double_type.MakeValue(1e-2));
std::unique_ptr<std::byte[]> ten_power_minus_2_plus_eps_div_ten(
double_type.MakeValue(1e-2 + 2.5e-17));

ASSERT_EQ(double_type_ref.Compare(ten_power_minus_2.get(),
ten_power_minus_2_plus_eps_div_ten.get()),
model::CompareResult::kLess);
}
TEST_F(DoubleCompare, BigFractionalNumber) {
std::unique_ptr<std::byte[]> thirty_thousands(double_type.MakeValue(30000.0));
std::unique_ptr<std::byte[]> thirty_thousands_next(
double_type.MakeValue(boost::math::float_next(30000.0)));

ASSERT_EQ(double_type_ref.Compare(thirty_thousands.get(), thirty_thousands_next.get()),
model::CompareResult::kEqual);
}

TEST_F(DoubleCompare, Denormalized) {
std::unique_ptr<std::byte[]> min_denorm(
double_type.MakeValue(std::numeric_limits<model::Double>::denorm_min()));
std::unique_ptr<std::byte[]> denorm(double_type.MakeValue(std::numeric_limits<model::Double>::min()/2.0));

ASSERT_EQ(double_type_ref.Compare(min_denorm.get(),denorm.get()),model::CompareResult::kEqual);
}

TEST_F(DoubleCompare, Infinity) {
std::unique_ptr<std::byte[]> infinity(
double_type.MakeValue(std::numeric_limits<model::Double>::infinity()));
std::unique_ptr<std::byte[]> min_denorm(
double_type.MakeValue(std::numeric_limits<model::Double>::denorm_min()));
std::unique_ptr<std::byte[]> max_norm(
double_type.MakeValue(std::numeric_limits<model::Double>::max()));
std::unique_ptr<std::byte[]> min_norm(
double_type.MakeValue(std::numeric_limits<model::Double>::min()));

ASSERT_EQ(double_type_ref.Compare(infinity.get(), infinity.get()),
model::CompareResult::kEqual);
ASSERT_EQ(double_type_ref.Compare(infinity.get(), min_denorm.get()),
model::CompareResult::kGreater);
ASSERT_EQ(double_type_ref.Compare(max_norm.get(), infinity.get()), model::CompareResult::kLess);
ASSERT_EQ(double_type_ref.Compare(min_norm.get(), infinity.get()), model::CompareResult::kLess);
}

TEST_F(DoubleCompare, DenormalizedNormalized) {
std::unique_ptr<std::byte[]> min_denorm(
double_type.MakeValue(std::numeric_limits<model::Double>::denorm_min()));
std::unique_ptr<std::byte[]> min_norm(
double_type.MakeValue(std::numeric_limits<model::Double>::min()));

ASSERT_EQ(double_type_ref.Compare(min_denorm.get(), min_norm.get()),
model::CompareResult::kEqual);
}
} // namespace tests

0 comments on commit c0c67a1

Please sign in to comment.