Skip to content

fix(libc): leave errno unchanged from ldexpf, as titles' libc.prx does - #12676

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
JJenkx:windows/libc-ldexpf-errno
Oct 29, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
JJenkx:windows/libc-ldexpf-errno

Conversation

@JJenkx

@JJenkx JJenkx commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

What

ldexpf set errno to ERANGE for some overflows and underflows. The libc.prx that titles ship never sets errno from ldexpf.

ldexpf called the host std::ldexp on a float. That computes in double and sets ERANGE only when the double result leaves the double range. So ldexpf(0x1.3d1c6p-95f, 1262) set ERANGE, while ldexpf(1.0f, 200), which overflows the float just the same, did not.

The titles' ldexpf leaves errno alone in both cases, as their scalbnf does. On Windows, main's scalbnf (std::scalbn on a float) already matches the titles exactly. So ldexpf now computes with std::scalbn and keeps errno across the call, as fmod does: glibc's scalbnf sets ERANGE on overflow and on underflow to zero (found by rooklz). The double ldexp already matches the titles and is unchanged.

Tested

Windows 11, WinLibs GCC 15.2.0 UCRT r7, Release, main 3a2b919816.

Linux and macOS: not built or run by me. rooklz ran the 8 CheckLdexpfErrno rows standalone. glibc 2.39 failed 5 of them with the plain std::scalbn body (errno = 34), and main failed the same 5. With the errno save and restore, all 8 pass on glibc and on macOS. On Linux, main's scalbnf and scalbn also set ERANGE where the titles don't; that's a separate gap for a follow-up.

Exception flags: a separate sweep of 100,000 random pairs compared the raised exception flags. ldexpf (with this PR), scalbn and scalbnf match the titles on every pair.

Noticed, not changed here: double ldexp differs in flags on 1,172 pairs, with the same values and errno. In each case shown the result is an inexact subnormal (for example ldexp(1.5, -1074)): the titles raise no flags, while this build raises underflow and inexact. For an underflow all the way to zero, both raise underflow and inexact.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

ldexpf called the host ldexp on a float, which computes in double and
sets ERANGE only when the double result leaves the double range, so
ldexpf(0x1.3d1c6p-95f, 1262) set ERANGE while ldexpf(1.0f, 200) did
not. The titles' ldexpf never sets errno, as their scalbnf does not.
ldexpf now computes like scalbnf: on 100,000 random arguments it
matches the libc.prx of PPSA07923 and PPSA05916 in every value and
errno, against 63,957 errno differences before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rooklz

rooklz commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Host-only review of head 03f5cfafe1 (vs main 3a2b919816) on macOS arm64/x86_64 and Linux x86_64. It fills the body's Not tested: Linux and macOS.

Linux breaks the new test. glibc's scalbnf sets ERANGE on overflow and on underflow to zero. So std::scalbn(float, int), which is this PR's new ldexpf body, does not leave errno alone there. I ran a standalone with the 8 CheckLdexpfErrno rows copied exactly. Each one calls std::scalbn and std::ldexp on a float and checks bits and errno == 1234, as the test does:

host head (std::scalbn) main (std::ldexp)
macOS 15 arm64 + x86_64, Apple clang 0/8 fail 0/8 fail
Ubuntu 24.04 x86_64, g++ 13.3, glibc 2.39 (anyps5-dev image), -O0 and -O2 5/8 fail (rows 0–4, errno = 34) 5/8 fail

The bits match in every row. Only errno fails. A 1,000,000-pair sweep (half any bit pattern, half exponent in [-20,60), n = a quarter any int and three quarters in [-2200,2200)) gave 0 value differences against a double-scaling reference on every host. errno changed on 950,341 pairs on glibc for head and main alike, and on 0 pairs on macOS.

So on Linux, guest_math would fail with this PR. PR CI doesn't show it: linux is SKIPPED, and the Ubuntu relinker lane doesn't build guest_math, so the first red would be main's full Linux build. One more point: the claim that "main's scalbnf already matches the titles exactly" holds on Windows only. On Linux, main's scalbnf sets ERANGE too. That's an existing gap, separate from this PR.

Suggested change. It uses the same pattern fmod_nid_postfix already uses a few lines below:

-float APS5_VABI ldexpf_nid_postfix(float x, int exp) { return std::scalbn(x, exp); }
+float APS5_VABI ldexpf_nid_postfix(float x, int exp) {
+    int saved_errno = errno;
+    float result = std::scalbn(x, exp);
+    errno = saved_errno;
+    return result;
+}

