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 32343 invoked from network); 1 Jan 2024 09:11:24 -0000 Received: from second.openwall.net (193.110.157.125) by inbox.vuxu.org with ESMTPUTF8; 1 Jan 2024 09:11:24 -0000 Received: (qmail 3203 invoked by uid 550); 1 Jan 2024 09:10: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 3168 invoked from network); 1 Jan 2024 09:10:11 -0000 Date: Mon, 1 Jan 2024 10:11:09 +0100 From: John M To: musl@lists.openwall.com Message-ID: MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Subject: [musl] Wrong rounding in printf when precision is not set to max 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? If yes, do you have an idea or comments on how a real fix would look like? 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; } --