The floating point environment

J

jacob navia

Hi people

I continue to work in the tutorial for lcc-win32, and started to try to
explain the floating point flags.

Here is the relevant part of the tutorial. Since it is a difficult part,
I would like your expert advise before I publish any serious nonsense.

Any comments are welcome, style, organization, hard errors, etc.

Thanks in advance

jacob
-------------------------------------------------------------------


Using the floating point environment
------------------------------------

The C standard specifies a type fenv_t that “refers collectively to any
floating-point status flags and control modes supported by the
implementation.”

This environment has two special parts, the “floating point status
flags” that are set by the operations the program performs, and the
“control flags” that changes how the hardware does the operations, for
instance the rounding mode, etc.

The status flags
----------------
The standard requires at least the following flags:
FE_DIVBYZERO A division by zero has occurred
FE_INEXACT The last operation had to be rounded
FE_INVALID An invalid operation was attempted
FE_OVERFLOW The result of an operation was too big to be represented
FE_UNDERFLOW The result of an operation was too small to be represented

In addition to this standard flags, lcc-win32 provides:
FE_DENORMAL The result of the last operation was a denormal number
FE_STACKFAULT The floating point stack has overflowed.

The denormal flag means that a loss of precision is certain, the number
is too small to be represented. The stack fault flag means that
lcc-win32 generated bad code, since all floating point operations should
balance the floating point stack. If you ever test positive for this
flag, do not hesitate to send me a bug report!

In general, the floating point flags can be queried by the program to
know if the last operation did complete without problems. Here is a
small example to show you how this works:

/* This program tests a division by zero */
#include <fenv.h>
#include <stdio.h>
int main(void)
{
double a=1,b=0;
feclearexcept(FE_DIVBYZERO);
a=a/b;
if (fetestexcept(FE_DIVBYZERO)) {
printf("You have divided by zero!\n");
}
return 0;
}

First we clear the flag that we are going to use using feclearexcept.
Then, we perform our operation and query if the flag is set using
fetestexcept.

Since we know that the flags are set but not cleared, the expression
could be very well be a much more complicated sequence of operations.
The above code would work the same, but we would loose the possibility
of knowing exactly which operation failed. This is in many cases not
very important, we could very well be interested that somewhere there
was a serious error, without bothering to investigate which operation
was that it failed.

Reinitializing the floating point environment
---------------------------------------------

Things can become messy, and you would like to reset everything to a
known state. The standard provides the macro FE_DFL_ENV, that represents
the address of the default environment, the one you started with. In
lcc-win32 this environment is stored in the __default_fe_env global
variable, so this macro is just:

#define FE_DFL_ENV (&__default_fe_env)

You can reset everything to its default state with:

fesetenv(FE_DFL_ENV);

The default environment in lcc-win32 has the following characteristics:
1) The rounding mode is set to round to nearest.
2) The precision of the FPU is set to full precision (80 bits)
3) All exceptions are masked, i.e. the result of invalid operations is a
NAN, not a trap.

---------------------------------------------------------------------------
 
C

Christian Bau

jacob navia said:
Hi people

I continue to work in the tutorial for lcc-win32, and started to try to
explain the floating point flags.

Here is the relevant part of the tutorial. Since it is a difficult part,
I would like your expert advise before I publish any serious nonsense.

Any comments are welcome, style, organization, hard errors, etc.

Thanks in advance

jacob
-------------------------------------------------------------------


Using the floating point environment
------------------------------------

The C standard specifies a type fenv_t that ³refers collectively to any
floating-point status flags and control modes supported by the
implementation.²

This environment has two special parts, the ³floating point status
flags² that are set by the operations the program performs, and the
³control flags² that changes how the hardware does the operations, for
instance the rounding mode, etc.

The status flags

Are you sure it is "the last operation", and not any operation? Does
this flag get cleared again after an operation that doesn't need
rounding?
FE_INVALID An invalid operation was attempted
FE_OVERFLOW The result of an operation was too big to be represented
FE_UNDERFLOW The result of an operation was too small to be represented

In addition to this standard flags, lcc-win32 provides:
FE_DENORMAL The result of the last operation was a denormal number
FE_STACKFAULT The floating point stack has overflowed.

The denormal flag means that a loss of precision is certain, the number
is too small to be represented.

Do you mean "too small to be represented with full precision"?
The stack fault flag means that
lcc-win32 generated bad code, since all floating point operations should
balance the floating point stack. If you ever test positive for this
flag, do not hesitate to send me a bug report!

