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). ...@@ -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 - Constructing a `typed_actor` handle from a pointer view failed du to a missing
constructor overload. This (explicit) overload now exists and the conversion constructor overload. This (explicit) overload now exists and the conversion
should work as expected. 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 ## [0.17.5] - 2020-05-13
......
...@@ -227,6 +227,7 @@ caf_add_test_suites(caf-core-test ...@@ -227,6 +227,7 @@ caf_add_test_suites(caf-core-test
deep_to_string deep_to_string
detached_actors detached_actors
detail.bounds_checker detail.bounds_checker
detail.ieee_754
detail.ini_consumer detail.ini_consumer
detail.limited_vector detail.limited_vector
detail.meta_object detail.meta_object
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <cmath> #include <cmath>
#include <cstdint> #include <cstdint>
#include <limits>
namespace caf::detail { namespace caf::detail {
...@@ -34,6 +35,11 @@ struct ieee_754_trait<float> { ...@@ -34,6 +35,11 @@ struct ieee_754_trait<float> {
static constexpr uint32_t expbits = 8; // bits used for exponent static constexpr uint32_t expbits = 8; // bits used for exponent
static constexpr float zero = 0.0f; // the value 0 static constexpr float zero = 0.0f; // the value 0
static constexpr float p5 = 0.5f; // the value 0.5 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 packed_type = uint32_t; // unsigned integer type
using signed_packed_type = int32_t; // signed integer type using signed_packed_type = int32_t; // signed integer type
using float_type = float; // floating point type using float_type = float; // floating point type
...@@ -48,6 +54,11 @@ struct ieee_754_trait<double> { ...@@ -48,6 +54,11 @@ struct ieee_754_trait<double> {
static constexpr uint64_t expbits = 11; static constexpr uint64_t expbits = 11;
static constexpr double zero = 0.0; static constexpr double zero = 0.0;
static constexpr double p5 = 0.5; 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 packed_type = uint64_t;
using signed_packed_type = int64_t; using signed_packed_type = int64_t;
using float_type = double; using float_type = double;
...@@ -60,10 +71,13 @@ template <class T> ...@@ -60,10 +71,13 @@ template <class T>
typename ieee_754_trait<T>::packed_type pack754(T f) { typename ieee_754_trait<T>::packed_type pack754(T f) {
using trait = ieee_754_trait<T>; using trait = ieee_754_trait<T>;
using result_type = typename trait::packed_type; using result_type = typename trait::packed_type;
// filter special type // filter special cases
if (std::fabs(f) <= trait::zero) { if (std::isnan(f))
return 0; // only true if f equals +0 or -0 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 auto significandbits = trait::bits - trait::expbits - 1; // -1 for sign bit
// check sign and begin normalization // check sign and begin normalization
result_type sign; result_type sign;
...@@ -102,8 +116,22 @@ typename ieee_754_trait<T>::float_type unpack754(T i) { ...@@ -102,8 +116,22 @@ typename ieee_754_trait<T>::float_type unpack754(T i) {
using trait = ieee_754_trait<T>; using trait = ieee_754_trait<T>;
using signed_type = typename trait::signed_packed_type; using signed_type = typename trait::signed_packed_type;
using result_type = typename trait::float_type; using result_type = typename trait::float_type;
if (i == 0) using limits = std::numeric_limits<result_type>;
switch (i) {
case trait::packed_pzero:
return trait::zero; 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 auto significandbits = trait::bits - trait::expbits - 1; // -1 for sign bit
// pull the significand: mask, convert back to float + add the one back on // pull the significand: mask, convert back to float + add the one back on
auto result = static_cast<result_type>(i & ((T{1} << significandbits) - 1)); 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<> { ...@@ -87,7 +87,13 @@ struct fixture : test_coordinator_fixture<> {
int32_t i32 = -345; int32_t i32 = -345;
int64_t i64 = -1234567890123456789ll; int64_t i64 = -1234567890123456789ll;
float f32 = 3.45f; 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 = 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}}; timestamp ts = timestamp{timestamp::duration{1478715821 * 1000000000ll}};
test_enum te = test_enum::b; test_enum te = test_enum::b;
std::string str = "Lorem ipsum dolor sit amet."; std::string str = "Lorem ipsum dolor sit amet.";
...@@ -174,24 +180,19 @@ struct is_message { ...@@ -174,24 +180,19 @@ struct is_message {
#define CHECK_RT(val) CAF_CHECK_EQUAL(val, roundtrip(val)) #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)) #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) { #define CHECK_SIGN_MSG_RT(value) \
// check conversion of float CAF_CHECK_EQUAL(std::signbit(msg_roundtrip(value)), std::signbit(value))
float f1 = 3.1415925f; // float value
auto p1 = caf::detail::pack754(f1); // packet value CAF_TEST_FIXTURE_SCOPE(serialization_tests, fixture)
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);
}
CAF_TEST(serializing and then deserializing produces the same value) { CAF_TEST(serializing and then deserializing produces the same value) {
CHECK_RT(i32); CHECK_RT(i32);
...@@ -202,6 +203,16 @@ CAF_TEST(serializing and then deserializing produces the same value) { ...@@ -202,6 +203,16 @@ CAF_TEST(serializing and then deserializing produces the same value) {
CHECK_RT(te); CHECK_RT(te);
CHECK_RT(str); CHECK_RT(str);
CHECK_RT(rs); 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) { CAF_TEST(messages serialize and deserialize their content) {
...@@ -213,6 +224,16 @@ 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(te);
CHECK_MSG_RT(str); CHECK_MSG_RT(str);
CHECK_MSG_RT(rs); 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) { 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