New module 'fpucw'.
authorBruno Haible <bruno@clisp.org>
Sun, 25 Mar 2007 02:29:46 +0000 (02:29 +0000)
committerBruno Haible <bruno@clisp.org>
Sun, 25 Mar 2007 02:29:46 +0000 (02:29 +0000)
20 files changed:
ChangeLog
lib/fpucw.h [new file with mode: 0644]
lib/frexp.c
lib/printf-frexp.c
lib/vasnprintf.c
modules/fprintf-posix
modules/fpucw [new file with mode: 0644]
modules/frexpl
modules/frexpl-tests
modules/printf-frexpl
modules/printf-frexpl-tests
modules/snprintf-posix
modules/sprintf-posix
modules/vasnprintf-posix
modules/vasprintf-posix
modules/vfprintf-posix
modules/vsnprintf-posix
modules/vsprintf-posix
tests/test-frexpl.c
tests/test-printf-frexpl.c

index 3409b07..8619055 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,35 @@
 2007-03-24  Bruno Haible  <bruno@clisp.org>
 
+       * 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  <bruno@clisp.org>
+
        * lib/float+.h: New file.
        * lib/isnan.c: Include float+.h.
        (SIZE): New macro.
diff --git a/lib/fpucw.h b/lib/fpucw.h
new file mode 100644 (file)
index 0000000..16aa4bc
--- /dev/null
@@ -0,0 +1,108 @@
+/* Manipulating the FPU control word.
+   Copyright (C) 2007 Free Software Foundation, Inc.
+   Written by Bruno Haible <bruno@clisp.org>, 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 <fpu_control.h> 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 */
index 3074f2e..701f00e 100644 (file)
@@ -28,6 +28,7 @@
 # include <float.h>
 # ifdef USE_LONG_DOUBLE
 #  include "isnanl-nolibm.h"
+#  include "fpucw.h"
 # else
 #  include "isnan.h"
 # endif
 #  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 @@ FUNC (DOUBLE x, int *exp)
 {
   int sign;
   int exponent;
+  DECL_ROUNDING
 
   /* Test for NaN, infinity, and zero.  */
   if (ISNAN (x) || x + x == x)
@@ -68,6 +76,8 @@ FUNC (DOUBLE x, int *exp)
       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 @@ FUNC (DOUBLE x, int *exp)
     /* Here 0.5 <= x < 1.0.  */
   }
 
+  if (sign < 0)
+    x = - x;
+
+  END_ROUNDING ();
+
   *exp = exponent;
-  return (sign < 0 ? - x : x);
+  return x;
 }
 
 #else
index e6032a4..978653d 100644 (file)
@@ -28,6 +28,9 @@
 
 # include <float.h>
 # include <math.h>
+# 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 @@ DOUBLE
 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 @@ FUNC (DOUBLE x, int *exp)
      or 1.0 <= x < 2.0 and exponent >= MIN_EXP - 1.  */
 # endif
 
+  END_ROUNDING ();
+
   *exp = exponent;
   return x;
 }
index 00419aa..75d33b3 100644 (file)
@@ -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 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar
                    else
                      {
                        int sign = 0;
+                       DECL_LONG_DOUBLE_ROUNDING
+
+                       BEGIN_LONG_DOUBLE_ROUNDING ();
 
                        if (arg < 0.0L)
                          {
@@ -542,6 +546,8 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar
                              while (*p != '\0')
                                p++;
                          }
+
+                       END_LONG_DOUBLE_ROUNDING ();
                      }
                  }
                else
index f79a8b4..2be06d0 100644 (file)
@@ -14,6 +14,7 @@ isnan-nolibm
 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 (file)
index 0000000..d03bad3
--- /dev/null
@@ -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
+
index 0bb8f58..465336e 100644 (file)
@@ -9,6 +9,7 @@ m4/frexpl.m4
 Depends-on:
 math
 isnanl-nolibm
+fpucw
 
 configure.ac:
 gl_FUNC_FREXPL
index 3dcaf11..461f47c 100644 (file)
@@ -2,6 +2,7 @@ Files:
 tests/test-frexpl.c
 
 Depends-on:
+fpucw
 
 configure.ac:
 
index 36d4f34..6d13762 100644 (file)
@@ -11,6 +11,7 @@ m4/longdouble.m4
 
 Depends-on:
 math
+fpucw
 
 configure.ac:
 gl_FUNC_PRINTF_FREXPL
index ab58971..7eb6983 100644 (file)
@@ -2,6 +2,7 @@ Files:
 tests/test-printf-frexpl.c
 
 Depends-on:
+fpucw
 
 configure.ac:
 
index a5a6ac1..65edc64 100644 (file)
@@ -13,6 +13,7 @@ isnan-nolibm
 isnanl-nolibm
 printf-frexp
 printf-frexpl
+fpucw
 
 configure.ac:
 gl_FUNC_SNPRINTF_POSIX
index 498e3d5..05d7d4f 100644 (file)
@@ -13,6 +13,7 @@ isnan-nolibm
 isnanl-nolibm
 printf-frexp
 printf-frexpl
+fpucw
 
 configure.ac:
 gl_FUNC_SPRINTF_POSIX
index e72337a..0f2aeb7 100644 (file)
@@ -12,6 +12,7 @@ isnan-nolibm
 isnanl-nolibm
 printf-frexp
 printf-frexpl
+fpucw
 
 configure.ac:
 gl_FUNC_VASNPRINTF_POSIX
index e07e3d3..d9bdf6d 100644 (file)
@@ -12,6 +12,7 @@ isnan-nolibm
 isnanl-nolibm
 printf-frexp
 printf-frexpl
+fpucw
 
 configure.ac:
 gl_FUNC_VASPRINTF_POSIX
index 1c0adb2..ce4bde8 100644 (file)
@@ -14,6 +14,7 @@ isnan-nolibm
 isnanl-nolibm
 printf-frexp
 printf-frexpl
+fpucw
 
 configure.ac:
 gl_FUNC_VFPRINTF_POSIX
index 4e42310..cfb35a6 100644 (file)
@@ -13,6 +13,7 @@ isnan-nolibm
 isnanl-nolibm
 printf-frexp
 printf-frexpl
+fpucw
 
 configure.ac:
 gl_FUNC_VSNPRINTF_POSIX
index af073f5..ef8b22c 100644 (file)
@@ -13,6 +13,7 @@ isnan-nolibm
 isnanl-nolibm
 printf-frexp
 printf-frexpl
+fpucw
 
 configure.ac:
 gl_FUNC_VSPRINTF_POSIX
index f98fcf1..447c3b9 100644 (file)
@@ -24,6 +24,8 @@
 #include <float.h>
 #include <stdlib.h>
 
+#include "fpucw.h"
+
 #define ASSERT(expr) if (!(expr)) abort ();
 
 static long double
@@ -41,6 +43,9 @@ main ()
 {
   int i;
   long double x;
+  DECL_LONG_DOUBLE_ROUNDING
+
+  BEGIN_LONG_DOUBLE_ROUNDING ();
 
   { /* NaN.  */
     int exp = -9999;
index 32c92e8..15cd91d 100644 (file)
@@ -24,6 +24,8 @@
 #include <float.h>
 #include <stdlib.h>
 
+#include "fpucw.h"
+
 #define ASSERT(expr) if (!(expr)) abort ();
 
 static long double
@@ -41,6 +43,9 @@ main ()
 {
   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)
     {