i386/75862: fpsetsticky() incorrectly clears, instead of sets, floating-point exception flags on IA-32

Nelson H. F. Beebe beebe at math.utah.edu
Wed Jan 5 10:10:42 PST 2005


>Number:         75862
>Category:       i386
>Synopsis:       fpsetsticky() incorrectly clears, instead of sets, floating-point exception flags on IA-32
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-i386
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Wed Jan 05 18:10:41 GMT 2005
>Closed-Date:
>Last-Modified:
>Originator:     Nelson H. F. Beebe
>Release:        4.4 and 5.0
>Organization:
University of Utah/Mathematics
>Environment:
FreeBSD XXXXXX.utah.edu 4.4-RELEASE FreeBSD 4.4-RELEASE #0: Tue Oct i386 i386 Pentium III/Pentium III Xeon/Celeron FreeBSD
FreeBSD XXXXXX.utah.edu 5.0-RELEASE FreeBSD 5.0-RELEASE #0: Thu Jan 16 22:16:53 GMT 2003     root at hollin.btc.adaptec.com:/usr/obj/usr/src/sys/GENERIC i386 i386 Pentium III/Pentium III Xeon/Celeron FreeBSD


>Description:
The fpsetsticky() macro in <flloatingpoint.h>, e.g.,
http://minnie.tuhs.org/FreeBSD-srctree/newsrc/i386/include/floatingpoint.h.html#fpgetsticky
is defined like this:
#define fpresetsticky(m)      ((fp_except_t) __fpsetreg(0, FP_STKY_REG, (m), FP_STKY_OFF))
#define fpsetsticky(m)	fpresetsticky(m)

That is wrong: fpresetsticky() CLEARS exception flags, and
fpsetsticky() SETS them.  The same API is present on HP-UX 10,
OpenBSD 3.2 and NetBSD 1.6, and works correctly on all of them.
It appears from the above URL that the error is long standing since
it is still the current 5.3 sources.

There is a secondary issue as well: "man fpgetsticky" does not
document fpsetsticky(), even though the latter is a critical
routine: one must be able to both set and test the floating-point
exception flags.


>How-To-Repeat:
The attached program produces this correct output on NetBSD, and OpenBSD:

cc test-invalid-bsd.c -lm && ./a.out 
After clear,         fpgetsticky() returns 0x00
After 1 / 0,         fpgetsticky() returns 0x04
z = inf
After clear,         fpgetsticky() returns 0x00
After nan * nan,     fpgetsticky() returns 0x00
z = nan
After clear,         fpgetsticky() returns 0x00
After inf - inf,     fpgetsticky() returns 0x01
z = nan
After clear,         fpgetsticky() returns 0x00
After big * big,     fpgetsticky() returns 0x28
z = inf
After clear,         fpgetsticky() returns 0x00
After small * small, fpgetsticky() returns 0x30
z = 0
After clear,         fpgetsticky() returns 0x00
After 1/3,           fpgetsticky() returns 0x20
z = 0.33333333333333331

For use on HP-UX, remove the include of <ieeefp.h> and #define
FP_X_DNML to 0; the output numbers are different (PA-RISC versus
IA-32), but still correct.

On FreeBSD, I get this:
cc test-invalid-bsd.c -lm && ./a.out 
After clear,         fpgetsticky() returns 0x05
After 1 / 0,         fpgetsticky() returns 0x05
z = inf
After clear,         fpgetsticky() returns 0x05
After nan * nan,     fpgetsticky() returns 0x05
z = nan
After clear,         fpgetsticky() returns 0x05
After inf - inf,     fpgetsticky() returns 0x05
z = nan
After clear,         fpgetsticky() returns 0x05
After big * big,     fpgetsticky() returns 0x2d
z = inf
After clear,         fpgetsticky() returns 0x2d
After small * small, fpgetsticky() returns 0x3d
z = 0
After clear,         fpgetsticky() returns 0x3d
After 1/3,           fpgetsticky() returns 0x3d
z = 0.33333333333333331

The returned flags from fpgetsticky() are nonsense, because
they were never set properly in the first place.

Here is the test program:

% cat test-invalid-bsd.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <ieeefp.h>

#if !defined(FP_X_STK)
#define FP_X_STK	0x40
#endif

#define FP_ALL (FP_X_INV | FP_X_DNML | FP_X_DZ | FP_X_OFL | FP_X_UFL | FP_X_IMP | FP_X_STK)

int
main(int argc, char* argv[])
{
    double inf, nan, x, y, z;
    x = 1.0;
    y = x;
    z = y - x;
    inf = x / z;
    nan = z / (x - y);

    fpsetsticky(~FP_ALL);
    (void)printf("After clear,         fpgetsticky() returns 0x%02x\n", fpgetsticky());
    z = x / z;
    (void)printf("After 1 / 0,         fpgetsticky() returns 0x%02x\n", fpgetsticky());
    (void)printf("z = %.17lg\n", z);

    fpsetsticky(~FP_ALL);
    (void)printf("After clear,         fpgetsticky() returns 0x%02x\n", fpgetsticky());
    z = nan * nan;
    (void)printf("After nan * nan,     fpgetsticky() returns 0x%02x\n", fpgetsticky());
    (void)printf("z = %.17lg\n", z);

    fpsetsticky(~FP_ALL);
    (void)printf("After clear,         fpgetsticky() returns 0x%02x\n", fpgetsticky());
    z = inf - inf; 
    (void)printf("After inf - inf,     fpgetsticky() returns 0x%02x\n", fpgetsticky());
    (void)printf("z = %.17lg\n", z);

    z = 1.0e+200;
    fpsetsticky(~FP_ALL);
    (void)printf("After clear,         fpgetsticky() returns 0x%02x\n", fpgetsticky());
    z = z * z;
    (void)printf("After big * big,     fpgetsticky() returns 0x%02x\n", fpgetsticky());
    (void)printf("z = %.17lg\n", z);

    z = 1.0e-200;
    fpsetsticky(~FP_ALL);
    (void)printf("After clear,         fpgetsticky() returns 0x%02x\n", fpgetsticky());
    z = z * z;
    (void)printf("After small * small, fpgetsticky() returns 0x%02x\n", fpgetsticky());
    (void)printf("z = %.17lg\n", z);

    z = 1.0;
    fpsetsticky(~FP_ALL);
    (void)printf("After clear,         fpgetsticky() returns 0x%02x\n", fpgetsticky());
    z = z / 3.0;
    (void)printf("After 1/3,           fpgetsticky() returns 0x%02x\n", fpgetsticky());
    (void)printf("z = %.17lg\n", z);

    return (EXIT_SUCCESS);
}


>Fix:
I found that this redefinition of fpsetsticky() solves the problem:

#define fpsetsticky(e)		((fp_except)__fpsetreg(0x3f, FP_STKY_REG, (e), FP_STKY_OFF))

With that change, I can now have a C99-style interface (sadly
still lacking the BSD world) like this:
#define feclearexcept(e)	(void)fpsetsticky(~((fp_except)(e)))
#define feraiseexcept(e)	(void)fpsetsticky(fpgetsticky() | ((fp_except)(e)))
#define fetestexcept(e)		((int)(fpgetsticky() & (fp_except)(e)))

where the C99 flags are defined like this:

#define FE_DIVBYZERO		FP_X_DZ
#define FE_INEXACT		FP_X_IMP
#define FE_INVALID		FP_X_INV
#define FE_OVERFLOW		FP_X_OFL
#define FE_UNDERFLOW		FP_X_UFL


>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-i386 mailing list