Unverified Commit e3f7e64c authored by Joseph Noir's avatar Joseph Noir Committed by GitHub

Merge pull request #1108

Fix packing/unpacking of NaN and infinite values
parents 47ebe211 8db02129
......@@ -144,6 +144,14 @@ is based on [Keep a Changelog](https://keepachangelog.com).
- Constructing a `typed_actor` handle from a pointer view failed du to a missing
constructor overload. This (explicit) overload now exists and the conversion
should work as expected.
- Sending floating points to remote actors changed `infinity` and `NaN` to
garbage values (#1107). The fixed packing / unpacking routines for IEEE 754
values keep these non-numeric values intact now. It is worth mentioning that
the new algorithm downgrades signaling NaN values to silent NaN values,
because the standard API does not provide predicates to distinguish between the
two. This should have no implications for real-world applications, because
actors that produce a signaling NaN trigger trap handlers before sending
the result to another actor.
## [0.17.5] - 2020-05-13
......
......@@ -227,6 +227,7 @@ caf_add_test_suites(caf-core-test
deep_to_string
detached_actors
detail.bounds_checker
detail.ieee_754
detail.ini_consumer
detail.limited_vector
detail.meta_object
......
......@@ -22,6 +22,7 @@
#include <cmath>
#include <cstdint>
#include <limits>
namespace caf::detail {
......@@ -30,13 +31,18 @@ struct ieee_754_trait;
template <>
struct ieee_754_trait<float> {
static constexpr uint32_t bits = 32; // number of bits
static constexpr uint32_t expbits = 8; // bits used for exponent
static constexpr float zero = 0.0f; // the value 0
static constexpr float p5 = 0.5f; // the value 0.5
using packed_type = uint32_t; // unsigned integer type
using signed_packed_type = int32_t; // signed integer type
using float_type = float; // floating point type
static constexpr uint32_t bits = 32; // number of bits
static constexpr uint32_t expbits = 8; // bits used for exponent
static constexpr float zero = 0.0f; // the value 0
static constexpr float p5 = 0.5f; // the value 0.5
static constexpr uint32_t packed_pzero = 0x00000000; // positive zero
static constexpr uint32_t packed_nzero = 0x80000000; // negative zero
static constexpr uint32_t packed_nan = 0xFFFFFFFF; // not-a-number
static constexpr uint32_t packed_pinf = 0xFF800000; // positive infinity
static constexpr uint32_t packed_ninf = 0x7F800000; // negative infinity
using packed_type = uint32_t; // unsigned integer type
using signed_packed_type = int32_t; // signed integer type
using float_type = float; // floating point type
};
template <>
......@@ -48,6 +54,11 @@ struct ieee_754_trait<double> {
static constexpr uint64_t expbits = 11;
static constexpr double zero = 0.0;
static constexpr double p5 = 0.5;
static constexpr uint64_t packed_pzero = 0x0000000000000000ull;
static constexpr uint64_t packed_nzero = 0x8000000000000000ull;
static constexpr uint64_t packed_nan = 0xFFFFFFFFFFFFFFFFull;
static constexpr uint64_t packed_pinf = 0xFFF0000000000000ull;
static constexpr uint64_t packed_ninf = 0x7FF0000000000000ull;
using packed_type = uint64_t;
using signed_packed_type = int64_t;
using float_type = double;
......@@ -60,10 +71,13 @@ template <class T>
typename ieee_754_trait<T>::packed_type pack754(T f) {
using trait = ieee_754_trait<T>;
using result_type = typename trait::packed_type;
// filter special type
if (std::fabs(f) <= trait::zero) {
return 0; // only true if f equals +0 or -0
}
// filter special cases
if (std::isnan(f))
return trait::packed_nan;
if (std::isinf(f))
return std::signbit(f) ? trait::packed_ninf : trait::packed_pinf;
if (std::fabs(f) <= trait::zero) // only true if f equals +0 or -0
return std::signbit(f) ? trait::packed_nzero : trait::packed_pzero;
auto significandbits = trait::bits - trait::expbits - 1; // -1 for sign bit
// check sign and begin normalization
result_type sign;
......@@ -102,8 +116,22 @@ typename ieee_754_trait<T>::float_type unpack754(T i) {
using trait = ieee_754_trait<T>;
using signed_type = typename trait::signed_packed_type;
using result_type = typename trait::float_type;
if (i == 0)
return trait::zero;
using limits = std::numeric_limits<result_type>;
switch (i) {
case trait::packed_pzero:
return trait::zero;
case trait::packed_nzero:
return -trait::zero;
case trait::packed_pinf:
return limits::infinity();
case trait::packed_ninf:
return -limits::infinity();
case trait::packed_nan:
return limits::quiet_NaN();
default:
// carry on
break;
}
auto significandbits = trait::bits - trait::expbits - 1; // -1 for sign bit
// pull the significand: mask, convert back to float + add the one back on
auto result = static_cast<result_type>(i & ((T{1} << significandbits) - 1));
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE detail.ieee_754
#include "caf/detail/ieee_754.hpp"
#include "caf/test/dsl.hpp"
#include <limits>
namespace {
using caf::detail::pack754;
using caf::detail::unpack754;
template <class T>
T roundtrip(T x) {
return unpack754(pack754(x));
}
using flimits = std::numeric_limits<float>;
using dlimits = std::numeric_limits<double>;
} // namespace
#define CHECK_RT(value) CAF_CHECK_EQUAL(roundtrip(value), value)
#define CHECK_PRED_RT(pred, value) CAF_CHECK(pred(roundtrip(value)))
#define CHECK_SIGN_RT(value) \
CAF_CHECK_EQUAL(std::signbit(roundtrip(value)), std::signbit(value))
CAF_TEST(packing and then unpacking floats returns the original value) {
CAF_MESSAGE("finite values compare equal");
CHECK_RT(0.f);
CHECK_RT(0xCAFp1);
CHECK_RT(flimits::epsilon());
CHECK_RT(flimits::min());
CHECK_RT(flimits::max());
CHECK_RT(-0.f);
CHECK_RT(0xCAFp1);
CHECK_RT(-flimits::epsilon());
CHECK_RT(-flimits::min());
CHECK_RT(-flimits::max());
CAF_MESSAGE("packing and unpacking preserves infinity and NaN");
CHECK_PRED_RT(std::isinf, flimits::infinity());
CHECK_PRED_RT(std::isinf, -flimits::infinity());
CHECK_PRED_RT(std::isnan, flimits::quiet_NaN());
CAF_MESSAGE("packing and unpacking preserves the sign bit");
CHECK_SIGN_RT(0.f);
CHECK_SIGN_RT(0xCAFp1);
CHECK_SIGN_RT(flimits::epsilon());
CHECK_SIGN_RT(flimits::min());
CHECK_SIGN_RT(flimits::max());
CHECK_SIGN_RT(flimits::infinity());
CHECK_SIGN_RT(-0.f);
CHECK_SIGN_RT(-0xCAFp1);
CHECK_SIGN_RT(-flimits::epsilon());
CHECK_SIGN_RT(-flimits::min());
CHECK_SIGN_RT(-flimits::max());
CHECK_SIGN_RT(-flimits::infinity());
}
CAF_TEST(packing and then unpacking doubles returns the original value) {
CAF_MESSAGE("finite values compare equal");
CHECK_RT(0.);
CHECK_RT(0xCAFp1);
CHECK_RT(dlimits::epsilon());
CHECK_RT(dlimits::min());
CHECK_RT(dlimits::max());
CHECK_RT(-0.);
CHECK_RT(0xCAFp1);
CHECK_RT(-dlimits::epsilon());
CHECK_RT(-dlimits::min());
CHECK_RT(-dlimits::max());
CAF_MESSAGE("packing and unpacking preserves infinity and NaN");
CHECK_PRED_RT(std::isinf, dlimits::infinity());
CHECK_PRED_RT(std::isinf, -dlimits::infinity());
CHECK_PRED_RT(std::isnan, dlimits::quiet_NaN());
CAF_MESSAGE("packing and unpacking preserves the sign bit");
CHECK_SIGN_RT(0.);
CHECK_SIGN_RT(0xCAFp1);
CHECK_SIGN_RT(dlimits::epsilon());
CHECK_SIGN_RT(dlimits::min());
CHECK_SIGN_RT(dlimits::max());
CHECK_SIGN_RT(dlimits::infinity());
CHECK_SIGN_RT(-0.);
CHECK_SIGN_RT(-0xCAFp1);
CHECK_SIGN_RT(-dlimits::epsilon());
CHECK_SIGN_RT(-dlimits::min());
CHECK_SIGN_RT(-dlimits::max());
CHECK_SIGN_RT(-dlimits::infinity());
}
......@@ -87,7 +87,13 @@ struct fixture : test_coordinator_fixture<> {
int32_t i32 = -345;
int64_t i64 = -1234567890123456789ll;
float f32 = 3.45f;
float f32_nan = std::numeric_limits<float>::quiet_NaN();
float f32_pos_inf = std::numeric_limits<float>::infinity();
float f32_neg_inf = -std::numeric_limits<float>::infinity();
double f64 = 54.3;
double f64_nan = std::numeric_limits<double>::quiet_NaN();
double f64_pos_inf = std::numeric_limits<double>::infinity();
double f64_neg_inf = -std::numeric_limits<double>::infinity();
timestamp ts = timestamp{timestamp::duration{1478715821 * 1000000000ll}};
test_enum te = test_enum::b;
std::string str = "Lorem ipsum dolor sit amet.";
......@@ -174,24 +180,19 @@ struct is_message {
#define CHECK_RT(val) CAF_CHECK_EQUAL(val, roundtrip(val))
#define CHECK_PRED_RT(pred, value) CAF_CHECK(pred(roundtrip(value)))
#define CHECK_SIGN_RT(value) \
CAF_CHECK_EQUAL(std::signbit(roundtrip(value)), std::signbit(value))
#define CHECK_MSG_RT(val) CAF_CHECK_EQUAL(val, msg_roundtrip(val))
CAF_TEST_FIXTURE_SCOPE(serialization_tests, fixture)
#define CHECK_PRED_MSG_RT(pred, value) CAF_CHECK(pred(msg_roundtrip(value)))
CAF_TEST(ieee_754_conversion) {
// check conversion of float
float f1 = 3.1415925f; // float value
auto p1 = caf::detail::pack754(f1); // packet value
CAF_CHECK_EQUAL(p1, static_cast<decltype(p1)>(0x40490FDA));
auto u1 = caf::detail::unpack754(p1); // unpacked value
CAF_CHECK_EQUAL(f1, u1);
// check conversion of double
double f2 = 3.14159265358979311600; // double value
auto p2 = caf::detail::pack754(f2); // packet value
CAF_CHECK_EQUAL(p2, static_cast<decltype(p2)>(0x400921FB54442D18));
auto u2 = caf::detail::unpack754(p2); // unpacked value
CAF_CHECK_EQUAL(f2, u2);
}
#define CHECK_SIGN_MSG_RT(value) \
CAF_CHECK_EQUAL(std::signbit(msg_roundtrip(value)), std::signbit(value))
CAF_TEST_FIXTURE_SCOPE(serialization_tests, fixture)
CAF_TEST(serializing and then deserializing produces the same value) {
CHECK_RT(i32);
......@@ -202,6 +203,16 @@ CAF_TEST(serializing and then deserializing produces the same value) {
CHECK_RT(te);
CHECK_RT(str);
CHECK_RT(rs);
CHECK_PRED_RT(std::isnan, f32_nan);
CHECK_PRED_RT(std::isinf, f32_pos_inf);
CHECK_PRED_RT(std::isinf, f32_neg_inf);
CHECK_PRED_RT(std::isnan, f64_nan);
CHECK_PRED_RT(std::isinf, f64_pos_inf);
CHECK_PRED_RT(std::isinf, f64_neg_inf);
CHECK_SIGN_RT(f32_pos_inf);
CHECK_SIGN_RT(f32_neg_inf);
CHECK_SIGN_RT(f64_pos_inf);
CHECK_SIGN_RT(f64_neg_inf);
}
CAF_TEST(messages serialize and deserialize their content) {
......@@ -213,6 +224,16 @@ CAF_TEST(messages serialize and deserialize their content) {
CHECK_MSG_RT(te);
CHECK_MSG_RT(str);
CHECK_MSG_RT(rs);
CHECK_PRED_MSG_RT(std::isnan, f32_nan);
CHECK_PRED_MSG_RT(std::isinf, f32_pos_inf);
CHECK_PRED_MSG_RT(std::isinf, f32_neg_inf);
CHECK_PRED_MSG_RT(std::isnan, f64_nan);
CHECK_PRED_MSG_RT(std::isinf, f64_pos_inf);
CHECK_PRED_MSG_RT(std::isinf, f64_neg_inf);
CHECK_SIGN_MSG_RT(f32_pos_inf);
CHECK_SIGN_MSG_RT(f32_neg_inf);
CHECK_SIGN_MSG_RT(f64_pos_inf);
CHECK_SIGN_MSG_RT(f64_neg_inf);
}
CAF_TEST(raw_arrays) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment