-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test: Add floating-point utils #454
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
// Fizzy: A fast WebAssembly interpreter | ||
// Copyright 2020 The Fizzy Authors. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#include <gtest/gtest.h> | ||
#include <test/utils/floating_point_utils.hpp> | ||
|
||
using namespace fizzy::test; | ||
|
||
TEST(floating_point_utils, double_as_uint) | ||
{ | ||
EXPECT_EQ(FP(0.0).as_uint(), 0x0000000000000000); | ||
EXPECT_EQ(FP(-0.0).as_uint(), 0x8000000000000000); | ||
EXPECT_EQ(FP(FP64::Limits::infinity()).as_uint(), 0x7FF'0000000000000); | ||
EXPECT_EQ(FP(-FP64::Limits::infinity()).as_uint(), 0xFFF'0000000000000); | ||
EXPECT_EQ(FP(FP64::Limits::max()).as_uint(), 0x7FE'FFFFFFFFFFFFF); | ||
EXPECT_EQ(FP(-FP64::Limits::max()).as_uint(), 0xFFE'FFFFFFFFFFFFF); | ||
EXPECT_EQ(FP(FP64::Limits::min()).as_uint(), 0x001'0000000000000); | ||
EXPECT_EQ(FP(-FP64::Limits::min()).as_uint(), 0x801'0000000000000); | ||
EXPECT_EQ(FP(FP64::Limits::denorm_min()).as_uint(), 0x000'0000000000001); | ||
EXPECT_EQ(FP(-FP64::Limits::denorm_min()).as_uint(), 0x800'0000000000001); | ||
EXPECT_EQ(FP(1.0).as_uint(), 0x3FF'0000000000000); | ||
EXPECT_EQ(FP(-1.0).as_uint(), 0xBFF'0000000000000); | ||
EXPECT_EQ(FP(std::nextafter(1.0, 0.0)).as_uint(), 0x3FE'FFFFFFFFFFFFF); | ||
EXPECT_EQ(FP(std::nextafter(-1.0, 0.0)).as_uint(), 0xBFE'FFFFFFFFFFFFF); | ||
EXPECT_EQ(FP(FP64::nan(FP64::canon)).as_uint(), 0x7FF'8000000000000); | ||
EXPECT_EQ(FP(-FP64::nan(FP64::canon)).as_uint(), 0xFFF'8000000000000); | ||
} | ||
|
||
TEST(floating_point_utils, binary_representation_implementation_defined) | ||
{ | ||
EXPECT_EQ(FP(FP64::Limits::quiet_NaN()).as_uint(), 0x7FF'8000000000000); | ||
EXPECT_EQ(FP(FP64::Limits::quiet_NaN()).nan_payload(), 0x8000000000000); | ||
EXPECT_EQ(FP(FP64::Limits::signaling_NaN()).nan_payload(), 0x4000000000000); | ||
|
||
EXPECT_EQ(FP(FP32::Limits::quiet_NaN()).as_uint(), 0x7FC00000); | ||
EXPECT_EQ(FP(FP32::Limits::quiet_NaN()).nan_payload(), 0x400000); | ||
EXPECT_EQ(FP(FP32::Limits::signaling_NaN()).nan_payload(), 0x200000); | ||
} | ||
|
||
TEST(floating_point_utils, float_as_uint) | ||
{ | ||
EXPECT_EQ(FP(0.0f).as_uint(), 0x00000000); | ||
EXPECT_EQ(FP(-0.0f).as_uint(), 0x80000000); | ||
EXPECT_EQ(FP(FP32::Limits::infinity()).as_uint(), 0x7F800000); | ||
EXPECT_EQ(FP(-FP32::Limits::infinity()).as_uint(), 0xFF800000); | ||
EXPECT_EQ(FP(FP32::Limits::max()).as_uint(), 0x7F7FFFFF); | ||
EXPECT_EQ(FP(-FP32::Limits::max()).as_uint(), 0xFF7FFFFF); | ||
EXPECT_EQ(FP(FP32::Limits::min()).as_uint(), 0x00800000); | ||
EXPECT_EQ(FP(-FP32::Limits::min()).as_uint(), 0x80800000); | ||
EXPECT_EQ(FP(FP32::Limits::denorm_min()).as_uint(), 0x00000001); | ||
EXPECT_EQ(FP(-FP32::Limits::denorm_min()).as_uint(), 0x80000001); | ||
EXPECT_EQ(FP(1.0f).as_uint(), 0x3F800000); | ||
EXPECT_EQ(FP(-1.0f).as_uint(), 0xBF800000); | ||
EXPECT_EQ(FP(std::nextafter(1.0f, 0.0f)).as_uint(), 0x3F7FFFFF); | ||
EXPECT_EQ(FP(std::nextafter(-1.0f, 0.0f)).as_uint(), 0xBF7FFFFF); | ||
EXPECT_EQ(FP(FP32::nan(FP32::canon)).as_uint(), 0x7FC00000); | ||
EXPECT_EQ(FP(-FP32::nan(FP32::canon)).as_uint(), 0xFFC00000); | ||
} | ||
|
||
TEST(floating_point_utils, double_from_uint) | ||
{ | ||
EXPECT_EQ(FP(uint64_t{0x0000000000000000}).value, 0.0); | ||
EXPECT_EQ(FP(uint64_t{0x8000000000000000}).value, -0.0); | ||
EXPECT_EQ(FP(uint64_t{0x3FF'000000000DEAD}).value, 0x1.000000000DEADp0); | ||
EXPECT_EQ(FP(uint64_t{0xBFF'000000000DEAD}).value, -0x1.000000000DEADp0); | ||
EXPECT_EQ(FP(uint64_t{0x7FF'0000000000000}).value, FP64::Limits::infinity()); | ||
EXPECT_EQ(FP(uint64_t{0xFFF'0000000000000}).value, -FP64::Limits::infinity()); | ||
} | ||
|
||
TEST(floating_point_utils, float_from_uint) | ||
{ | ||
EXPECT_EQ(FP(uint32_t{0x00000000}).value, 0.0f); | ||
EXPECT_EQ(FP(uint32_t{0x80000000}).value, -0.0f); | ||
EXPECT_EQ(FP(uint32_t{0x3FEF5680}).value, 0x1.DEADp0f); | ||
EXPECT_EQ(FP(uint32_t{0xBFEF5680}).value, -0x1.DEADp0f); | ||
EXPECT_EQ(FP(uint32_t{0x7F800000}).value, FP32::Limits::infinity()); | ||
EXPECT_EQ(FP(uint32_t{0xFF800000}).value, -FP32::Limits::infinity()); | ||
} | ||
|
||
TEST(floating_point_utils, double_nan_payload) | ||
{ | ||
constexpr auto inf = FP64::Limits::infinity(); | ||
const auto qnan = FP64::nan(FP64::canon); | ||
|
||
EXPECT_EQ(FP(0.0).nan_payload(), 0); | ||
EXPECT_EQ(FP(FP64::nan(1)).nan_payload(), 1); | ||
EXPECT_EQ(FP(FP64::nan(FP64::canon + 1)).nan_payload(), FP64::canon + 1); | ||
EXPECT_EQ(FP(qnan).nan_payload(), FP64::canon); | ||
EXPECT_EQ(FP(qnan + 1.0).nan_payload(), FP64::canon); | ||
EXPECT_EQ(FP(inf - inf).nan_payload(), FP64::canon); | ||
EXPECT_EQ(FP(inf * 0.0).nan_payload(), FP64::canon); | ||
} | ||
|
||
TEST(floating_point_utils, float_nan_payload) | ||
{ | ||
constexpr auto inf = FP32::Limits::infinity(); | ||
const auto qnan = FP32::nan(FP32::canon); | ||
|
||
EXPECT_EQ(FP(0.0f).nan_payload(), 0); | ||
EXPECT_EQ(FP(FP32::nan(1)).nan_payload(), 1); | ||
EXPECT_EQ(FP(FP32::nan(FP32::canon + 1)).nan_payload(), FP32::canon + 1); | ||
EXPECT_EQ(FP(qnan).nan_payload(), FP32::canon); | ||
EXPECT_EQ(FP(qnan + 1.0f).nan_payload(), FP32::canon); | ||
EXPECT_EQ(FP(inf - inf).nan_payload(), FP32::canon); | ||
EXPECT_EQ(FP(inf * 0.0f).nan_payload(), FP32::canon); | ||
} | ||
|
||
TEST(floating_point_utils, double_nan) | ||
{ | ||
EXPECT_TRUE(std::isnan(FP64::nan(FP64::canon))); | ||
EXPECT_TRUE(std::isnan(FP64::nan(1))); | ||
EXPECT_TRUE(std::isnan(FP64::nan(0xDEADBEEF))); | ||
EXPECT_TRUE(std::isnan(FP64::nan(0xDEADBEEFBEEEF))); | ||
EXPECT_FALSE(std::isnan(FP64::nan(0))); | ||
|
||
EXPECT_EQ(FP{FP64::nan(FP64::canon)}.nan_payload(), FP64::canon); | ||
|
||
EXPECT_EQ(FP{FP64::nan(FP64::canon)}.as_uint(), 0x7FF'8000000000000); | ||
EXPECT_EQ(FP{FP64::nan(0xDEADBEEF)}.as_uint(), 0x7FF'00000DEADBEEF); | ||
EXPECT_EQ(FP{FP64::nan(0xDEADBEEFBEEEF)}.as_uint(), 0x7FF'DEADBEEFBEEEF); | ||
} | ||
|
||
TEST(floating_point_utils, float_nan) | ||
{ | ||
EXPECT_TRUE(std::isnan(FP32::nan(FP32::canon))); | ||
EXPECT_TRUE(std::isnan(FP32::nan(1))); | ||
EXPECT_TRUE(std::isnan(FP32::nan(0x7fffff))); | ||
EXPECT_TRUE(std::isnan(FP32::nan(0x400001))); | ||
EXPECT_FALSE(std::isnan(FP32::nan(0))); | ||
|
||
EXPECT_EQ(FP{FP32::nan(FP32::canon)}.nan_payload(), FP32::canon); | ||
|
||
EXPECT_EQ(FP{FP32::nan(FP32::canon)}.as_uint(), 0x7FC00000); | ||
EXPECT_EQ(FP{FP32::nan(0x7FFFFF)}.as_uint(), 0x7FFFFFFF); | ||
EXPECT_EQ(FP{FP32::nan(0x400001)}.as_uint(), 0x7FC00001); | ||
} | ||
|
||
TEST(floating_point_utils, std_nan) | ||
{ | ||
EXPECT_EQ(FP(std::nan("")).nan_payload(), FP64::canon); | ||
EXPECT_EQ(FP(std::nan("1")).nan_payload(), FP64::canon + 1); | ||
EXPECT_EQ(FP(std::nan("0xDEAD")).nan_payload(), FP64::canon + 0xDEAD); | ||
} | ||
|
||
TEST(floating_point_utils, compare_double) | ||
{ | ||
const auto one = 1.0; | ||
const auto inf = FP64::Limits::infinity(); | ||
const auto cnan = FP64::nan(FP64::canon); | ||
const auto snan = FP64::nan(1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is snan? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. signaling nan |
||
|
||
EXPECT_EQ(FP{one}, FP{one}); | ||
EXPECT_EQ(FP{one}, one); | ||
EXPECT_EQ(one, FP{one}); | ||
|
||
EXPECT_EQ(FP{inf}, FP{inf}); | ||
EXPECT_EQ(FP{inf}, inf); | ||
EXPECT_EQ(inf, FP{inf}); | ||
|
||
EXPECT_EQ(FP{cnan}, FP{cnan}); | ||
EXPECT_EQ(FP{cnan}, cnan); | ||
EXPECT_EQ(cnan, FP{cnan}); | ||
|
||
EXPECT_EQ(FP{snan}, FP{snan}); | ||
EXPECT_EQ(FP{snan}, snan); | ||
EXPECT_EQ(snan, FP{snan}); | ||
|
||
EXPECT_NE(FP{one}, FP{inf}); | ||
EXPECT_NE(FP{one}, inf); | ||
EXPECT_NE(one, FP{inf}); | ||
|
||
EXPECT_NE(FP{one}, FP{cnan}); | ||
EXPECT_NE(FP{one}, cnan); | ||
EXPECT_NE(one, FP{cnan}); | ||
|
||
EXPECT_NE(FP{one}, FP{snan}); | ||
EXPECT_NE(FP{one}, snan); | ||
EXPECT_NE(one, FP{snan}); | ||
|
||
EXPECT_NE(FP{inf}, FP{cnan}); | ||
EXPECT_NE(FP{inf}, cnan); | ||
EXPECT_NE(inf, FP{cnan}); | ||
|
||
EXPECT_NE(FP{inf}, FP{snan}); | ||
EXPECT_NE(FP{inf}, snan); | ||
EXPECT_NE(inf, FP{snan}); | ||
|
||
EXPECT_NE(FP{cnan}, FP{snan}); | ||
EXPECT_NE(FP{cnan}, snan); | ||
EXPECT_NE(cnan, FP{snan}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
|
||
#include "execute.hpp" | ||
#include <gmock/gmock.h> | ||
#include <test/utils/floating_point_utils.hpp> | ||
#include <iosfwd> | ||
|
||
MATCHER(Traps, "") // NOLINT(readability-redundant-string-init) | ||
|
@@ -23,10 +24,8 @@ MATCHER_P(Result, value, "") // NOLINT(readability-redundant-string-init) | |
if (arg.trapped || !arg.has_value) | ||
return false; | ||
|
||
if constexpr (std::is_same_v<value_type, float>) | ||
return arg.value.f32 == value; | ||
else if constexpr (std::is_same_v<value_type, double>) | ||
return arg.value.f64 == value; | ||
if constexpr (std::is_floating_point_v<value_type>) | ||
return arg.value.template as<value_type>() == fizzy::test::FP{value}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This C++ syntax of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I understand the FP class will come handy later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The The |
||
else // always check 64 bit of result for all integers, including 32-bit results | ||
return arg.value.i64 == static_cast<std::make_unsigned_t<value_type>>(value); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Fizzy: A fast WebAssembly interpreter | ||
// Copyright 2020 The Fizzy Authors. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#pragma once | ||
|
||
#include <cmath> | ||
#include <cstdint> | ||
#include <limits> | ||
#include <type_traits> | ||
|
||
namespace fizzy::test | ||
{ | ||
/// Simple implementation of C++20's std::bit_cast. | ||
template <typename DstT, typename SrcT> | ||
DstT bit_cast(SrcT x) noexcept | ||
{ | ||
DstT z; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. x, z are an interesting choice of combination :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Took it from Go's BigInt, where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is fine, does not matter too much. Would have used something like from/to, a/b, x/y, in/out -- but many of these could be subject to some clashes with certain compilers, so not worth the hassle. |
||
static_assert(sizeof(x) == sizeof(z)); | ||
__builtin_memcpy(&z, &x, sizeof(x)); | ||
return z; | ||
} | ||
|
||
/// A wrapper for floating-point types with inspection/construction/comparison utilities. | ||
template <typename T> | ||
struct FP | ||
{ | ||
static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, do you also want to add a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
||
/// Shortcut to numeric_limits. | ||
using Limits = std::numeric_limits<T>; | ||
static_assert(Limits::is_iec559); | ||
|
||
/// The unsigned integer type matching the size of this floating-point type. | ||
using UintType = std::conditional_t<std::is_same_v<T, float>, uint32_t, uint64_t>; | ||
static_assert(sizeof(T) == sizeof(UintType)); | ||
|
||
/// The number of mantissa bits in the binary representation. | ||
static constexpr auto num_mantissa_bits = Limits::digits - 1; | ||
|
||
/// The binary mask of the mantissa part of the binary representation. | ||
static constexpr auto mantissa_mask = (UintType{1} << num_mantissa_bits) - 1; | ||
|
||
/// The number of exponent bits in the binary representation. | ||
static constexpr auto num_exponent_bits = int{sizeof(T) * 8} - num_mantissa_bits - 1; | ||
|
||
/// The exponent value (all exponent bits set) for NaNs. | ||
static constexpr auto nan_exponent = (UintType{1} << num_exponent_bits) - 1; | ||
|
||
/// The payload of the canonical NaN (only the top bit set). | ||
/// See: https://webassembly.github.io/spec/core/syntax/values.html#canonical-nan. | ||
static constexpr auto canon = UintType{1} << (num_mantissa_bits - 1); | ||
|
||
T value{}; | ||
|
||
explicit FP(T v) noexcept : value{v} {}; | ||
|
||
explicit FP(UintType u) noexcept : value{bit_cast<T>(u)} {}; | ||
|
||
/// Return unsigned integer with the binary representation of the value. | ||
UintType as_uint() const noexcept { return bit_cast<UintType>(value); } | ||
|
||
/// Returns NaN payload if the value is a NaN, otherwise 0 (NaN payload is never 0). | ||
UintType nan_payload() const noexcept | ||
{ | ||
return std::isnan(value) ? (as_uint() & mantissa_mask) : 0; | ||
} | ||
|
||
/// Build the NaN value with the given payload. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Perhaps add a comment it is canonical by default? Though from a reading-tests-perspective perhaps having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I can make the argument explicit, as they are coming in groups, e.g. |
||
/// | ||
/// The NaN values have any sign, all exponent bits set, and non-zero mantissa (otherwise they | ||
/// would be infinities). | ||
/// The IEEE 754 defines quiet NaN as having the top bit of the mantissa set to 1. Wasm calls | ||
/// this NaN _arithmetic_. The arithmetic NaN with the lowest mantissa (the top bit set, all | ||
/// other zeros) is the _canonical_ NaN. | ||
static T nan(UintType payload) noexcept | ||
{ | ||
return FP{(nan_exponent << num_mantissa_bits) | (payload & mantissa_mask)}.value; | ||
} | ||
|
||
friend bool operator==(FP a, FP b) noexcept { return a.as_uint() == b.as_uint(); } | ||
friend bool operator==(FP a, T b) noexcept { return a == FP{b}; } | ||
friend bool operator==(T a, FP b) noexcept { return FP{a} == b; } | ||
|
||
friend bool operator!=(FP a, FP b) noexcept { return !(a == b); } | ||
friend bool operator!=(FP a, T b) noexcept { return a != FP{b}; } | ||
friend bool operator!=(T a, FP b) noexcept { return FP{a} != b; } | ||
}; | ||
|
||
FP(uint32_t)->FP<float>; | ||
FP(uint64_t)->FP<double>; | ||
|
||
using FP32 = FP<float>; | ||
using FP64 = FP<double>; | ||
} // namespace fizzy::test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Damn these helpers, so this is the
1.0 - <smallest representable value>
(e.g. 0.999999999999999...)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not exactly. This is the next representable value after
1.0
towards0.0
. But should be something like0.9999...
.