Skip to content

Commit

Permalink
iox-eclipse-iceoryx#2055 Add LongDouble::Eq and digit workaround
Browse files Browse the repository at this point in the history
Signed-off-by: Dennis Liu <[email protected]>
  • Loading branch information
Dennis40816 committed Jan 14, 2024
1 parent 8c35c99 commit e27a455
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 44 deletions.
107 changes: 68 additions & 39 deletions iceoryx_hoofs/test/moduletests/test_utility_convert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ class LongDouble
IOX_LOG(DEBUG, "epsilon: " << epsilon);

long double abs_diff = std::fabs(a - b);
IOX_LOG(DEBUG, "fabs: " << abs_diff);
IOX_LOG(DEBUG, "fabs result: " << abs_diff);

return abs_diff <= epsilon;
bool is_equal = abs_diff <= epsilon;
IOX_LOG(DEBUG, "<< a and b " << ((is_equal) ? "IS" : "IS NOT") << " considered equal! >>");

return is_equal;
}
};

Expand All @@ -68,30 +71,20 @@ class convert_test : public Test
template <typename T>
std::string fp_to_string(T value)
{
static_assert(std::is_floating_point<T>::value, "fp_to_string requires floating point type");
static_assert(std::is_floating_point<T>::value, "requires floating point type");

std::ostringstream oss;
oss << std::scientific << std::setprecision(get_digits<T>()) << value;
oss << std::scientific << std::setprecision(std::numeric_limits<T>::digits10) << value;
return oss.str();
}

private:
// see https://github.com/eclipse-iceoryx/iceoryx/pull/2155
template <typename T>
constexpr static uint32_t get_digits()
std::string fp_to_string(T value, uint16_t digits)
{
if constexpr (std::is_same_v<T, float>)
{
return 9;
}
else if (std::is_same_v<T, double> || std::is_same_v<T, long double>)
{
return 19;
}
else
{
static_assert(std::is_floating_point_v<T>, "T should be floating point");
}
static_assert(std::is_floating_point<T>::value, "requires floating point type");

std::ostringstream oss;
oss << std::scientific << std::setprecision(digits) << value;
return oss.str();
}
};

Expand Down Expand Up @@ -224,11 +217,12 @@ TEST_F(convert_test, fromString_Double_Fail)
TEST_F(convert_test, fromString_LongDouble_Success)
{
::testing::Test::RecordProperty("TEST_ID", "2864fbae-ef1c-48ab-97f2-745baadc4dc5");
std::string source = "121.01";
constexpr long double VERIFY = 121.01L;
std::string source = "121.01";

auto result = iox::convert::from_string<long double>(source.c_str());
ASSERT_THAT(result.has_value(), Eq(true));
EXPECT_THAT(LongDouble::Eq(VERIFY, result.value()), Eq(true));
EXPECT_THAT(LongDouble::Eq(VERIFY, result.value()), Eq(true)); // NOLINT(clang-analyzer-core.CallAndMessage)
}

TEST_F(convert_test, fromString_LongDouble_Fail)
Expand Down Expand Up @@ -512,7 +506,6 @@ TEST_F(convert_test, fromString_SignedLongLong_EdgeCase_InRange_Success)
std::string source = "-9223372036854775808";
auto long_long_min = iox::convert::from_string<long long>(source.c_str());
ASSERT_THAT(long_long_min.has_value(), Eq(true));
// we don't use -9223372036854775808LL here for the compiler will parse it in way we don't want
EXPECT_THAT(long_long_min.value(), Eq(std::numeric_limits<long long>::min()));

source = "9223372036854775807";
Expand Down Expand Up @@ -689,90 +682,126 @@ TEST_F(convert_test, fromString_Float_EdgeCase_InRange_Success)
{
::testing::Test::RecordProperty("TEST_ID", "cf849d5d-d0ed-4447-89b8-d6b9f47287c7");

std::string source = fp_to_string(std::numeric_limits<float>::min());
// the number larger than numeric_limits<long double>::digits10 that will pass all tests for all platforms
constexpr uint16_t PLATFORM_DIGIT_WORKAROUND_MIN{7};
constexpr uint16_t PLATFORM_DIGIT_WORKAROUND_MAX{7};

std::string source = fp_to_string(std::numeric_limits<float>::min(), PLATFORM_DIGIT_WORKAROUND_MIN);
auto float_min = iox::convert::from_string<float>(source.c_str());
ASSERT_THAT(float_min.has_value(), Eq(true));
EXPECT_THAT(float_min.value(), FloatEq(std::numeric_limits<float>::min()));

source = fp_to_string(std::numeric_limits<float>::lowest());
source = fp_to_string(std::numeric_limits<float>::lowest(), PLATFORM_DIGIT_WORKAROUND_MAX);
auto float_lowest = iox::convert::from_string<float>(source.c_str());
ASSERT_THAT(float_lowest.has_value(), Eq(true));
EXPECT_THAT(float_lowest.value(), FloatEq(std::numeric_limits<float>::lowest()));

source = fp_to_string(std::numeric_limits<float>::max());
source = fp_to_string(std::numeric_limits<float>::max(), PLATFORM_DIGIT_WORKAROUND_MAX);
auto float_max = iox::convert::from_string<float>(source.c_str());
ASSERT_THAT(float_max.has_value(), Eq(true));
EXPECT_THAT(float_max.value(), FloatEq(std::numeric_limits<float>::max()));
}

