feat(hex8): preserve full high-low state in dynamic element responses
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
#include "../../../src/fesa/math/high_low_arithmetic.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
using namespace fesa::math_internal;
|
||||
|
||||
TEST(HighLowArithmetic, RetainsSubUlpAdditionsAndSignedCancellation) {
|
||||
ASSERT_TRUE(ArithmeticEnvironmentSupported());
|
||||
EXPECT_FALSE(IsNormalized(HighLow{-0.0, -0.0}));
|
||||
EXPECT_TRUE(IsNormalized(HighLow{-0.0}));
|
||||
EXPECT_FALSE(std::signbit(HighLow{-0.0}.high));
|
||||
EXPECT_FALSE(std::signbit(Negate(HighLow{}).high));
|
||||
EXPECT_FALSE(std::signbit(Negate(HighLow{}).low));
|
||||
const double tiny = std::ldexp(1.0, -60);
|
||||
const auto positive = Add(HighLow{1}, HighLow{tiny});
|
||||
EXPECT_EQ(positive.high, 1);
|
||||
EXPECT_EQ(positive.low, tiny);
|
||||
EXPECT_EQ(Rounded(positive), 1);
|
||||
const auto difference = Subtract(positive, HighLow{1});
|
||||
EXPECT_EQ(difference.high, tiny);
|
||||
EXPECT_EQ(difference.low, 0);
|
||||
EXPECT_EQ(Compare(positive, HighLow{1}), 1);
|
||||
EXPECT_EQ(Compare(HighLow{1, -tiny}, HighLow{1}), -1);
|
||||
EXPECT_EQ(Compare(Add(positive, Negate(positive)), HighLow{}), 0);
|
||||
EXPECT_EQ(Abs(HighLow{-1, -tiny}).low, tiny);
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, AllProductTermsAndDivisionResidualReachState) {
|
||||
const double tiny = std::ldexp(1.0, -60);
|
||||
const auto product = Multiply(HighLow{1, tiny}, HighLow{1, -tiny});
|
||||
EXPECT_EQ(product.high, 1);
|
||||
EXPECT_EQ(product.low, -std::ldexp(1.0, -120));
|
||||
const auto quotient = Divide(HighLow{1}, HighLow{3});
|
||||
ASSERT_TRUE(IsNormalized(quotient));
|
||||
const auto defect = Subtract(Multiply(quotient, HighLow{3}), HighLow{1});
|
||||
EXPECT_LE(std::abs(defect.high), std::ldexp(1.0, -104));
|
||||
const auto cancellation = Subtract(HighLow{1, tiny}, HighLow{1, tiny / 2});
|
||||
EXPECT_EQ(cancellation.high, tiny / 2);
|
||||
EXPECT_EQ(cancellation.low, 0);
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, NormalizesTiesAndCrossesPowerOfTwoBoundary) {
|
||||
const double half_ulp = std::ldexp(1.0, -53);
|
||||
EXPECT_TRUE(IsNormalized(HighLow{1, half_ulp}));
|
||||
EXPECT_FALSE(IsNormalized(HighLow{std::nextafter(1.0, 2.0), -half_ulp}));
|
||||
auto result = HighLow{1};
|
||||
for (int i = 0; i < 4; ++i) result = Add(result, HighLow{half_ulp / 2});
|
||||
EXPECT_EQ(result.high, std::nextafter(1.0, 2.0));
|
||||
EXPECT_EQ(result.low, 0);
|
||||
const auto minimum = HighLow{std::numeric_limits<double>::denorm_min()};
|
||||
EXPECT_EQ(Add(minimum, minimum).high, 2 * minimum.high);
|
||||
EXPECT_EQ(Multiply(minimum, HighLow{1}).high, minimum.high);
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, RejectsUnsupportedDyadicTailAndNonfiniteResults) {
|
||||
const auto minimum = HighLow{std::numeric_limits<double>::denorm_min()};
|
||||
EXPECT_FALSE(IsFinite(Multiply(minimum, HighLow{0.5})));
|
||||
EXPECT_FALSE(IsFinite(Multiply(HighLow{std::ldexp(1.0, -1022)},
|
||||
HighLow{std::nextafter(0.5, 1.0)})));
|
||||
EXPECT_FALSE(IsFinite(Divide(minimum, HighLow{2})));
|
||||
EXPECT_FALSE(IsFinite(Divide(HighLow{1}, HighLow{})));
|
||||
EXPECT_FALSE(IsFinite(Add(HighLow{std::numeric_limits<double>::max()},
|
||||
HighLow{std::numeric_limits<double>::max()})));
|
||||
EXPECT_EQ(Compare(InvalidHighLow(), HighLow{}), 2);
|
||||
EXPECT_FALSE(IsNormalized(HighLow{1, 1}));
|
||||
EXPECT_TRUE(std::isnan(Rounded(HighLow{1, 1})));
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, ExactDyadicOracleBoundsDiscardedTailAndRefinement) {
|
||||
// Expected pairs were obtained independently using exact rational arithmetic.
|
||||
// Add and multiply deliberately discard a term at 2^-108; neither is exact.
|
||||
for (int scale : {-100, 0, 100}) {
|
||||
const auto x = HighLow{std::ldexp(1.0, scale), std::ldexp(1.0, scale-54)};
|
||||
const auto y = HighLow{std::ldexp(1.0, scale-53), std::ldexp(1.0, scale-108)};
|
||||
const auto sum = Add(x, y);
|
||||
EXPECT_EQ(sum.high, std::ldexp(0x1.0000000000001p+0, scale));
|
||||
EXPECT_EQ(sum.low, -std::ldexp(1.0, scale-54));
|
||||
const auto square = Multiply(x, x);
|
||||
EXPECT_EQ(square.high, std::ldexp(1.0, 2*scale));
|
||||
EXPECT_EQ(square.low, std::ldexp(1.0, 2*scale-53));
|
||||
}
|
||||
const auto third = Divide(HighLow{1}, HighLow{3});
|
||||
EXPECT_EQ(third.high, 0x1.5555555555555p-2);
|
||||
EXPECT_EQ(third.low, 0x1.5555555555555p-56);
|
||||
const auto third_residual = Subtract(Multiply(HighLow{3}, third), HighLow{1});
|
||||
EXPECT_EQ(third_residual.high, -std::ldexp(1.0, -108));
|
||||
EXPECT_EQ(third_residual.low, 0);
|
||||
const auto refined = Divide(HighLow{1, std::ldexp(1.0,-60)},
|
||||
HighLow{1.25, -std::ldexp(1.0,-60)});
|
||||
EXPECT_EQ(refined.high, 0x1.999999999999ap-1);
|
||||
EXPECT_EQ(refined.low, -0x1.8e147ae147ae1p-55);
|
||||
const auto boundary = TwoProduct(0x1.0000000000001p+0,
|
||||
0x1.0000000000001p-970);
|
||||
EXPECT_EQ(boundary.high, 0x1.0000000000002p-970);
|
||||
EXPECT_EQ(boundary.low, std::numeric_limits<double>::denorm_min());
|
||||
EXPECT_FALSE(IsFinite(TwoProduct(std::nextafter(1.0, 2.0),
|
||||
std::nextafter(std::numeric_limits<double>::min(), 1.0))));
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, RejectsChangedRoundingAndFlushToZeroEnvironment) {
|
||||
struct Restore {
|
||||
int rounding = std::fegetround();
|
||||
#if defined(_M_X64) || defined(__SSE2__)
|
||||
unsigned csr = _mm_getcsr();
|
||||
#endif
|
||||
~Restore() {
|
||||
std::fesetround(rounding);
|
||||
#if defined(_M_X64) || defined(__SSE2__)
|
||||
_mm_setcsr(csr);
|
||||
#endif
|
||||
}
|
||||
} restore;
|
||||
ASSERT_TRUE(ArithmeticEnvironmentSupported());
|
||||
ASSERT_EQ(std::fesetround(FE_DOWNWARD), 0);
|
||||
EXPECT_FALSE(ArithmeticEnvironmentSupported());
|
||||
ASSERT_EQ(std::fesetround(FE_TONEAREST), 0);
|
||||
#if defined(_M_X64) || defined(__SSE2__)
|
||||
_mm_setcsr(restore.csr | 0x8000U);
|
||||
EXPECT_FALSE(ArithmeticEnvironmentSupported());
|
||||
_mm_setcsr(restore.csr | 0x0040U);
|
||||
EXPECT_FALSE(ArithmeticEnvironmentSupported());
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user