With this change, the same standalone passes all 8 rows on glibc (bits identical, errno preserved) and on macOS. It doesn't affect Windows, where scalbn already leaves errno alone per your native comparison. If you agree, scalbnf_nid_postfix/scalbn_nid_postfix probably want the same treatment in a follow-up. Your 100k native sweep shows the titles never set errno there either. I haven't run the full guest_math binary on Linux, only the standalone that copies its rows. The flag sweep is untouched, because restoring errno doesn't touch the FP environment.

glibc's scalbnf sets ERANGE on overflow and on underflow to zero, so on
Linux the scalbn body still changed errno and CheckLdexpfErrno failed
5 of its 8 rows (reported by rooklz). ldexpf now saves and restores
errno around the call, as fmod does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JJenkx

JJenkx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

@rooklz thanks, you're right: on glibc std::scalbn on a float sets ERANGE, which my Windows-only run couldn't show. Pushed cb09427a74 with your suggested change: ldexpf saves and restores errno around the std::scalbn call, as fmod does.

Rechecked on Windows at that head: guest_math, guest_math_exact, guest_dinkumware_internals and public_headers pass 3/3. The native sweep against PPSA07923 and PPSA05916 is still 0 value and 0 errno differences in 100,000 ldexpf pairs. I updated the description: the claim that main's scalbnf matches the titles now says Windows only, and the Linux/macOS section cites your run.

I'll take scalbn/scalbnf on Linux as a separate follow-up.

AI-assisted: yes (Claude Code).

@JJenkx

JJenkx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up opened as #12681: scalbn and scalbnf now also keep errno across the host call. A native probe shows the titles' whole scalbn/scalbln family never sets errno, even at exponents of ±(2^47 − 1), while double ldexp sets ERANGE there, as main already does. Windows can't give that PR a negative control, since UCRT doesn't set errno here, so a Linux run would be welcome if you have the image handy.

AI-assisted: yes (Claude Code).

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@JJenkx Thanks for the careful differential. A Linux run on this PR (the host-only review comment) shows CheckLdexpfErrno failing on glibc: std::scalbn(float, int) sets ERANGE there for overflow and underflow to zero, which is the same behaviour #12681 fixes for scalbn/scalbnf. Merging #12676 with #12681 (either order, no conflict) leaves ldexpf_nid_postfix as a bare std::scalbn(x, exp), so it is not covered by the guard added to scalbnf_nid_postfix and still changes errno on Linux. Could you make ldexpf_nid_postfix return scalbnf_nid_postfix(x, exp) (it is defined earlier in the file), or save and restore errno in it, so the test passes on glibc as well as on Windows? #12681 can land first; nothing else in the PR needs to change.

@JJenkx

JJenkx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

@oneandonlydean thanks. That change is already on the PR: the current head cb09427a74 (pushed after rooklz's review) saves and restores errno in ldexpf_nid_postfix itself:

float APS5_VABI ldexpf_nid_postfix(float x, int exp) {
    const int saved_errno = errno;
    const float result = std::scalbn(x, exp);
    errno = saved_errno;
    return result;
}

So it doesn't depend on #12681's guard, and the two can land in either order. The Linux failure you describe matches the earlier head 03f5cfafe1.

AI-assisted: yes (Claude Code).

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 6085 core/libs/prx/libc/src/Math.cpp
PR 6295 core/libs/prx/libc/src/Math.cpp
core/libs/tests/GuestMath.cpp

@JJenkx

JJenkx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Linux run at this PR's exact head cb09427a74 (cb09427a748279128307f5b48b42e822d34ba1da), not merged onto anything else; its base on main is 3a2b919816.

Arch Linux, kernel 7.2.9, Ryzen 9 7950X, g++ (GCC) 16.2.1 20260810, glibc 2.44, x86-64. Release, BUILD_TESTING=ON, Ninja -j4, private worktree.

  • ctest -R '^guest_(math|math_exact|dinkumware_internals)$': passes in each of 3 runs (9 passes).

@github-actions github-actions Bot added the libc label Oct 29, 2026
adriadam10 pushed a commit that referenced this pull request Oct 29, 2026
The titles' scalbn and scalbnf never set errno, at any exponent. On
Windows the host scalbn already matches, but glibc's scalbn and scalbnf
set ERANGE on overflow and on underflow to zero (found by rooklz while
reviewing #12676), so on Linux they did. Both now keep errno across the
host call, as scalbln and scalblnf already do. CheckScalbnErrno checks
results read from the titles' libc.prx.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adriadam10
adriadam10 merged commit f0c8f26 into boykopovar:main Oct 29, 2026
18 of 19 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the diff, the 8 CheckLdexpfErrno rows (bits and errno kept) and the errno save/restore around std::scalbn, which covers the glibc ERANGE rooklz found; Windows CI green and the author's Linux run at this head passes; merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants