mailing list of musl libc
 help / color / mirror / code / Atom feed
From: Szabolcs Nagy <nsz@port70.net>
To: John M <johnm@braincalibration.de>
Cc: musl@lists.openwall.com
Subject: Re: [musl] Wrong rounding in printf when precision is not set to max
Date: Mon, 1 Jan 2024 14:43:09 +0100	[thread overview]
Message-ID: <20240101134309.GN1427497@port70.net> (raw)
In-Reply-To: <ZZKBretdrHqBM1Ph@alberich>

* John M <johnm@braincalibration.de> [2024-01-01 10:11:09 +0100]:
> I noticed printf rounding wrongly when FPU Control Word[PC] != 11.
> 
> I do not think my workaround is a fix, it just serves to show where the
> reduced precision breaks the rounding.
> 
> Do you consider this a bug?

the precision control setting of x87 on i386 and x86-64 is ABI
and on linux must be double extended (64bit pc=0b11, not 53bit
pc=0b10 nor 24bit pc=0b00)

if you change the setting then the behaviour is undefined, it's
not just the libc that may misbeahave, but the compiler and
other libraries too.

> If yes, do you have an idea or comments on how a real fix would look
> like?

any x87 fp arithmetic will round the result to sinle prec float,
so the only ways to "fix" this is to use pure integer arithmetics
or change the precision setting around the internal fp computations.

i suspect glibc uses int arithmetics to compute the digits that's
why it works there, but that's just luck: glibc does not support
non-default precision setting when you call its apis either.

musl could do the same, but you would have to ensure the code
works for all supported long double formats which i guess would be
harder to do with pure int arithmetics.

> 
> Test case:
> ---
> // x86_64-pc-linux-gnu-gcc float.c -o float-glibc
> // x86_64-pc-linux-musl-gcc float.c -o float-musl
> 
> #include <stdio.h>
> 
> #define STREFLOP_FSTCW(cw) do { asm volatile ("fstcw %0" : "=m" (cw) : ); } while (0)
> #define STREFLOP_FLDCW(cw) do { asm volatile ("fclex \n fldcw %0" : : "m" (cw)); } while (0)
> 
> void cw_set(unsigned short x87_mode) {
>     STREFLOP_FLDCW(x87_mode);
> }
> 
> void cw_print() {
>     unsigned short x87_mode;
>     STREFLOP_FSTCW(x87_mode);
>     printf("FPU Control Word: 0x%04X\n", x87_mode);
> }
> 
> int main() {
>     cw_print();
>     printf("%11.3f\n", 1200.12345);
>     printf("%11.3f\n", 3.14159274);
> 
>     cw_set(0x007F);
>     cw_print();
>     // FIXME: musl rounds this up to 1200.124 when FPU Control Word[PC] != 11. glibc does round correctly.
>     printf("%11.3f\n", 1200.12345);
>     // FIXME: musl rounds this down to 3.141 when FPU Control Word[PC] != 11. glibc does round correctly.
>     printf("%11.3f\n", 3.14159274);
> 
>     return 0;
> }
> --
> 
> My workaround:
> ---
> diff --git a/src/stdio/vfprintf.c b/src/stdio/vfprintf.c
> index 497c5e19..9b2bdb8c 100644
> --- a/src/stdio/vfprintf.c
> +++ b/src/stdio/vfprintf.c
> @@ -13,6 +13,26 @@
> 
>  /* Some useful macros */
> 
> +#define STREFLOP_FSTCW(cw) do { __asm__ volatile ("fstcw %0" : "=m" (cw) : ); } while (0)
> +#define STREFLOP_FLDCW(cw) do { __asm__ volatile ("fclex \n fldcw %0" : : "m" (cw)); } while (0)
> +
> +unsigned short x87_mode_old;
> +void cw_push(unsigned short flags) {
> +    STREFLOP_FSTCW(x87_mode_old);
> +    unsigned short x87_mode = x87_mode_old | flags;
> +    STREFLOP_FLDCW(x87_mode);
> +}
> +
> +void cw_pop() {
> +    STREFLOP_FLDCW(x87_mode_old);
> +}
> +
> +void cw_print() {
> +    unsigned short x87_mode;
> +    STREFLOP_FSTCW(x87_mode);
> +    printf("FPU Control Word: 0x%04X\n", x87_mode);
> +}
> +
>  #define MAX(a,b) ((a)>(b) ? (a) : (b))
>  #define MIN(a,b) ((a)<(b) ? (a) : (b))
> 
> @@ -318,6 +338,7 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
>  		x = *d % i;
>  		/* Are there any significant digits past j? */
>  		if (x || d+1!=z) {
> +			cw_push(0x0300);
>  			long double round = 2/LDBL_EPSILON;
>  			long double small;
>  			if ((*d/i & 1) || (i==1000000000 && d>a && (d[-1]&1)))
> @@ -337,6 +358,7 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
>  				}
>  				for (i=10, e=9*(r-a); *a>=i; i*=10, e++);
>  			}
> +			cw_pop();
>  		}
>  		if (z>d+1) z=d+1;
>  	}
> --

  reply	other threads:[~2024-01-01 13:43 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-01-01  9:11 John M
2024-01-01 13:43 ` Szabolcs Nagy [this message]
2024-01-01 17:47   ` Rich Felker
2024-01-02 10:16     ` Florian Weimer
2024-01-02 21:08       ` Rich Felker

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240101134309.GN1427497@port70.net \
    --to=nsz@port70.net \
    --cc=johnm@braincalibration.de \
    --cc=musl@lists.openwall.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/musl/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).