Skip to content

Fix fdim for an infinite x and for a signaling NaN - #1526

Merged
mborland merged 2 commits into
boostorg:developfrom
ibmibmibm:fdim-inf
Oct 22, 2026
Merged

mborland merged 2 commits into
boostorg:developfrom
ibmibmibm:fdim-inf

Conversation

@ibmibmibm

Copy link
Copy Markdown
Contributor

This PR has 2 commits.

  • The cmath page no longer shows an int* quo parameter for fdim, fmax and fmin, and calls the result of fdim the positive difference.
  • fdim gives +0 for an infinite x that is not above y, and a NaN for an infinite x with a NaN y.
  • fdim gives a quiet NaN for a signaling NaN argument.
  • fdim no longer has its own NaN and infinity branch. The compare y >= x is false for a NaN, and x - y gives a quiet NaN and the correct result for an infinite x.
  • A new test covers infinite arguments and signaling NaN arguments for the six types.

C17 7.12.12.1 gives x - y if x > y, and +0 if x <= y. IEEE 754-2019 6.2 and 7.2 give a quiet NaN for an operation on a signaling NaN. Intel libbid and glibc give the same results as the fix.

Fixes #1525

Correctness
decimal32_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN
decimal64_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN
decimal128_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN
decimal_fast32_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN
decimal_fast64_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN
decimal_fast128_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN
  • Compared with Intel libbid __bid32_fdim, __bid64_fdim and __bid128_fdim on 160000 pairs per type in the 5 rounding modes. The pairs include zeros, infinities, quiet and signaling NaNs, subnormals, the largest values, equal values and neighbors. develop gives 4060 (decimal32_t), 2025 (decimal64_t) and 2025 (decimal128_t) incorrect values. The fix gives 0. Two differences remain, and both are permitted:
    • A result can have the same value as libbid with a different exponent. This comes from operator-, not from fdim.
    • If x is a quiet NaN and y is a signaling NaN of the other sign, the fix gives the NaN of y, and libbid gives the NaN of x. IEEE 754-2019 6.2.3 permits either.
  • github_issue_1525 fails on develop with 162 errors and passes with the fix.
  • github_issue_1525 and test_cmath pass with GCC 16.2 and Clang 23.1, in C++14 and C++20, with -Wall -Wextra -Werror. The new test also passes with BOOST_DECIMAL_FAST_MATH.
  • With BOOST_DECIMAL_FAST_MATH, the code does not change: the removed branch was already disabled in that mode.
Speed

Nanoseconds for each call, best of 3 runs of 1024 pairs, -O2, AMD Ryzen 9 3900X, one core. mix has random pairs, gt has x > y and le has x <= y:

type inputs GCC develop GCC fix Clang develop Clang fix
decimal32_t mix 8.7 9.3 10.8 10.8
decimal32_t gt 13.7 14.6 17.8 17.7
decimal32_t le 2.9 3.1 4.1 4.2
decimal64_t mix 11.4 11.3 13.6 13.5
decimal64_t gt 18.1 17.9 23.1 22.7
decimal64_t le 4.5 4.4 4.9 4.9
decimal128_t mix 23.6 24.7 27.9 27.9
decimal128_t gt 40.8 42.0 46.8 46.5
decimal128_t le 8.9 9.0 12.1 12.1
decimal_fast32_t mix 8.7 9.0 8.1 7.6
decimal_fast32_t gt 13.9 14.8 13.8 12.3
decimal_fast32_t le 3.6 3.5 4.2 3.4
decimal_fast64_t mix 10.0 10.1 10.6 9.0
decimal_fast64_t gt 17.8 18.0 17.4 16.7
decimal_fast64_t le 3.6 3.2 5.0 3.6
decimal_fast128_t mix 15.6 16.1 15.6 15.3
decimal_fast128_t gt 26.9 26.6 31.4 30.5
decimal_fast128_t le 7.1 7.3 3.3 3.1

GCC is up to 7% slower for decimal32_t and decimal_fast32_t, and Clang is up to 28% faster for the fast types. Intel libbid __bid64_fdim takes 18.8 ns for the mix pairs on the same machine.

Size

.text bytes of one object file with six functions that call fdim, one for each type:

compiler flags develop fix
GCC 16.2 -O2 8297 9329
GCC 16.2 -Os 5349 5716
Clang 23.1 -O2 863 896
Clang 23.1 -Os 261 307

A NaN now goes to operator-, so the code of operator- for values that are not finite stays in fdim.

@cppalliance-bot

cppalliance-bot commented Oct 20, 2026 •

Copy link
Copy Markdown

An automated preview of the documentation is available at https://1526.decimal.prtest3.cppalliance.org/libs/decimal/doc/html/index.html

If more commits are pushed to the pull request, the docs will rebuild at the same URL.

2026-10-21 21:25:17 UTC

@codecov

codecov Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.5%. Comparing base (59c75da) to head (fcae535).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##           develop   #1526     +/-   ##
=========================================
+ Coverage     98.5%   98.5%   +0.1%     
=========================================
  Files          325     326      +1     
  Lines        27657   27689     +32     
  Branches      2300    2301      +1     
=========================================
+ Hits         27216   27248     +32     
  Misses         441     441             
Files with missing lines Coverage Δ
include/boost/decimal/detail/cmath/fdim.hpp 100.0% <ø> (ø)
test/github_issue_1525.cpp 100.0% <100.0%> (ø)

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 59c75da...fcae535. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The cmath page showed fdim, fmax and fmin with an int* quo parameter,
which is a copy of the remquo signature. These functions take only x
and y. The table also called the result of fdim an unsigned
difference. C calls it the positive difference.
fdim returned x for each infinite x. C gives x - y when x > y, and +0
otherwise, so fdim(-inf, y) and fdim(inf, inf) must be +0. When y is
NaN, the result must be NaN, but fdim(inf, NaN) gave inf.

fdim also returned a signaling NaN input unchanged. IEEE 754 requires
a quiet NaN as the result of an operation on a signaling NaN.

The compare is false for a NaN, and the subtraction gives a quiet NaN
and the correct result for an infinite x. Thus fdim no longer has its
own NaN and infinity checks.

Fixes boostorg#1525
@ibmibmibm
ibmibmibm marked this pull request as ready for review October 22, 2026 07:17

@mborland mborland left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good. Thank you!

@mborland
mborland merged commit 79d0cb7 into boostorg:develop Oct 22, 2026
75 checks passed
@ibmibmibm
ibmibmibm deleted the fdim-inf branch October 22, 2026 21:57
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.

fdim gives an incorrect result for an infinite x and for a signaling NaN

3 participants