Bug 30220 - String to double returns incorrectly rounded value for hexadecimal subnormal
Summary: String to double returns incorrectly rounded value for hexadecimal subnormal
Status: RESOLVED FIXED
Alias: None
Product: glibc
Classification: Unclassified
Component: math (show other bugs)
Version: unspecified
: P2 minor
Target Milestone: 2.41
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2023-03-10 22:50 UTC by Michael Jones
Modified: 2024-09-27 16:00 UTC (History)
0 users

See Also:
Host:
Target:
Build:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Michael Jones 2023-03-10 22:50:37 UTC
When evaluating the string "0x30000002222225p-1077" with atof or strtod in the NEAREST rounding mode, the returned value is 0x0.6000000444444p-1022 when it should be 0x0.6000000444445p-1022.

The easiest way to demonstrate this is by running the following line of code:
printf("%a\n%a\n", 0x30000002222225p-1077, atof("0x30000002222225p-1077"));

I discovered this bug with version 2.36, but it also occurs with a fresh checkout (latest commit at time of writing is e78c4c49831d0ca6253ff5ce5b956cdc4189c8a9).

When building glibc I used configure with the option "--prefix=$HOME/glibc-install/", my triple is x86_64-linux-gnu (host/build/target are the same), and I'm using gcc version 12.2.0.

I hope this is enough information to replicate my issue, thank you in advance.
--Michael
Comment 1 Michael Jones 2023-04-27 20:40:09 UTC
I've done a bit more testing and it seems that the issue is that when shifting away bits for a (hexadecimal) subnormal result, only the lowest bit is checked. If two bits will be shifted away, and only the second lowest bit is set, that bit won't be detected and it may be rounded down incorrectly. Here's an updated test case:

printf("%a\n%a\n\n%a\n%a\n",    
  0x0.7fffffffffffeap-1022, 
  atof("0x0.7fffffffffffeap-1022"),
  0x0.7fffffffffffe9p-1022, 
  atof("0x0.7fffffffffffe9p-1022")
  );

This gives the following result:

"
0x0.7ffffffffffffp-1022
0x0.7fffffffffffep-1022

0x0.7ffffffffffffp-1022
0x0.7ffffffffffffp-1022
"

As you can see, atof is mistakenly rounding the case ending in "ea" down despite it being higher than the case ending in "e9".

Hope this helps.
--Michael
Comment 2 Sourceware Commits 2024-08-27 12:41:18 UTC
The master branch has been updated by Joseph Myers <jsm28@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=457622c2fa8f9f7435822d5287a437bc8be8090d

commit 457622c2fa8f9f7435822d5287a437bc8be8090d
Author: Joseph Myers <josmyers@redhat.com>
Date:   Tue Aug 27 12:41:02 2024 +0000

    Fix strtod subnormal rounding (bug 30220)
    
    As reported in bug 30220, the implementation of strtod-family
    functions has a bug in the following case: the input string would,
    with infinite exponent range, take one more bit to represent than is
    available in the normal precision of the return type; the value
    represented is in the subnormal range; and there are no nonzero bits
    in the value, below those that can be represented in subnormal
    precision, other than the least significant bit and possibly the
    0.5ulp bit.  In this case, round_and_return ends up discarding the
    least significant bit.
    
    Fix by saving that bit to merge into more_bits (it can't be merged in
    at the time it's computed, because more_bits mustn't include this bit
    in the case of after-rounding tininess detection checking if the
    result is still subnormal when rounded to normal precision, so merging
    this bit into more_bits needs to take place after that check).
    
    Tested for x86_64.
Comment 3 Joseph Myers 2024-08-27 12:41:58 UTC
Fixed for 2.41.
Comment 4 Sourceware Commits 2024-09-27 15:57:17 UTC
The release/2.40/master branch has been updated by Arjun Shankar <arjun@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=d0c1792ad269566f877208ffda91c21dcd1a72e6

commit d0c1792ad269566f877208ffda91c21dcd1a72e6
Author: Joseph Myers <josmyers@redhat.com>
Date:   Tue Aug 27 12:41:02 2024 +0000

    Fix strtod subnormal rounding (bug 30220)
    
    As reported in bug 30220, the implementation of strtod-family
    functions has a bug in the following case: the input string would,
    with infinite exponent range, take one more bit to represent than is
    available in the normal precision of the return type; the value
    represented is in the subnormal range; and there are no nonzero bits
    in the value, below those that can be represented in subnormal
    precision, other than the least significant bit and possibly the
    0.5ulp bit.  In this case, round_and_return ends up discarding the
    least significant bit.
    
    Fix by saving that bit to merge into more_bits (it can't be merged in
    at the time it's computed, because more_bits mustn't include this bit
    in the case of after-rounding tininess detection checking if the
    result is still subnormal when rounded to normal precision, so merging
    this bit into more_bits needs to take place after that check).
    
    Tested for x86_64.
    
    (cherry picked from commit 457622c2fa8f9f7435822d5287a437bc8be8090d)
Comment 5 Sourceware Commits 2024-09-27 16:00:36 UTC
The release/2.39/master branch has been updated by Arjun Shankar <arjun@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=a7be595c67d12f1a442e3f6894d67e20c7724bed

commit a7be595c67d12f1a442e3f6894d67e20c7724bed
Author: Joseph Myers <josmyers@redhat.com>
Date:   Tue Aug 27 12:41:02 2024 +0000

    Fix strtod subnormal rounding (bug 30220)
    
    As reported in bug 30220, the implementation of strtod-family
    functions has a bug in the following case: the input string would,
    with infinite exponent range, take one more bit to represent than is
    available in the normal precision of the return type; the value
    represented is in the subnormal range; and there are no nonzero bits
    in the value, below those that can be represented in subnormal
    precision, other than the least significant bit and possibly the
    0.5ulp bit.  In this case, round_and_return ends up discarding the
    least significant bit.
    
    Fix by saving that bit to merge into more_bits (it can't be merged in
    at the time it's computed, because more_bits mustn't include this bit
    in the case of after-rounding tininess detection checking if the
    result is still subnormal when rounded to normal precision, so merging
    this bit into more_bits needs to take place after that check).
    
    Tested for x86_64.
    
    (cherry picked from commit 457622c2fa8f9f7435822d5287a437bc8be8090d)