Skip to content

Set errc::result_out_of_range on over/underflow#189

Merged
lemire merged 2 commits intofastfloat:mainfrom
alugowski:out_of_range
Mar 30, 2023
Merged

Set errc::result_out_of_range on over/underflow#189
lemire merged 2 commits intofastfloat:mainfrom
alugowski:out_of_range

Conversation

@alugowski
Copy link
Contributor

Best-effort values are still returned, such as 0 for underflow and infinity for overflow, but now the returned ec is set to std::errc::result_out_of_range instead of std::errc().

See #120

@lemire
Copy link
Member

lemire commented Mar 29, 2023

@alugowski Please review the failing tests.

Best-effort values are still returned, such as 0 for underflow and infinity for overflow, but now the returned ec is set to std::errc::result_out_of_range instead of std::errc().
@alugowski
Copy link
Contributor Author

@lemire should be fixed. Curious why these didn't fail when I was working on that same file last night :)

@lemire
Copy link
Member

lemire commented Mar 30, 2023

Curious why these didn't fail when I was working on that same file last night :)

Gremlins.

@alugowski
Copy link
Contributor Author

alugowski commented Mar 30, 2023

@lemire do you have thoughts on this GCC 9, fast-math run failure?

The powers_of_ten test fails with -ffast-math -O2, also reproduced on Apple Clang 15. Not just the out-of-range check, but the returned values don't match either (i.e. actual != expected) and I didn't touch any of that code.

Values that are expected to underflow are parsed successfully, namely values in the range [1e-323, 1e-308]. That sounds like the subnormal range.

/Users/alugowski/projects/forks/fast_float/tests/basictest.cpp:597: Failure:
  CHECK(result.ec == expected_ec)
with expansion:
  0 == 34
with message:
  i=-322
  I could not parse 1e-322
  

/Users/alugowski/projects/forks/fast_float/tests/basictest.cpp:598: Failure:
  CHECK(actual == expected)
with expansion:
  9.88131e-323 == 0
with message:
  i=-322
  String '1e-322'parsed to 9.88131e-323

I'm unclear what the desired behavior is in this case.

@alugowski
Copy link
Contributor Author

alugowski commented Mar 30, 2023

Adding the denormals to the testing_power_of_ten table yields the expected values that match fast_float::from_chars and the tests pass on my laptop both with -ffast-math -O2 and without.

Is this an acceptable fix? I'm hesitant only because I'm surprised to be the first to hit this.

static const double testing_power_of_ten[] = {
       // denormals:
      1e-323, 1e-322, 1e-321, 1e-320, 1e-319, 1e-318, 1e-317, 1e-316, 1e-315,
      1e-314, 1e-313, 1e-312, 1e-311, 1e-310, 1e-309, 1e-308,

      1e-307, 1e-306, 1e-305, 1e-304, 1e-303, 1e-302, 1e-301, 1e-300, 1e-299,
      ...

@lemire
Copy link
Member

lemire commented Mar 30, 2023

Is this an acceptable fix?

Please try it. It is not like your table can be less accurate than a runtime computation.

I'm hesitant only because I'm surprised to be the first to hit this.

I am not sure. One would need to examine the assembly, but the most likely explanation, reading your code, is that the compiler goes out and does some lossy approximation (triggered by your new code).

It does not imply that your code is itself a problem. We may simply have been lucky so far.

I might have missed something, but your code looks clean and correct.

On some platforms std::pow returns 0 instead of a subnormal number with `-ffast-math -O2` compiler options.
@alugowski
Copy link
Contributor Author

Updated PR. Works on my system.

If I had to guess I'm going with std::pow handling subnormals differently with and without -ffast-math. If this test is false:

    bool is_pow_correct{1e-308 == std::pow(10,-308)};

then the test skips subnormals and wouldn't alert on any differences.

@lemire lemire merged commit d41d507 into fastfloat:main Mar 30, 2023
@lemire
Copy link
Member

lemire commented Mar 30, 2023

I will issue a release.

@alugowski alugowski deleted the out_of_range branch March 31, 2023 05:30
@alugowski
Copy link
Contributor Author

I will issue a release.

Appreciate it!

Given that that test still fails, I looked more into it and it looks like the subnormal range is platform specific, and even method-specific within a platform.

This GCC bug shows that std::stod (and implicitly strtod) and istringstream produce different results:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86409#c2

I tried that code with Apple Clang 14 and GCC 12 and got different results. Only GCC's istringstream parsed 3.23514e-320 without raising ERANGE. Clang's returned the right value but also set ERANGE, as did both versions of stod. This is probably why it worked for me on Clang and fails here on GCC.

If neither explicit constants nor std::pow are reliable, the only remaining options I can think of are to specify expected bit patterns for subnormals or to loosen the test to pass an i if the expected value is zero.

@alugowski
Copy link
Contributor Author

I found another solution that makes GCC 9 happy. PR: #190

pitrou added a commit to pitrou/fast_float that referenced this pull request Jan 28, 2026
In fastfloat#189 the behavior on underflow was changed to better match the standard's recommendations, but the README does not mention underflow explicitly.
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.

2 participants