TEST_F(convert_test, fromString_Float_EdgeCase_SubNormalFloat_ShouldFail)
TEST_F(convert_test, fromString_Float_EdgeCase_SubNormalFloat_ShouldFailExceptMsvc)
{
::testing::Test::RecordProperty("TEST_ID", "68d4f096-a93c-406b-b081-fe50e4b1a2c9");

// strtof will trigger ERANGE if the input is a subnormal float, resulting in a nullopt return value.
// note that for MSVC, sub normal float is a valid input!
auto normal_float_min_eps = std::nextafter(std::numeric_limits<float>::min(), 0.0F);
std::string source = fp_to_string(std::numeric_limits<float>::min() - normal_float_min_eps);
auto float_min_dec_eps = iox::convert::from_string<float>(source.c_str());
#ifdef _WIN32
ASSERT_THAT(float_min_dec_eps.has_value(), Eq(true));
ASSERT_THAT(std::fpclassify(float_min_dec_eps.value()), Eq(FP_SUBNORMAL));
EXPECT_THAT(float_min_dec_eps.value(), FloatNear(0.0F, std::numeric_limits<float>::min()));
#else
ASSERT_THAT(float_min_dec_eps.has_value(), Eq(false));
#endif
}

TEST_F(convert_test, fromString_Double_EdgeCase_InRange_Success)
{
::testing::Test::RecordProperty("TEST_ID", "d5e5e5ad-92ed-4229-8128-4ee82059fbf7");

std::string source = fp_to_string(std::numeric_limits<double>::min());
// the number larger than numeric_limits<double>::digits10 that will pass all tests for all platforms
constexpr uint16_t PLATFORM_DIGIT_WORKAROUND_MIN{19};
constexpr uint16_t PLATFORM_DIGIT_WORKAROUND_MAX{18};

std::string source = fp_to_string(std::numeric_limits<double>::min(), PLATFORM_DIGIT_WORKAROUND_MIN);
auto double_min = iox::convert::from_string<double>(source.c_str());
ASSERT_THAT(double_min.has_value(), Eq(true));
EXPECT_THAT(double_min.value(), DoubleEq(std::numeric_limits<double>::min()));

source = fp_to_string(std::numeric_limits<double>::lowest());
source = fp_to_string(std::numeric_limits<double>::lowest(), PLATFORM_DIGIT_WORKAROUND_MAX);
auto double_lowest = iox::convert::from_string<double>(source.c_str());
ASSERT_THAT(double_lowest.has_value(), Eq(true));
EXPECT_THAT(double_lowest.value(), DoubleEq(std::numeric_limits<double>::lowest()));

source = fp_to_string(std::numeric_limits<double>::max());
source = fp_to_string(std::numeric_limits<double>::max(), PLATFORM_DIGIT_WORKAROUND_MAX);
auto double_max = iox::convert::from_string<double>(source.c_str());
ASSERT_THAT(double_max.has_value(), Eq(true));
EXPECT_THAT(double_max.value(), DoubleEq(std::numeric_limits<double>::max()));
}

TEST_F(convert_test, fromString_Double_EdgeCase_SubNormalDouble_ShouldFail)
TEST_F(convert_test, fromString_Double_EdgeCase_SubNormalDouble_ShouldFailExceptMsvc)
{
::testing::Test::RecordProperty("TEST_ID", "af7ca2e6-ba7e-41f7-a321-5f68617d3566");

auto normal_double_min_eps = std::nextafter(std::numeric_limits<double>::min(), 0.0);
std::string source = fp_to_string(std::numeric_limits<double>::min() - normal_double_min_eps);
auto double_min_dec_eps = iox::convert::from_string<double>(source.c_str());
#ifdef _WIN32
ASSERT_THAT(double_min_dec_eps.has_value(), Eq(true));
ASSERT_THAT(std::fpclassify(double_min_dec_eps.value()), Eq(FP_SUBNORMAL));
EXPECT_THAT(double_min_dec_eps.value(), DoubleNear(0.0, std::numeric_limits<double>::min()));
#else
ASSERT_THAT(double_min_dec_eps.has_value(), Eq(false));
#endif
}

TEST_F(convert_test, fromString_LongDouble_EdgeCase_InRange_Success)
{
::testing::Test::RecordProperty("TEST_ID", "cab1c90b-1de0-4654-bbea-4bb4e55e4fc3");

std::string source = fp_to_string(std::numeric_limits<long double>::min());
// the number larger than numeric_limits<long double>::digits10 that will pass all tests for all platforms
constexpr uint16_t PLATFORM_DIGIT_WORKAROUND_MIN{34};
constexpr uint16_t PLATFORM_DIGIT_WORKAROUND_MAX{34};

std::string source = fp_to_string(std::numeric_limits<long double>::min(), PLATFORM_DIGIT_WORKAROUND_MIN);
auto long_double_min = iox::convert::from_string<long double>(source.c_str());
ASSERT_THAT(long_double_min.has_value(), Eq(true));
EXPECT_THAT(LongDouble::Eq(long_double_min.value(), std::numeric_limits<long double>::min()), Eq(true));
EXPECT_THAT(LongDouble::Eq(long_double_min.value(), std::numeric_limits<long double>::min()),
Eq(true)); // NOLINT(clang-analyzer-core.CallAndMessage)

source = fp_to_string(std::numeric_limits<long double>::lowest());
source = fp_to_string(std::numeric_limits<long double>::lowest(), PLATFORM_DIGIT_WORKAROUND_MAX);
auto long_double_lowest = iox::convert::from_string<long double>(source.c_str());
ASSERT_THAT(long_double_lowest.has_value(), Eq(true));
EXPECT_THAT(LongDouble::Eq(long_double_lowest.value(), std::numeric_limits<long double>::lowest()), Eq(true));
EXPECT_THAT(LongDouble::Eq(long_double_lowest.value(), std::numeric_limits<long double>::lowest()),
Eq(true)); // NOLINT(clang-analyzer-core.CallAndMessage)

source = fp_to_string(std::numeric_limits<long double>::max());
source = fp_to_string(std::numeric_limits<long double>::max(), PLATFORM_DIGIT_WORKAROUND_MAX);
auto long_double_max = iox::convert::from_string<long double>(source.c_str());
ASSERT_THAT(long_double_max.has_value(), Eq(true));
EXPECT_THAT(LongDouble::Eq(long_double_lowest.value(), std::numeric_limits<long double>::max()), Eq(true));
EXPECT_THAT(LongDouble::Eq(long_double_max.value(), std::numeric_limits<long double>::max()),
Eq(true)); // NOLINT(clang-analyzer-core.CallAndMessage)
}

TEST_F(convert_test, fromString_LongDouble_EdgeCase_SubNormalLongDouble_ShouldFail)
TEST_F(convert_test, fromString_LongDouble_EdgeCase_SubNormalLongDouble_ShouldFailExceptMsvc)
{
::testing::Test::RecordProperty("TEST_ID", "fb96e526-8fb6-4af9-87f0-dfd4193237a5");

auto normal_long_double_min_eps = std::nextafter(std::numeric_limits<long double>::min(), 0.0L);
std::string source = fp_to_string(std::numeric_limits<long double>::min() - normal_long_double_min_eps);
auto long_double_min_dec_eps = iox::convert::from_string<long double>(source.c_str());
#ifdef _WIN32
ASSERT_THAT(long_double_min_dec_eps.has_value(), Eq(true));
ASSERT_THAT(std::fpclassify(long_double_min_dec_eps.value()), Eq(FP_SUBNORMAL));
// There's no LongDoubleNear
EXPECT_TRUE(std::fabsl(long_double_min_dec_eps.value() - 0.0L) <= std::numeric_limits<long double>::min());
#else
ASSERT_THAT(long_double_min_dec_eps.has_value(), Eq(false));
#endif
}

/// NORMAL FLOATING POINT TYPE EDGE CASES END
Expand Down Expand Up @@ -904,7 +933,7 @@ TEST_F(convert_test, fromString_LongDouble_EdgeCase_ZeroDecimalNotation_Success)
{
auto decimal_ret = iox::convert::from_string<long double>(v.c_str());
ASSERT_THAT(decimal_ret.has_value(), Eq(true));
ASSERT_THAT(LongDouble::Eq(decimal_ret.value(), 0.0L), Eq(true));
ASSERT_THAT(LongDouble::Eq(decimal_ret.value(), 0.0L), Eq(true)); // NOLINT(clang-analyzer-core.CallAndMessage)
}
}

Expand Down
5 changes: 0 additions & 5 deletions iceoryx_hoofs/utility/include/iox/detail/convert.inl
Original file line number Diff line number Diff line change
Expand Up @@ -371,11 +371,6 @@ inline bool convert::is_within_range(const SourceType& source_val) noexcept
{
return true;
}
// should be normal or zero
if (!std::isnormal(source_val) && (source_val != 0.0))
{
return false;
}
}
// out of range (upper bound)
if (source_val > std::numeric_limits<TargetType>::max())
Expand Down

0 comments on commit e27a455

Please sign in to comment.