Skip to content
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

Merged
merged 2 commits into from
Aug 7, 2020
Merged

test: Add floating-point utils #454

merged 2 commits into from
Aug 7, 2020

Conversation

chfast
Copy link
Collaborator

@chfast chfast commented Aug 4, 2020

These are helpers mostly to inspect and build NaN "values". Wasm has concepts of canonical and arithmetic NaNs, which are not present in C/C++.

Some future usage examples:

@chfast chfast force-pushed the fp_utils branch 3 times, most recently from 7e223b6 to e694fc7 Compare August 4, 2020 14:59
@codecov
Copy link

codecov bot commented Aug 4, 2020

Codecov Report

Merging #454 into master will increase coverage by 0.00%.
The diff coverage is 100.00%.

@@           Coverage Diff            @@
##           master     #454    +/-   ##
========================================
  Coverage   99.50%   99.51%            
========================================
  Files          52       54     +2     
  Lines       14939    15120   +181     
========================================
+ Hits        14865    15046   +181     
  Misses         74       74            

}

template <typename T, typename = std::enable_if<std::is_floating_point_v<T>>>
T nan(uint64_t payload = FPUtils<T>::CanonicalNaNPayload) noexcept
Copy link
Collaborator

@gumb0 gumb0 Aug 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we use std::nan/std::nanf for this? They seem to be able to set a payload, too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but they do it in an implementation-defined manner related to printing NaN "values". Maybe there is a way to find a combination of chars that gives wasm canonical and arithmetic NaNs, but I decided to go with my own NaN inspection helpers. Also to learn the internal structure of floats.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some unit tests comparing with std::nan(). It produces only quiet (aka arithmetic) nans. I prefer testing others (i.e. signaling) nans too.

@chfast chfast force-pushed the fp_utils branch 3 times, most recently from e34d942 to d19a6fb Compare August 6, 2020 10:45
test/utils/floating_point_utils.hpp Outdated Show resolved Hide resolved
test/utils/floating_point_utils.hpp Outdated Show resolved Hide resolved
test/utils/floating_point_utils.hpp Outdated Show resolved Hide resolved
@chfast chfast force-pushed the fp_utils branch 3 times, most recently from 9dc8ab7 to f1de6c7 Compare August 6, 2020 11:28
@gumb0
Copy link
Collaborator

gumb0 commented Aug 6, 2020

Tests for FP(UintType u) are missing

@chfast chfast force-pushed the fp_utils branch 2 times, most recently from 8c71f3e to 2b35f84 Compare August 6, 2020 19:58
@chfast
Copy link
Collaborator Author

chfast commented Aug 6, 2020

Tests for FP(UintType u) are missing

Added some tests for that. They are also used in nan().

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};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This C++ syntax of template just to be able to use as<value_type> is terribly, but it is what it is.

Copy link
Member

@axic axic Aug 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this beneficial over the previous one, btw? Wouldn't it be simpler here just comparing the binary representation?

I understand the FP class will come handy later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FP is comparing binary representation.

The template is a mandatory hint to the compiler that the < in as<value_type> is a template bracket. Fortunately, GCC is giving nice hints when you forget it.

return std::isnan(value) ? (as_uint() & mantissa_mask) : 0;
}

/// Build the NaN value with the given payload.
Copy link
Member

@axic axic Aug 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the canonical one?

Perhaps add a comment it is canonical by default? Though from a reading-tests-perspective perhaps having nan() and cnan() or canonical_nan() is better.

Copy link
Collaborator Author

@chfast chfast Aug 7, 2020

Choose a reason for hiding this comment

The 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. canon, canon+1, 1, ....

@axic
Copy link
Member

axic commented Aug 6, 2020

Out of curiousity: could we add tests to the main cmake (next to the bigendian tests) which ensures the platform has the proper IEEE-754 / NaN support?

@chfast
Copy link
Collaborator Author

chfast commented Aug 7, 2020

Out of curiousity: could we add tests to the main cmake (next to the bigendian tests) which ensures the platform has the proper IEEE-754 / NaN support?

See #462.

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);
Copy link
Member

@axic axic Aug 7, 2020

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...)

Copy link
Collaborator Author

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 towards 0.0. But should be something like 0.9999....

template <typename DstT, typename SrcT>
DstT bit_cast(SrcT x) noexcept
{
DstT z;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x, z are an interesting choice of combination :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took it from Go's BigInt, where z is always the return value. Want me to change it?

Copy link
Member

Choose a reason for hiding this comment

The 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.

const auto one = 1.0;
const auto inf = FP64::Limits::infinity();
const auto cnan = FP64::nan(FP64::canon);
const auto snan = FP64::nan(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is snan?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

signaling nan

template <typename T>
struct FP
{
static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, do you also want to add a static_assert for float/double being iee754 or fine as we have it in value.h and/or the limits check below?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static_assert(Limits::is_iec559); does exactly that, except only when FP is instantiated.

Copy link
Member

@axic axic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't conducted a 100% thorough review (depending on @gumb0 here), but this looks okay to me.

constexpr auto qnan = FP32::Limits::quiet_NaN();

EXPECT_EQ(FP(0.0f).nan_payload(), 0);
EXPECT_EQ(FP(FP32::Limits::signaling_NaN()).nan_payload(), 0x200000);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exact payloads of Limits::quiet_NaN() and Limits::signaling_NaN() are implementation defined, aren't they? So maybe not worth it to put it into tests? Or maybe add a comment about this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to separate tests.

friend bool operator!=(T a, FP b) noexcept { return FP{a} != b; }
};

FP(uint64_t)->FP<double>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: these two could be ordered float, then double, like using below

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reordered.

@chfast chfast merged commit 55801bc into master Aug 7, 2020
@chfast chfast deleted the fp_utils branch August 7, 2020 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants