Skip to content

Commit

Permalink
test: Add floating-point utils
Browse files Browse the repository at this point in the history
  • Loading branch information
chfast committed Aug 4, 2020
1 parent c4a67e8 commit 4beb4fd
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 0 deletions.
1 change: 1 addition & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ target_sources(
execute_floating_point_test.cpp
execute_numeric_test.cpp
execute_test.cpp
floating_point_utils_test.cpp
instantiate_test.cpp
leb128_test.cpp
module_test.cpp
Expand Down
106 changes: 106 additions & 0 deletions test/unittests/floating_point_utils_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 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_uint64)
{
using Limits = std::numeric_limits<double>;

EXPECT_EQ(as_uint64(0.0), 0x0000000000000000);
EXPECT_EQ(as_uint64(-0.0), 0x8000000000000000);
EXPECT_EQ(as_uint64(Limits::infinity()), 0x7FF'0000000000000);
EXPECT_EQ(as_uint64(-Limits::infinity()), 0xFFF'0000000000000);
EXPECT_EQ(as_uint64(Limits::max()), 0x7FE'FFFFFFFFFFFFF);
EXPECT_EQ(as_uint64(-Limits::max()), 0xFFE'FFFFFFFFFFFFF);
EXPECT_EQ(as_uint64(Limits::min()), 0x001'0000000000000);
EXPECT_EQ(as_uint64(-Limits::min()), 0x801'0000000000000);
EXPECT_EQ(as_uint64(Limits::denorm_min()), 0x000'0000000000001);
EXPECT_EQ(as_uint64(-Limits::denorm_min()), 0x800'0000000000001);
EXPECT_EQ(as_uint64(1.0), 0x3FF'0000000000000);
EXPECT_EQ(as_uint64(-1.0), 0xBFF'0000000000000);
EXPECT_EQ(as_uint64(std::nextafter(1.0, 0.0)), 0x3FE'FFFFFFFFFFFFF);
EXPECT_EQ(as_uint64(std::nextafter(-1.0, 0.0)), 0xBFE'FFFFFFFFFFFFF);
EXPECT_EQ(as_uint64(Limits::quiet_NaN()), 0x7FF'8000000000000);
EXPECT_EQ(as_uint64(-Limits::quiet_NaN()), 0xFFF'8000000000000);
}

TEST(floating_point_utils, float_as_uint64)
{
using Limits = std::numeric_limits<float>;

EXPECT_EQ(as_uint64(0.0f), 0x00000000);
EXPECT_EQ(as_uint64(-0.0f), 0x80000000);
EXPECT_EQ(as_uint64(Limits::infinity()), 0x7F800000);
EXPECT_EQ(as_uint64(-Limits::infinity()), 0xFF800000);
EXPECT_EQ(as_uint64(Limits::max()), 0x7F7FFFFF);
EXPECT_EQ(as_uint64(-Limits::max()), 0xFF7FFFFF);
EXPECT_EQ(as_uint64(Limits::min()), 0x00800000);
EXPECT_EQ(as_uint64(-Limits::min()), 0x80800000);
EXPECT_EQ(as_uint64(Limits::denorm_min()), 0x00000001);
EXPECT_EQ(as_uint64(-Limits::denorm_min()), 0x80000001);
EXPECT_EQ(as_uint64(1.0f), 0x3F800000);
EXPECT_EQ(as_uint64(-1.0f), 0xBF800000);
EXPECT_EQ(as_uint64(std::nextafter(1.0f, 0.0f)), 0x3F7FFFFF);
EXPECT_EQ(as_uint64(std::nextafter(-1.0f, 0.0f)), 0xBF7FFFFF);
EXPECT_EQ(as_uint64(Limits::quiet_NaN()), 0x7FC00000);
EXPECT_EQ(as_uint64(-Limits::quiet_NaN()), 0xFFC00000);
}

TEST(floating_point_utils, double_nan_payload)
{
using Limits = std::numeric_limits<double>;
constexpr auto qnan = Limits::quiet_NaN();

EXPECT_EQ(get_nan_payload(0.0), 0);
EXPECT_EQ(get_nan_payload(Limits::signaling_NaN()), 0x4000000000000);
EXPECT_EQ(get_nan_payload(qnan), 0x8000000000000); // Wasm canonical nan.
EXPECT_EQ(get_nan_payload(qnan + 1.0), 0x8000000000000);
EXPECT_EQ(get_nan_payload(Limits::infinity() - Limits::infinity()), 0x8000000000000);
}

TEST(floating_point_utils, float_nan_payload)
{
using Limits = std::numeric_limits<float>;
constexpr auto qnan = Limits::quiet_NaN();

EXPECT_EQ(get_nan_payload(0.0f), 0);
EXPECT_EQ(get_nan_payload(Limits::signaling_NaN()), 0x200000);
EXPECT_EQ(get_nan_payload(qnan), 0x400000); // Wasm canonical nan.
EXPECT_EQ(get_nan_payload(qnan + 1.0f), 0x400000);
EXPECT_EQ(get_nan_payload(Limits::infinity() - Limits::infinity()), 0x400000);
}

TEST(floating_point_utils, double_nan)
{
EXPECT_TRUE(std::isnan(nan<double>()));
EXPECT_TRUE(std::isnan(nan<double>(1)));
EXPECT_TRUE(std::isnan(nan<double>(0xdeadbeef)));
EXPECT_TRUE(std::isnan(nan<double>(0xdeadbeefbeeef)));
EXPECT_FALSE(std::isnan(nan<double>(0)));

EXPECT_EQ(get_nan_payload(nan<double>()), FPUtils<double>::CanonicalNaNPayload);

EXPECT_EQ(as_uint64(nan<double>()), 0x7FF'8000000000000);
EXPECT_EQ(as_uint64(nan<double>(0xdeadbeef)), 0x7FF'00000deadbeef);
EXPECT_EQ(as_uint64(nan<double>(0xdeadbeefbeeef)), 0x7FF'deadbeefbeeef);
}

TEST(floating_point_utils, float_nan)
{
EXPECT_TRUE(std::isnan(nan<float>()));
EXPECT_TRUE(std::isnan(nan<float>(1)));
EXPECT_TRUE(std::isnan(nan<float>(0x7fffff)));
EXPECT_TRUE(std::isnan(nan<float>(0x400001)));
EXPECT_FALSE(std::isnan(nan<float>(0)));

EXPECT_EQ(get_nan_payload(nan<float>()), FPUtils<float>::CanonicalNaNPayload);

EXPECT_EQ(as_uint64(nan<float>()), 0x7FC00000);
EXPECT_EQ(as_uint64(nan<float>(0x7fffff)), 0x7FFFFFFF);
EXPECT_EQ(as_uint64(nan<float>(0x400001)), 0x7FC00001);
}
1 change: 1 addition & 0 deletions test/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ target_sources(
asserts.hpp
execute_helpers.hpp
fizzy_engine.cpp
floating_point_utils.hpp
hex.cpp
hex.hpp
leb128_encode.cpp
Expand Down
69 changes: 69 additions & 0 deletions test/utils/floating_point_utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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
{
template <typename T, typename = std::enable_if<std::is_floating_point_v<T>>>
struct FPUtils
{
using Limits = std::numeric_limits<T>;

static constexpr auto NumMantissaBits = Limits::digits - 1;
static constexpr auto MantissaMask = (uint64_t{1} << NumMantissaBits) - 1;

static constexpr auto CanonicalNaNPayload = uint64_t{1} << (NumMantissaBits - 1);
};

template <typename T, typename = std::enable_if<std::is_floating_point_v<T>>>
uint64_t as_uint64(T value) noexcept
{
constexpr auto num_bytes = sizeof(T);
uint8_t bytes[num_bytes];
__builtin_memcpy(bytes, &value, num_bytes);

uint64_t u = 0;
for (size_t i = 0; i < num_bytes; ++i)
u |= uint64_t{bytes[i]} << (i * 8); // little-endian
return u;
}

template <typename T, typename = std::enable_if<std::is_floating_point_v<T>>>
T from_uint64(uint64_t value) noexcept
{
constexpr auto num_bytes = sizeof(T);
uint8_t bytes[num_bytes];

for (size_t i = 0; i < num_bytes; ++i)
bytes[i] = static_cast<uint8_t>(value >> (i * 8)); // little-endian

T result;
__builtin_memcpy(&result, bytes, num_bytes);
return result;
}

template <typename T, typename = std::enable_if<std::is_floating_point_v<T>>>
uint64_t get_nan_payload(T value) noexcept
{
if (!std::isnan(value))
return 0; // NaN payload is never 0.

return as_uint64(value) & FPUtils<T>::MantissaMask;
}

template <typename T, typename = std::enable_if<std::is_floating_point_v<T>>>
T nan(uint64_t payload = FPUtils<T>::CanonicalNaNPayload) noexcept
{
using Utils = FPUtils<T>;
const auto nan_exponent = (uint64_t{Utils::Limits::max_exponent} << 1) - 1;
return from_uint64<T>(
((nan_exponent << Utils::NumMantissaBits)) | (payload & Utils::MantissaMask));
}
} // namespace fizzy::test

0 comments on commit 4beb4fd

Please sign in to comment.