-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #454 from wasmx/fp_utils
test: Add floating-point utils
- Loading branch information
Showing
5 changed files
with
292 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
|
||
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}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
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>); | ||
|
||
/// 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. | ||
/// | ||
/// 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 |