From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-3.3 required=5.0 tests=MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.4 Received: (qmail 28488 invoked from network); 1 Jan 2024 13:43:23 -0000 Received: from second.openwall.net (193.110.157.125) by inbox.vuxu.org with ESMTPUTF8; 1 Jan 2024 13:43:23 -0000 Received: (qmail 32211 invoked by uid 550); 1 Jan 2024 13:42:11 -0000 Mailing-List: contact musl-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Reply-To: musl@lists.openwall.com Received: (qmail 32176 invoked from network); 1 Jan 2024 13:42:11 -0000 Date: Mon, 1 Jan 2024 14:43:09 +0100 From: Szabolcs Nagy To: John M Cc: musl@lists.openwall.com Message-ID: <20240101134309.GN1427497@port70.net> Mail-Followup-To: John M , musl@lists.openwall.com References: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: Subject: Re: [musl] Wrong rounding in printf when precision is not set to max * John M [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 > > #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; > } > --