Format zero as 0x0p+0 in hexfloat - #4878
Open
hexonal wants to merge 1 commit into
Open
Conversation
fmt::format("{:a}", 0.0) produced 0x0p-1022 where printf's %a gives
0x0p+0, and likewise for -0.0, {:A}, {:#a} and any explicit precision.
basic_fp::assign maps a zero biased exponent to 1 ("subnormals use
biased exponent 1"), which is right for subnormals but not for zero, so
format_hexfloat inherited the minimum subnormal exponent for a value
whose significand carries no information. Reset the exponent there
rather than in assign, which the decimal path shares.
A zero significand can only come from +-0: normals get the implicit bit
added, and a subnormal has a nonzero significand by definition. So the
guard cannot affect anything else, and denorm_min keeps its existing
denormalized form (0x0.0000000000001p-1022, asserted in format_double).
The five new expectations were taken from printf rather than derived,
and all fail without this change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fmt::format("{:a}", 0.0)gives0x0p-1022whereprintf("%a", 0.0)gives0x0p+0:Same for
{:A},{:#a}and any explicit precision. Non-zero values already agree.Cause
basic_fp::assignmaps a zero biased exponent to 1:That is right for subnormals, but zero shares the same biased exponent and is not a subnormal, so
format_hexfloatends up printing the minimum subnormal exponent for a value whose significand carries no information.The fix resets the exponent in
format_hexfloatrather than inassign, since the decimal path sharesassignand relies on the subnormal mapping.Scope
A zero significand can only come from ±0 — normals get the implicit bit added, and a subnormal has a nonzero significand by definition — so the guard cannot affect any other value. I checked the neighbours explicitly:
denorm_min,min,max,±infandnanare byte-identical before and after, anddenorm_minkeeps fmt's existing denormalized form0x0.0000000000001p-1022thatformat_doublealready asserts.Testing
The five new expectations were taken from
printfoutput rather than derived by hand. All five fail without the change, with exactly the old values:With it,
ctestis 21/21 (Debug, macOS arm64, Apple clang 17).clang-format --dry-run -Werroris clean on both files. No existing test asserted the hexfloat form of zero, so nothing had to be updated.I took printf as the reference because of your comments on #4657 — "the reference implementation of
std::formatalways printed0xlike pretty much every other facility" — and LWG 4515. If you would rather keep zero consistent with how fmt prints subnormals instead, say so and I will close this.