I don't like this at all. I wouldn't want to write code that checks if
the compiler is broken. If there is any possibility at all that this
happens, could you make this throw a hardware exception, so that it
definitely will get detected?
In general, the floating point flags can be queried by the program to
know if the last operation did complete without problems. Here is a
small example to show you how this works:

/* This program tests a division by zero */
#include <fenv.h>
#include <stdio.h>
int main(void)
{
double a=1,b=0;
feclearexcept(FE_DIVBYZERO);
a=a/b;
if (fetestexcept(FE_DIVBYZERO)) {
printf("You have divided by zero!\n");
}
return 0;
}

First we clear the flag that we are going to use using feclearexcept.
Then, we perform our operation and query if the flag is set using
fetestexcept.

Since we know that the flags are set but not cleared, the expression
could be very well be a much more complicated sequence of operations.
The above code would work the same, but we would loose the possibility
**** lose
of knowing exactly which operation failed. This is in many cases not
******

I wouldn't say that the operaton failed. Most floating-point operations
will set the FE_INEXACT flag, that doesn't mean they failed.
 
J

jacob navia

Christian Bau a écrit :
Are you sure it is "the last operation", and not any operation? Does
this flag get cleared again after an operation that doesn't need
rounding?

The x87 instruction manual specifies that by most arithmetic
operations, i.e. FADD FSUB, etc this flag is set/unset if the
result needed rounding. The internal flags (C0 C1 C3) specify
the direction of the rounding that was performed: up or down.
Do you mean "too small to be represented with full precision"?

Yes, you are right. Thanks.
I don't like this at all. I wouldn't want to write code that checks if
the compiler is broken. If there is any possibility at all that this
happens, could you make this throw a hardware exception, so that it
definitely will get detected?

Yes, it would just mean unmasking the exception flag, what would
provoke a trap if a FP stack overflow happens.
**** lose


******

I wouldn't say that the operaton failed. Most floating-point operations
will set the FE_INEXACT flag, that doesn't mean they failed.

True, will correct that.


Thanks for your input.

jacob
 
T

Thad Smith

jacob said:
The status flags

It would be much more straight forward to say that the result produced
was not the exact mathematical result of the operation. That avoids the
awkwardness of having to define "had to be rounded".
FE_INVALID An invalid operation was attempted
FE_OVERFLOW The result of an operation was too big to be represented
> FE_UNDERFLOW The result of an operation was too small to be
represented

Nit: The magnitude of the result was too large/small to be represented.
In addition to this standard flags, lcc-win32 provides:
FE_DENORMAL The result of the last operation was a denormal number
FE_STACKFAULT The floating point stack has overflowed.

The denormal flag means that a loss of precision is certain, the number
is too small to be represented.

The statement "loss of precision is certain" is accurate, but
misleading. It doesn't imply an erroneous value or loss of accuracy.

A denormalized number is too small to be represented with full
precision, but not too small to be represented at all.
In general, the floating point flags can be queried by the program to
know if the last operation did complete without problems. Here is a
suggest: completed ^^^^^^^^^^^^
small example to show you how this works:

/* This program tests a division by zero */
#include <fenv.h>
#include <stdio.h>
int main(void)
{
double a=1,b=0;
feclearexcept(FE_DIVBYZERO);
a=a/b;
if (fetestexcept(FE_DIVBYZERO)) {
printf("You have divided by zero!\n");
}
return 0;
}

First we clear the flag that we are going to use using feclearexcept.
First we clear the flag FE_DIVBYZERO using feclearexcept.
Then, we perform our operation and query if the flag is set using
fetestexcept.
We next perform the divide, then test the flag using fetestexcept.
Since we know that the flags are set but not cleared, the expression xxxxxxxxxxxx
could be very well be a much more complicated sequence of operations.
The above code would work the same, but we would loose the possibility
of knowing exactly which operation failed. This is in many cases not
very important, we could very well be interested that somewhere there
xxxx . We might only need to know
 
J

jacob navia

Thad Smith a écrit :
It would be much more straight forward to say that the result produced
was not the exact mathematical result of the operation. That avoids the
awkwardness of having to define "had to be rounded".

OK, it is a better formulation. Will change that.
represented

Nit: The magnitude of the result was too large/small to be represented.

Mmmm, if I use magnitude I would have to explain what does that mean
in this context. I do explain in a previous chapter the floating
point format, so I could go back to that and explain overflow

explicitely with normal --> denormal --> overflow
The statement "loss of precision is certain" is accurate, but
misleading. It doesn't imply an erroneous value or loss of accuracy.

A denormalized number is too small to be represented with full
precision, but not too small to be represented at all.


suggest: completed ^^^^^^^^^^^^


Yes, typo. Thanks
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top