# HG changeset patch # User Bruno Haible # Date 1174789786 0 # Node ID 52719799a90e0bbae0a1d6a679491d3eed9a728f # Parent be99958bc9bc0ccab216a48500e19ab4856ace7d New module 'fpucw'. diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,33 @@ +2007-03-24 Bruno Haible + + * modules/fpucw: New file. + * lib/fpucw.h: New file. + * lib/frexp.c: Include fpucw.h. + (DECL_ROUNDING, BEGIN_ROUNDING, END_ROUNDING): New macros. + (FUNC): Use them. + * lib/printf-frexp.c: Include fpucw.h. + (DECL_ROUNDING, BEGIN_ROUNDING, END_ROUNDING): New macros. + (FUNC): Use them. + * lib/vasnprintf.c: Include fpucw.h. + (VASNPRINTF): Invoke BEGIN/END_LONG_DOUBLE_ROUNDING around the + 'long double' calculations. + * tests/test-frexpl.c: Include fpucw.h. + (main): Invoke BEGIN_LONG_DOUBLE_ROUNDING. + * tests/test-printf-frexpl.c: Include fpucw.h. + (main): Invoke BEGIN_LONG_DOUBLE_ROUNDING. + * modules/frexpl (Depends-on): Add fpucw. + * modules/printf-frexpl (Depends-on): Likewise. + * modules/fprintf-posix (Depends-on): Likewise. + * modules/snprintf-posix (Depends-on): Likewise. + * modules/sprintf-posix (Depends-on): Likewise. + * modules/vasnprintf-posix (Depends-on): Likewise. + * modules/vasprintf-posix (Depends-on): Likewise. + * modules/vfprintf-posix (Depends-on): Likewise. + * modules/vsnprintf-posix (Depends-on): Likewise. + * modules/vsprintf-posix (Depends-on): Likewise. + * modules/frexpl-tests (Depends-on): Likewise. + * modules/printf-frexpl-tests (Depends-on): Likewise. + 2007-03-24 Bruno Haible * lib/float+.h: New file. diff --git a/lib/fpucw.h b/lib/fpucw.h new file mode 100644 --- /dev/null +++ b/lib/fpucw.h @@ -0,0 +1,108 @@ +/* Manipulating the FPU control word. + Copyright (C) 2007 Free Software Foundation, Inc. + Written by Bruno Haible , 2007. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _FPUCW_H +#define _FPUCW_H + +/* The i386 floating point hardware (the 387 compatible FPU, not the modern + SSE/SSE2 hardware) has a controllable rounding precision. It is specified + through the 'PC' bits in the FPU control word ('fctrl' register). (See + the GNU libc i386 header for details.) + + On some platforms, such as Linux or Solaris, the default precision setting + is set to "extended precision". This means that 'long double' instructions + operate correctly, but 'double' computations often produce slightly + different results as on strictly IEEE 754 conforming systems. + + On some platforms, such as NetBSD, the default precision is set to + "double precision". This means that 'long double' instructions will operate + only as 'double', i.e. lead wrong results. + + The FPU control word is under control of the application, i.e. it is + not required to be set either way by the ABI. (In fact, the i386 ABI + http://refspecs.freestandards.org/elf/abi386-4.pdf page 3-12 = page 38 + is not clear about it. But in any case, gcc treats the control word + like a "preserved" register: it emits code that assumes that the control + word is preserved across calls, and it restores the control word at the + end of functions that modify it.) + + See Vincent Lefèvre's page http://www.vinc17.org/research/extended.en.html + for a good explanation. + See http://www.uwsg.iu.edu/hypermail/linux/kernel/0103.0/0453.html for + some argumentation which setting should be the default. */ + +/* This header file provides the following facilities: + fpucw_t integral type holding the value of 'fctrl' + FPU_PC_MASK bit mask denoting the precision control + FPU_PC_DOUBLE precision control for 53 bits mantissa + FPU_PC_EXTENDED precision control for 64 bits mantissa + GET_FPUCW () yields the current FPU control word + SET_FPUCW (word) sets the FPU control word + DECL_LONG_DOUBLE_ROUNDING variable declaration for + BEGIN/END_LONG_DOUBLE_ROUNDING + BEGIN_LONG_DOUBLE_ROUNDING () starts a sequence of instructions with + 'long double' safe operation precision + END_LONG_DOUBLE_ROUNDING () ends a sequence of instructions with + 'long double' safe operation precision + */ + +/* Inline assembler like this works only with GNU C. */ +#if defined __i386__ && defined __GNUC__ + +typedef unsigned short fpucw_t; /* glibc calls this fpu_control_t */ + +# define FPU_PC_MASK 0x0300 +# define FPU_PC_DOUBLE 0x200 /* glibc calls this _FPU_DOUBLE */ +# define FPU_PC_EXTENDED 0x300 /* glibc calls this _FPU_EXTENDED */ + +# define GET_FPUCW() \ + ({ fpucw_t _cw; \ + __asm__ __volatile__ ("fnstcw %0" : "=m" (*&_cw)); \ + _cw; \ + }) +# define SET_FPUCW(word) \ + (void)({ fpucw_t _ncw = (word); \ + __asm__ __volatile__ ("fldcw %0" : : "m" (*&_ncw)); \ + }) + +# define DECL_LONG_DOUBLE_ROUNDING \ + fpucw_t oldcw; +# define BEGIN_LONG_DOUBLE_ROUNDING() \ + (void)(oldcw = GET_FPUCW (), \ + SET_FPUCW ((oldcw & ~FPU_PC_MASK) | FPU_PC_EXTENDED)) +# define END_LONG_DOUBLE_ROUNDING() \ + SET_FPUCW (oldcw) + +#else + +typedef unsigned int fpucw_t; + +# define FPU_PC_MASK 0 +# define FPU_PC_DOUBLE 0 +# define FPU_PC_EXTENDED 0 + +# define GET_FPUCW() 0 +# define SET_FPUCW(word) (void)(word) + +# define DECL_LONG_DOUBLE_ROUNDING +# define BEGIN_LONG_DOUBLE_ROUNDING() +# define END_LONG_DOUBLE_ROUNDING() + +#endif + +#endif /* _FPUCW_H */ diff --git a/lib/frexp.c b/lib/frexp.c --- a/lib/frexp.c +++ b/lib/frexp.c @@ -28,6 +28,7 @@ # include # ifdef USE_LONG_DOUBLE # include "isnanl-nolibm.h" +# include "fpucw.h" # else # include "isnan.h" # endif @@ -40,11 +41,17 @@ # define FUNC frexpl # define DOUBLE long double # define ISNAN isnanl +# define DECL_ROUNDING DECL_LONG_DOUBLE_ROUNDING +# define BEGIN_ROUNDING() BEGIN_LONG_DOUBLE_ROUNDING () +# define END_ROUNDING() END_LONG_DOUBLE_ROUNDING () # define L_(literal) literal##L # else # define FUNC frexp # define DOUBLE double # define ISNAN isnan +# define DECL_ROUNDING +# define BEGIN_ROUNDING() +# define END_ROUNDING() # define L_(literal) literal # endif @@ -53,6 +60,7 @@ { int sign; int exponent; + DECL_ROUNDING /* Test for NaN, infinity, and zero. */ if (ISNAN (x) || x + x == x) @@ -68,6 +76,8 @@ sign = -1; } + BEGIN_ROUNDING (); + { /* Since the exponent is an 'int', it fits in 64 bits. Therefore the loops are executed no more than 64 times. */ @@ -149,8 +159,13 @@ /* Here 0.5 <= x < 1.0. */ } + if (sign < 0) + x = - x; + + END_ROUNDING (); + *exp = exponent; - return (sign < 0 ? - x : x); + return x; } #else diff --git a/lib/printf-frexp.c b/lib/printf-frexp.c --- a/lib/printf-frexp.c +++ b/lib/printf-frexp.c @@ -28,6 +28,9 @@ # include # include +# ifdef USE_LONG_DOUBLE +# include "fpucw.h" +# endif /* This file assumes FLT_RADIX = 2. If FLT_RADIX is a power of 2 greater than 2, or not even a power of 2, some rounding errors can occur, so that @@ -42,6 +45,9 @@ # define FREXP frexpl # define LDEXP ldexpl # endif +# define DECL_ROUNDING DECL_LONG_DOUBLE_ROUNDING +# define BEGIN_ROUNDING() BEGIN_LONG_DOUBLE_ROUNDING () +# define END_ROUNDING() END_LONG_DOUBLE_ROUNDING () # define L_(literal) literal##L # else # define FUNC printf_frexp @@ -52,6 +58,9 @@ # define FREXP frexp # define LDEXP ldexp # endif +# define DECL_ROUNDING +# define BEGIN_ROUNDING() +# define END_ROUNDING() # define L_(literal) literal # endif @@ -59,6 +68,9 @@ FUNC (DOUBLE x, int *exp) { int exponent; + DECL_ROUNDING + + BEGIN_ROUNDING (); # ifdef USE_FREXP_LDEXP /* frexp and ldexp are usually faster than the loop below. */ @@ -170,6 +182,8 @@ or 1.0 <= x < 2.0 and exponent >= MIN_EXP - 1. */ # endif + END_ROUNDING (); + *exp = exponent; return x; } diff --git a/lib/vasnprintf.c b/lib/vasnprintf.c --- a/lib/vasnprintf.c +++ b/lib/vasnprintf.c @@ -57,6 +57,7 @@ # if HAVE_LONG_DOUBLE # include "isnanl-nolibm.h" # include "printf-frexpl.h" +# include "fpucw.h" # endif #endif @@ -415,6 +416,9 @@ else { int sign = 0; + DECL_LONG_DOUBLE_ROUNDING + + BEGIN_LONG_DOUBLE_ROUNDING (); if (arg < 0.0L) { @@ -542,6 +546,8 @@ while (*p != '\0') p++; } + + END_LONG_DOUBLE_ROUNDING (); } } else diff --git a/modules/fprintf-posix b/modules/fprintf-posix --- a/modules/fprintf-posix +++ b/modules/fprintf-posix @@ -14,6 +14,7 @@ isnanl-nolibm printf-frexp printf-frexpl +fpucw configure.ac: gl_FUNC_FPRINTF_POSIX diff --git a/modules/fpucw b/modules/fpucw new file mode 100644 --- /dev/null +++ b/modules/fpucw @@ -0,0 +1,21 @@ +Description: +Set the FPU control word, so as to allow correct 'long double' computations. + +Files: +lib/fpucw.h + +Depends-on: + +configure.ac: + +Makefile.am: + +Include: +"fpucw.h" + +License: +LGPL + +Maintainer: +Bruno Haible + diff --git a/modules/frexpl b/modules/frexpl --- a/modules/frexpl +++ b/modules/frexpl @@ -9,6 +9,7 @@ Depends-on: math isnanl-nolibm +fpucw configure.ac: gl_FUNC_FREXPL diff --git a/modules/frexpl-tests b/modules/frexpl-tests --- a/modules/frexpl-tests +++ b/modules/frexpl-tests @@ -2,6 +2,7 @@ tests/test-frexpl.c Depends-on: +fpucw configure.ac: diff --git a/modules/printf-frexpl b/modules/printf-frexpl --- a/modules/printf-frexpl +++ b/modules/printf-frexpl @@ -11,6 +11,7 @@ Depends-on: math +fpucw configure.ac: gl_FUNC_PRINTF_FREXPL diff --git a/modules/printf-frexpl-tests b/modules/printf-frexpl-tests --- a/modules/printf-frexpl-tests +++ b/modules/printf-frexpl-tests @@ -2,6 +2,7 @@ tests/test-printf-frexpl.c Depends-on: +fpucw configure.ac: diff --git a/modules/snprintf-posix b/modules/snprintf-posix --- a/modules/snprintf-posix +++ b/modules/snprintf-posix @@ -13,6 +13,7 @@ isnanl-nolibm printf-frexp printf-frexpl +fpucw configure.ac: gl_FUNC_SNPRINTF_POSIX diff --git a/modules/sprintf-posix b/modules/sprintf-posix --- a/modules/sprintf-posix +++ b/modules/sprintf-posix @@ -13,6 +13,7 @@ isnanl-nolibm printf-frexp printf-frexpl +fpucw configure.ac: gl_FUNC_SPRINTF_POSIX diff --git a/modules/vasnprintf-posix b/modules/vasnprintf-posix --- a/modules/vasnprintf-posix +++ b/modules/vasnprintf-posix @@ -12,6 +12,7 @@ isnanl-nolibm printf-frexp printf-frexpl +fpucw configure.ac: gl_FUNC_VASNPRINTF_POSIX diff --git a/modules/vasprintf-posix b/modules/vasprintf-posix --- a/modules/vasprintf-posix +++ b/modules/vasprintf-posix @@ -12,6 +12,7 @@ isnanl-nolibm printf-frexp printf-frexpl +fpucw configure.ac: gl_FUNC_VASPRINTF_POSIX diff --git a/modules/vfprintf-posix b/modules/vfprintf-posix --- a/modules/vfprintf-posix +++ b/modules/vfprintf-posix @@ -14,6 +14,7 @@ isnanl-nolibm printf-frexp printf-frexpl +fpucw configure.ac: gl_FUNC_VFPRINTF_POSIX diff --git a/modules/vsnprintf-posix b/modules/vsnprintf-posix --- a/modules/vsnprintf-posix +++ b/modules/vsnprintf-posix @@ -13,6 +13,7 @@ isnanl-nolibm printf-frexp printf-frexpl +fpucw configure.ac: gl_FUNC_VSNPRINTF_POSIX diff --git a/modules/vsprintf-posix b/modules/vsprintf-posix --- a/modules/vsprintf-posix +++ b/modules/vsprintf-posix @@ -13,6 +13,7 @@ isnanl-nolibm printf-frexp printf-frexpl +fpucw configure.ac: gl_FUNC_VSPRINTF_POSIX diff --git a/tests/test-frexpl.c b/tests/test-frexpl.c --- a/tests/test-frexpl.c +++ b/tests/test-frexpl.c @@ -24,6 +24,8 @@ #include #include +#include "fpucw.h" + #define ASSERT(expr) if (!(expr)) abort (); static long double @@ -41,6 +43,9 @@ { int i; long double x; + DECL_LONG_DOUBLE_ROUNDING + + BEGIN_LONG_DOUBLE_ROUNDING (); { /* NaN. */ int exp = -9999; diff --git a/tests/test-printf-frexpl.c b/tests/test-printf-frexpl.c --- a/tests/test-printf-frexpl.c +++ b/tests/test-printf-frexpl.c @@ -24,6 +24,8 @@ #include #include +#include "fpucw.h" + #define ASSERT(expr) if (!(expr)) abort (); static long double @@ -41,6 +43,9 @@ { int i; long double x; + DECL_LONG_DOUBLE_ROUNDING + + BEGIN_LONG_DOUBLE_ROUNDING (); for (i = 1, x = 1.0L; i <= LDBL_MAX_EXP; i++, x *= 2.0L) {