What is "INEXACT" in this code?

Hello everyone
In this following code I have a trouble in understanding the INEXACT. Is it a parameter or variable? and how it is defined later as INEXACT REAL?
Please help me to understand it, Thanks

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#define INEXACT

int start (REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe)
{
INEXACT REAL axby1, bxcy1, cxdy1, dxey1, exay1;
INEXACT REAL bxay1, cxby1, dxcy1, exdy1, axey1;
INEXACT REAL axcy1, bxdy1, cxey1, dxay1, exby1;
INEXACT REAL cxay1, dxby1, excy1, axdy1, bxey1;
.
.
Absolutely nothing :)

When you define something you're saying to the preprocessor: hey by the way, every time you see this, substitute this other thing (or every time I ask you if this is defined, say yes)

It is absolutely vital in the role it has.

However in this case you obviously have more code than what you're showing us. REAL is not defined in stdio.h- INEXACT has no bearing on that code, here's an example of what you can do:

1
2
3
4
5
6
7
8
#define PLSHLP
#define PENGUINS
#define EVERYWHERE int
#define BUTTER(a,b) ((a)<(b)?(a):(b))
int main(){
    PLSHLP PENGUINS EVERYWHERE THING = BUTTER(5,3);
    cout << a;
}


Thanks for your attention and help, it was really useful. Actually, there are some other libraries added as well, e.g.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
However, I have found some comments by the writer of the code, in which, he states that, the INEXACT type of values are used for inhibition of some arithmetic roundoff errors. Would you please consider these notes and exemplify me these kinds of problems, and also how INEXACT type helps to improve it.
I'm trying to convert its routines into fortran, and it is vital to understand every action.

code comments:
1
2
3
4
5
6
7
8
9
10
11
/* On some machines, the exact arithmetic routines might be defeated by the  */
/*   use of internal extended precision floating-point registers.  Sometimes */
/*   this problem can be fixed by defining certain values to be volatile,    */
/*   thus forcing them to be stored to memory and rounded off.  This isn't   */
/*   a great solution, though, as it slows the arithmetic down.              */
/*                                                                           */
/* To try this out, write "#define INEXACT volatile" below.  Normally,       */
/*   however, INEXACT should be defined to be nothing.  ("#define INEXACT".) */

#define INEXACT                          /* Nothing */
/* #define INEXACT volatile */



in other part of the code, he states a note about some functions as this:
1
2
3
4
5
6
7
8
9
10
11
12
/* Many of the operations are broken up into two pieces, a main part that    */
/*   performs an approximate operation, and a "tail" that computes the       */
/*   roundoff error of that operation.                                       */
/*                                                                           */
/* The operations Fast_Two_Sum(), Fast_Two_Diff(), Two_Sum(), Two_Diff(),    */
/*   Split(), and Two_Product() are all implemented as described in the      */
/*   reference.  Each of these macros requires certain variables to be       */
/*   defined in the calling routine.  The variables `bvirt', `c', `abig',    */
/*   `_i', `_j', `_k', `_l', `_m', and `_n' are declared `INEXACT' because   */
/*   they store the result of an operation that may incur roundoff error.    */
/*   The input parameter `x' (or the highest numbered `x_' parameter) must   */
/*   also be declared `INEXACT'.                                             */

I'm no expert on the subject but I have read that computers often have 80 bits floating-point registers while a double in C++ often is 64 bits. For the computer to carry out various operations (+, -, *, /, etc.) it has to use the registers so first the 64 bit doubles has to be read from memory and stored in the 80 bit registers, then the result of the operation is computed, and at the end the result will be written back to memory where it will be stored as a 64 bit value, something like that.

Writing and reading from memory is relatively slow. If the computed value are going to be used again very soon it might be faster to just keep it in the register and avoid the write to and back from memory. The only problem with this is that result might not be exactly the same because when the floating point value is converted from 80 bits to 64 bits it loses precision and you will of course not get this precision back when converting the 64 bit back to a 80 bits. This means that carrying out two operations that in theory should give the same result might be slightly different because they have some small rounding error.

Volatile means that the variable can be changed at any time by the hardware so when you use it on a variable it will prevent a lot of optimizations. It also prevents the optimization mentioned above, so the result of the floating point operations has to be written to the variable's location in memory right away. For this to work correctly I think you would have to avoid subexpressions, and instead store every computation in a volatile variable.

1
2
3
4
5
6
7
8
9
volatile double a = 1;
volatile double b = 2;

// Here we can't be sure if the result of (a / b) will have 80 or 64 bit precision.
volatile double c1 = a / b * 2;

// Here we know m1 will be 64 bits.
volatile double m1 = a / b;
volatile double c2 = m1 * 2;

In the header file you can see that #define INEXACT volatile has been commented out. Instead INEXACT is defined to nothing at all, which means that it will simply be ignored. The comment suggests the volatile hack is only needed on some computer architectures, but as I said I'm no expert on this so I don't know how much of a problem this is.

I also found this page https://gcc.gnu.org/wiki/x87note which I find a bit worrying because it suggests not even simple assignments like a = b; can be trusted with a == b giving true.
Last edited on
To add to Peter87 explanation:
http://ideone.com/be3490
Here you can see two values which should be equal are not compared equal. (Now compiler is updated, compiler flags are changed, so it does not work anymore. Do not try to fork this code).
I disassembled code (compiled on GCC 4.5 AFAIR) and found out that a is stored on stack (rounded to 64 bits), but b is never moved from FPU register (as it is needed right away). This behavior is standard, but surprising to most.

If you declare both variables as volatile http://ideone.com/xCL6q7
Then compiler will be forced to save both values (cutting it to 64 bit both) and then read them again to ensure that rounded values are compared.
Last edited on
Topic archived. No new replies allowed.