Excel and the Math Coprocessor for DLLs

J

joeu2004

Errata.... I said:
In contrast, the FP control word is set to
_PC_64 + _RC_NEAR when Excel executes.

Well, we cannot distinguish between _PC_53 and _PC_64 in Excel.

Excel certainly behaves that way. I always assumed that was because Excel
stores each pairwise operation into 64-bit memory, in contrast to VBA which
does not.

But we cannot distinguish between that behavior and setting _PC_53.

(In contrast, we can distinguish among the various rounding modes. So we
know that Excel uses _RC_NEAR, either by default or by setting it.)

I thought I had remembered stumbling across a situation that demonstrates
that SUM() behaves as if _PC_64 is set. But I cannot remember the
situation. And now I realize I was thinking of a different anomaly of Excel
floating-point arithmetic.

Note that if we call a DLL function to alter the precision mode "directly"
from an Excel formula, apparently the call to DLL is occurring the VBA
thread. So it has no impact on the Excel thread.

3. Note the #pragma fenv_access (on) directive.
This is needed per VC++ documentation. Otherwise,
compile-time FP optimization (e.g. evaluation of constant FP
subexpressions) might not have the effect
intended by changing the FP control word at runtime

Although that might be relevant to someone who requires a particular effect,
it is probably not relevant to Lynn's situation for two reasons.

First, Lynn is not trying to have a "particular effect", but simply to
duplicate the behavior of DLL code in both VBA and exe environments.

Second, Lynn's particular example is not likely to benefit from any
compile-time evaluations.
 
J

joeu2004

Arrgghh! I miswrote:
Well, we cannot distinguish between _PC_53 and _PC_64 in Excel.

Excel certainly behaves that way. I always assumed that was because Excel
stores each pairwise operation into 64-bit memory, in contrast to VBA
which does not.

But we cannot distinguish between that behavior and setting _PC_53.

Due to some last-minute over-editing, the second paragraph might reasonably
be misread.

By "behaves that way", I meant _PC_53. But since I always assumed (until
now) that Excel does not alter the FP mode, I presumed the "53-bit" behavior
was due to Excel storing pairwise operations into memory.

However, now that it appears that VBA alters the FP mode (between
"executions"), it is possible that Excel does as well, perhaps setting
_PC_53.
 
L

Lynn McGuire

By "behaves that way", I meant _PC_53. But since I always assumed (until
now) that Excel does not alter the FP mode, I presumed the "53-bit"
behavior was due to Excel storing pairwise operations into memory.

However, now that it appears that VBA alters the FP mode (between
"executions"), it is possible that Excel does as well, perhaps setting
_PC_53.

BTW, there are two Win32 DLLs. The first DLL is
written in C and handles the VB/VBA interface.
The second DLL is written in F77 and does all the
floating point calcs. I used the Open Watcom C
and F77 compilers but Visual Studio 2005 C++
exhibits the problems also. I have tested using
both Excel 2003 and Excel 2010.

I have created a test app at
www.winsim.com/testdll.zip
that is a micro model but does exhibit the problem.
If you want to take a look, please download and
unzip into "c:\testdll". If you change that
directory then you will need to change the VBA
current working directory code.

Just load testdll.xls into Excel (I am using Excel
2003 but any should work OK). Then press the RUN
button on the spreadsheet. The first time, all is
OK. The second time, the chptst calc in the C DLL
code exhibits the calc failure and the chptst code
in the F77 DLL code fails. Weird !

I commented the calls to capsmn (the F77 DLL) and
the math coprocessor function. The C DLL is still
having the problem with the chptst calc.

Thanks,
Lynn
 
J

joeu2004

Lynn McGuire said:
I have created a test app at
www.winsim.com/testdll.zip

When I open testdll.xls and look at CommandButton1_Click, the relevant code
that I see is a call to InitTestDLL.

When I look at InitTestDLL in testdll.c, the relevant code that I see is:

1. The chptst calculation, which is displayed using MessageBox.
2. A call to capsmn().
3. A call to checkMathCoprocessorStatus [sic].

When I look at checkMathCoprocessorStatus, the relevant code that I see is:

1. old87Control = _control87 (0, 0).
2. Commented-out code that might change the FP control word (FPCW).
3. new87result = _control87 (0, 0)
4. A call to MessageBox to display old87Control and new87Result.


Lynn McGuire said:
Just load testdll.xls into Excel (I am using Excel
2003 but any should work OK). Then press the RUN
button on the spreadsheet. The first time, all is
OK. The second time, the chptst calc in the C DLL
code exhibits the calc failure

By "OK", I presume you mean chptst is exactly zero. And by "failure", I
presume you mean that chptst is non-zero, namely about 2.851266E-10.

And that is indeed what I observe, as well, when I execute
CommandButton1_Click in testdll.xls.

But that behavior makes no sense based on my summary of the InitTestDLL and
checkMathCoprocessorStatus functions above.

(See the speculative explanation at the end below.)

The first time InitTestDLL is called, the FPCW should be set according to
VBA. We know that _PC_64 + _RC_NEAR. So chptst should be the non-zero
value ("failure").

The second time InitTestDLL is called, __if__ the FPCW were set to _RC_53,
chptst should be zero ("OK").

I believe my summaries above are correct.

So I conclude that the testdll.c file in the testdll.zip archive is __not__
the same(!) as the one used to create testdll.dll.

That is readily apparent since, at a minimum, the checkMathCoprocessorStatus
function in that testdll.c file has every line commented out that might
alter the state of the FPCW. So it would not modify the FPCW at all(!).

If you want to discuss this further with me, you will have to use the files
that I put into lynntest.zip, which you can download from
http://www.box.com/s/cedabb54b1026fce5f46.

There you will find a "clean" (distilled) implementation of InitTestDLL and
setMathCoprocessorStatus (now correctly named for its function), along with
a much-simplified VBA file.

Use testdll.c, testdll.h and testdll.def to create lynntest.dll with your
compiler. (I used VC++.)

Import lynntest.bas into VBA to test the behavior of InitTestDLL.

Note that you must the correct Lib path "C:\yourPathTo". And you need to
execute doit() manually (press F5), since I did not bother to set up a
"button" in Excel.

When I execute doit()....

1. The first time, chptst is non-zero, the old FPCW is 0xc001f, and the new
FPCW is 0xd001f, all as expected.

2. The subsequent times, chptst is zero, and the old and new FPCW are
0xd001f, again all as expected.

If you get different results(!) from those files, unmodified except for
"C:\yourPathTo", I would be surprised. Please let us know in either case.

Assuming you get the same results, use those files to correct your
implementation.

PS: It you do not want to call setMathCoprocessorStatus within your DLL
code, as you said before, I have declared setMathCoprocessorStatus to you
can all it (once?) from VBA.

But as I believe I explained previously, you must call it once for each
"execution" of VBA -- that is, from each function that you might call form
an Excel formula, and from each of the first "sub" procedures that you
invoke either manually or using Excel "buttons".


-----

Speculative explanation of the behavior of your testdll.xls and testdll.dll
.....

When I execute CommandButton1_Click in testdll.xls:

1. The first time, the old FPCW is 0x127f and the new FPCW is 0x137f.
2. Subsequent times, the old and new FPCW are 0x137f.

Note that _control87 returns an __abstraction__ of the machine FP control
word. I suspect you are expecting the _control87 values to be the same as
the machine FP control word. They are not.

Note that for _control87, 0xN2NN represents _RC_UP and 0xN3NN represents
_RC_CHOP. That might explain why chptst is zero the first time, but
non-zero subsequent times.

I suspect that when you built testdll.dll, you uncommented the following
line to set the FPCW:

new87result = _control87 (0x27f, 0xffff);

There we see 0x2NN, which would set _RC_UP(!). Look at the definitions of
the _control87 parameters in <float.h> and in
http://msdn.microsoft.com/en-us/library/e9b52ceh(v=vs.80).aspx.

I suspect you chose 0x27f, in part, because 0x2NN does indeed represent
53-bit precision in the machine FP control word. But as I noted above, that
is not the same format of the "FPCW" used as input for and output from
_control87.

We do not see where you set _control87(0x37f,0xffff). But I suspect you did
just that in the version of testdll.c that you used to create testdll.dll.

As I noted before, it is readily apparent the testdll.c included in
testdll.zip is not the version of testdll.c that you used to create
testdll.dll.

It would surprise me if _control87 itself changed _RC_UP to _RC_CHOP. But
since we do not know how _control87 is implemented, and since it is
obviously returning some undocumented bits, it is possible that a faulty
implementation within _control87 is responding incorrectly to the
undocumented bits that you set.
 
J

joeu2004

PS.... I wrote:
joeu2004 said:
When I execute doit()....

1. The first time, chptst is non-zero, the old FPCW is 0xc001f, and the
new FPCW is 0xd001f, all as expected.

2. The subsequent times, chptst is zero, and the old and new FPCW are
0xd001f, again all as expected.

I forgot to explain why those are the expected results.

With 0xc001f and 0xd001f, 0xNN0NN represents _RC_NEAR.

With 0xcNNNN represents _PC_64 since the lower 2 bits are 00.

With 0xd001f, 0xdNNNN represents _PC_53 since the lower 2 bits are 01.

Remember that those are the _control87 representations, not the
representation in the machine FPU control word.
 
M

Martin Brown

BTW, there are two Win32 DLLs. The first DLL is
written in C and handles the VB/VBA interface.
The second DLL is written in F77 and does all the
floating point calcs. I used the Open Watcom C
and F77 compilers but Visual Studio 2005 C++
exhibits the problems also. I have tested using
both Excel 2003 and Excel 2010.

I have created a test app at
www.winsim.com/testdll.zip
that is a micro model but does exhibit the problem.
If you want to take a look, please download and
unzip into "c:\testdll". If you change that
directory then you will need to change the VBA
current working directory code.

Just load testdll.xls into Excel (I am using Excel
2003 but any should work OK). Then press the RUN
button on the spreadsheet. The first time, all is
OK. The second time, the chptst calc in the C DLL
code exhibits the calc failure and the chptst code
in the F77 DLL code fails. Weird !

I commented the calls to capsmn (the F77 DLL) and
the math coprocessor function. The C DLL is still
having the problem with the chptst calc.

Glancing at your code the problem is that you have *not* used the right
control mask combination for precision control that the C and Fortran
compiler assumes about its floating point environment namely:

new87result = _control87 (_PC_53 + _RC_NEAR, _MCW_PC + _MCW_RC);

You want to force a 53 bit mantissa for REAL*8 variables.

The math coprocessor is *not* failing. The test you are using is
predicated on the math coprocessor being in the right mode. Set the
rounding mode incorrectly or work to full machine precision of 80bits
and the math coprocessor gives the answer computed with a mantissa of
64bits which due to rounding effects *IS* correctly non-zero.

See the spectrum of possible roundings that my toy C program produces
earlier in the thread or better still splice it into your code and
establish once and for all which rounding mode and precision you really
want to employ. You are chasing a phantom "fault" here.

Also the code in the dll does not match the source code in the ZIP :(
The strings used in msgboxes are different.
 
M

Martin Brown

Lynn McGuire said:
I have created a test app at
www.winsim.com/testdll.zip

When I open testdll.xls and look at CommandButton1_Click, the relevant
code that I see is a call to InitTestDLL.

When I look at InitTestDLL in testdll.c, the relevant code that I see is:

1. The chptst calculation, which is displayed using MessageBox.
2. A call to capsmn().
3. A call to checkMathCoprocessorStatus [sic].

When I look at checkMathCoprocessorStatus, the relevant code that I see is:

1. old87Control = _control87 (0, 0).
2. Commented-out code that might change the FP control word (FPCW).
3. new87result = _control87 (0, 0)
4. A call to MessageBox to display old87Control and new87Result.


Lynn McGuire said:
Just load testdll.xls into Excel (I am using Excel
2003 but any should work OK). Then press the RUN
button on the spreadsheet. The first time, all is
OK. The second time, the chptst calc in the C DLL
code exhibits the calc failure

By "OK", I presume you mean chptst is exactly zero. And by "failure", I
presume you mean that chptst is non-zero, namely about 2.851266E-10.

And that is indeed what I observe, as well, when I execute
CommandButton1_Click in testdll.xls.

But that behavior makes no sense based on my summary of the InitTestDLL
and checkMathCoprocessorStatus functions above.

(See the speculative explanation at the end below.)

Having had a look at a disassembly of the DLL code it looks like the C
bindings are resetting the numeric coprocessor on *exit* with an FPINIT
instruction which puts the arithmetic into 80bit mode nearest rounding.
The first time InitTestDLL is called, the FPCW should be set according
to VBA. We know that _PC_64 + _RC_NEAR. So chptst should be the non-zero
value ("failure").

Actually no - it looks like VBA/XL uses _PC_53 + _RC_NEAR on entry which
is why the test code gives the "right" answer first time through.

(although inside compiled floating point expressions computations on the
stack may well be done as if _PC_64 was selected)

And her compiler returns bare metal X87 status words 0x127F works
But on subsequent calls the FPRESET state 0x137F prevails since that is
what C has forced it to!

Third digit 2 vs 3 is 53bit mantissa REAL*8 vs 64bit mantissa REAL*10
The second time InitTestDLL is called, __if__ the FPCW were set to
_RC_53, chptst should be zero ("OK").

Indeed but since as its parting shot the C DLL resets the NPU to 80bit
mode this condition is not met. The failure is in the exit from the C
binding code. For once XL appears to be innocent on all counts.
 
L

Lynn McGuire

Glancing at your code the problem is that you have *not* used the right control mask combination for precision control that the C and
Fortran compiler assumes about its floating point environment namely:

new87result = _control87 (_PC_53 + _RC_NEAR, _MCW_PC + _MCW_RC);

You want to force a 53 bit mantissa for REAL*8 variables.

The math coprocessor is *not* failing. The test you are using is predicated on the math coprocessor being in the right mode. Set the
rounding mode incorrectly or work to full machine precision of 80bits and the math coprocessor gives the answer computed with a
mantissa of 64bits which due to rounding effects *IS* correctly non-zero.

See the spectrum of possible roundings that my toy C program produces earlier in the thread or better still splice it into your code
and establish once and for all which rounding mode and precision you really want to employ. You are chasing a phantom "fault" here.

Also the code in the dll does not match the source code in the ZIP :(
The strings used in msgboxes are different.

I am using the Open Watcom compilers, not the Visual
Studio compilers to build the DLLs.

Open Watcom ==> __386__

MSVC ==> _MSC_VER

Thanks,
Lynn
 
J

joeu2004

Martin Brown said:
Actually no - it looks like VBA/XL uses _PC_53 + _RC_NEAR
on entry which is why the test code gives the "right"
answer first time through.

I believe that is incorrect, at least regarding VBA.

Assuming _RC_NEAR is set, the following can be used to demonstrate the
difference between the precision modes:

double factor2 = 2^-24 + 2^-53 + 2^-55
double z = 1 + factor2 + factor2

if z-1 = 1.1920928977E-7, then _PC_64
if z-1 = 1.1920928999E-7, then _PC_53
if z-1 = 2.3841857910E-7, then _PC_24

In VBA, we get 1.19...77E-7. That demonstrates that VBA sets _PC_64
precision. (Or it does not change the FPU default. We cannot tell the
difference.)

In Excel, we get 1.19...99E-7. That suggests that Excel sets _PC_53.

And Excel might do that. But actually, we cannot tell the difference
between that v. Excel itself simply storing each pairwise (and unary)
operation into 64-bit memory, as I would expect it does.

My __guess__ is that Excel also sets _PC_64 (or it does not change the FPU
default). We simply cannot tell one way or another.


Martin Brown said:
(although inside compiled floating point expressions
computations on the stack may well be done as if _PC_64
was selected)

It is true that the (Intel-compatible) FPU performs each FP operation with
64-bit precision, i.e. the 80-bit floating-point. The conversion to the
selected precision occurs after each FP operation.

For _PC_64, the result is converted effectively to _PC_53 when the FP
register is stored into memory for a 64-bit (double) or 32-bit (single)
floating-point variable.

We can demonstrate that by setting _RC_UP and computing double z = 1 + 2^-63
+ 2^-63 and printing z-1.

When _PC_53 or _PC_24 is set, if the FPU performed operations with 53-bit or
24-bit precision, z would be exactly 1 and z-1 would be exactly zero because
2^-63 is beyond 53-bit or 24-bit precision.

However, the result of z-1 is not exactly zero.

Instead, if _RC_UP is set, the result z-1, where double z = 1 + 2^-63 +
2^63, is:

_PC_64: about 2.22E-16 (2^-52)
_PC_53: about 4.44E-16 (2^-51)
_PC_24: about 2.38E-07 (2^-22)

Explanation....

For _PC_53 and _PC_24, 1 + 2^-63 is rounded up to 1 + 2^-52 for _PC_53 and
to 1 + 2^-23 for _PC_24. Then that intermediate result + 2^-63 is rounded
up to 1 + 2^-51 for _PC_53 and to 1 + 2^-22 for _PC_24. (Note that 2^-51 =
2 * 2^-52, and 2^-22 = 2 * 2^-23.)

For _PC_64, 1 + 2^-63 can be represented exactly. And that intermediate
result + 2^-63 can be represented exactly as 1 + 2^-62. That final result
is rounded up to 1 + 2^-52 when it is stored into double z (64-bit
floating-point representation).


Martin Brown said:
And her compiler returns bare metal X87 status words
0x127F works
But on subsequent calls the FPRESET state 0x137F prevails
since that is what C has forced it to!

That is an interesting theory. You might be correct.

I assumed that _control87 returns the abstract values for _PC_64, _RC_NEAR
et al documented in
http://msdn.microsoft.com/en-us/library/e9b52ceh(v=vs.80).aspx.

I also read (misread?) that webpage to say that _control87 and _controlfp
used the same parameters and returned the same results.

And that seems to be the case on my Intel Pentium CPU using WinXP SP3, VC++
2010 and Excel 2010.

But your observation is indeed consistent (almost) with the values displayed
by the testdll.dll provided by Lynn and the FPU control word documentation
in www.intel.com/Assets/ja_JP/PDF/manual/253665.pdf.

And upon rereading the _controlfp webpage, one reasonable interpretation is
that _control87 __might_be__ platform-dependent, whereas _controlfp is
clearly described as being platform-independent. Only the latter is
described explicitly as platform-independent.

However, the only platform dependency discussed is the difference in
handling denormal control.

Moreover, 0xxx7x includes the representation of an apparently undocumented
bit, namely bit 6.

-----

In any case, we are both saying the same thing: the testdll.dll provided by
Lynn does not match the testdll.c file provided.

Also, Lynn should be using _controlfp or _controlfp_s, not _control87. I
believe I pointed that out before.
 
J

joeu2004

PS.... I said:
That is an interesting theory. You might be correct.

I assumed that _control87 returns the abstract values for
_PC_64, _RC_NEAR et al documented in
http://msdn.microsoft.com/en-us/library/e9b52ceh(v=vs.80).aspx.

I also read (misread?) that webpage to say that _control87
and _controlfp used the same parameters and returned the
same results.

And I believe my interpretation (no misreading) is reinforced by this
example in that webpage:

_control87( _EM_INVALID, _MCW_EM );
// DENORMAL is unmasked by this call
_controlfp( _EM_INVALID, _MCW_EM );
// DENORMAL exception mask remains unchanged

Note that the documented value for _EM_INVALID, 0x10, is not consistent with
the position of that bit in the FPU control word, namely 0x01.
 
M

Martin Brown

I can't quite prove it. The DLL is too large and confusing and I cannot
locate the actual test example inside it. It isn't quite consistent
since on checking the FPRESET state should be 0x037F according to the
datasheet so something has tweaked infinity handling elsewhere.

I need a version stripped to the absolute minimum bare bones and
preferably as an MSC project DLL to trace into the executable code and
or with an assembler listing of the generated code.

I can't figure out the differences between TESTDLL1.DLL and TESTDLL.DLL
the VBA only seems to reference the first, but both are locked when XL
is running the test.
And I believe my interpretation (no misreading) is reinforced by this
example in that webpage:

_control87( _EM_INVALID, _MCW_EM );
// DENORMAL is unmasked by this call
_controlfp( _EM_INVALID, _MCW_EM );
// DENORMAL exception mask remains unchanged

Note that the documented value for _EM_INVALID, 0x10, is not consistent
with the position of that bit in the FPU control word, namely 0x01.

I suspect that this is down to a difference between the implementation
on the Watcom C compiler and in the MS Visual Studio. Same symbolic
names are being used but with very different constant values!
 
J

joeu2004

Martin Brown said:
I need a version stripped to the absolute minimum bare
bones and preferably as an MSC project DLL to trace into
the executable code and or with an assembler listing of
the generated code.

Yes. That is what I provided in lynntest.zip. I wish Lynn would compile
that in his/her environment, report the results and make the zip archive of
the resulting DLL and other files.


Martin Brown said:
And I believe my interpretation (no misreading) is reinforced
by this example in that webpage:
_control87( _EM_INVALID, _MCW_EM );
// DENORMAL is unmasked by this call
_controlfp( _EM_INVALID, _MCW_EM );
// DENORMAL exception mask remains unchanged
[....]
I suspect that this is down to a difference between the
implementation on the Watcom C compiler and in the MS
Visual Studio. Same symbolic names are being used but
with very different constant values!

Yes. I came to the same conclusion while I was out biking and "gymming".
 
L

Lynn McGuire

Lynn McGuire said:
I have created a test app at
www.winsim.com/testdll.zip

When I open testdll.xls and look at CommandButton1_Click, the relevant code that I see is a call to InitTestDLL.

When I look at InitTestDLL in testdll.c, the relevant code that I see is:

1. The chptst calculation, which is displayed using MessageBox.
2. A call to capsmn().
3. A call to checkMathCoprocessorStatus [sic].

When I look at checkMathCoprocessorStatus, the relevant code that I see is:

1. old87Control = _control87 (0, 0).
2. Commented-out code that might change the FP control word (FPCW).
3. new87result = _control87 (0, 0)
4. A call to MessageBox to display old87Control and new87Result.


Lynn McGuire said:
Just load testdll.xls into Excel (I am using Excel
2003 but any should work OK). Then press the RUN
button on the spreadsheet. The first time, all is
OK. The second time, the chptst calc in the C DLL
code exhibits the calc failure

By "OK", I presume you mean chptst is exactly zero. And by "failure", I presume you mean that chptst is non-zero, namely about
2.851266E-10.

And that is indeed what I observe, as well, when I execute CommandButton1_Click in testdll.xls.

But that behavior makes no sense based on my summary of the InitTestDLL and checkMathCoprocessorStatus functions above.

(See the speculative explanation at the end below.)

The first time InitTestDLL is called, the FPCW should be set according to VBA. We know that _PC_64 + _RC_NEAR. So chptst should be
the non-zero value ("failure").

The second time InitTestDLL is called, __if__ the FPCW were set to _RC_53, chptst should be zero ("OK").

I believe my summaries above are correct.

So I conclude that the testdll.c file in the testdll.zip archive is __not__ the same(!) as the one used to create testdll.dll.

That is readily apparent since, at a minimum, the checkMathCoprocessorStatus function in that testdll.c file has every line commented
out that might alter the state of the FPCW. So it would not modify the FPCW at all(!).

If you want to discuss this further with me, you will have to use the files that I put into lynntest.zip, which you can download from
http://www.box.com/s/cedabb54b1026fce5f46.

There you will find a "clean" (distilled) implementation of InitTestDLL and setMathCoprocessorStatus (now correctly named for its
function), along with a much-simplified VBA file.

Use testdll.c, testdll.h and testdll.def to create lynntest.dll with your compiler. (I used VC++.)

Import lynntest.bas into VBA to test the behavior of InitTestDLL.

Note that you must the correct Lib path "C:\yourPathTo". And you need to execute doit() manually (press F5), since I did not bother
to set up a "button" in Excel.

When I execute doit()....

1. The first time, chptst is non-zero, the old FPCW is 0xc001f, and the new FPCW is 0xd001f, all as expected.

2. The subsequent times, chptst is zero, and the old and new FPCW are 0xd001f, again all as expected.

If you get different results(!) from those files, unmodified except for "C:\yourPathTo", I would be surprised. Please let us know in
either case.

Assuming you get the same results, use those files to correct your implementation.

PS: It you do not want to call setMathCoprocessorStatus within your DLL code, as you said before, I have declared
setMathCoprocessorStatus to you can all it (once?) from VBA.

But as I believe I explained previously, you must call it once for each "execution" of VBA -- that is, from each function that you
might call form an Excel formula, and from each of the first "sub" procedures that you invoke either manually or using Excel "buttons".


-----

Speculative explanation of the behavior of your testdll.xls and testdll.dll ....

When I execute CommandButton1_Click in testdll.xls:

1. The first time, the old FPCW is 0x127f and the new FPCW is 0x137f.
2. Subsequent times, the old and new FPCW are 0x137f.

Note that _control87 returns an __abstraction__ of the machine FP control word. I suspect you are expecting the _control87 values to
be the same as the machine FP control word. They are not.

Note that for _control87, 0xN2NN represents _RC_UP and 0xN3NN represents _RC_CHOP. That might explain why chptst is zero the first
time, but non-zero subsequent times.

I suspect that when you built testdll.dll, you uncommented the following line to set the FPCW:

new87result = _control87 (0x27f, 0xffff);

There we see 0x2NN, which would set _RC_UP(!). Look at the definitions of the _control87 parameters in <float.h> and in
http://msdn.microsoft.com/en-us/library/e9b52ceh(v=vs.80).aspx.

I suspect you chose 0x27f, in part, because 0x2NN does indeed represent 53-bit precision in the machine FP control word. But as I
noted above, that is not the same format of the "FPCW" used as input for and output from _control87.

We do not see where you set _control87(0x37f,0xffff). But I suspect you did just that in the version of testdll.c that you used to
create testdll.dll.

As I noted before, it is readily apparent the testdll.c included in testdll.zip is not the version of testdll.c that you used to
create testdll.dll.

It would surprise me if _control87 itself changed _RC_UP to _RC_CHOP. But since we do not know how _control87 is implemented, and
since it is obviously returning some undocumented bits, it is possible that a faulty implementation within _control87 is responding
incorrectly to the undocumented bits that you set.

Been busy today.

There is no XLS file in the lynntest ZIP file.
Did you want me to use my own XLS file modified ?

Thanks,
Lynn
 
L

Lynn McGuire

I can't quite prove it. The DLL is too large and confusing and I cannot locate the actual test example inside it. It isn't quite
consistent since on checking the FPRESET state should be 0x037F according to the datasheet so something has tweaked infinity handling
elsewhere.

I need a version stripped to the absolute minimum bare bones and preferably as an MSC project DLL to trace into the executable code
and or with an assembler listing of the generated code.

I can't figure out the differences between TESTDLL1.DLL and TESTDLL.DLL the VBA only seems to reference the first, but both are
locked when XL is running the test.


I suspect that this is down to a difference between the implementation on the Watcom C compiler and in the MS Visual Studio. Same
symbolic names are being used but with very different constant values!

BTW, Lynn is a he. Michael Lynn McGuire.

TestDLL.dll is created using my testdll.c code and
the Open Watcom C compiler and linker.

TestDLL1.dll is created using my testdll1.f code
and the Open Watcom F77 compiler and linker.

TestDLL.dll loads TestDLL1.dll. TestDLL.dll used
to call testdll1.dll but does no longer since I
got the problem to duplicate in the c code.

Thanks,
Lynn
 
J

joeu2004

Lynn McGuire said:
Been busy today.
There is no XLS file in the lynntest ZIP file.
Did you want me to use my own XLS file modified ?

Good to hear that you will try that eventually.

To be on the safe side, simply open a new Excel instance, then import
lynntest.bas into VBA.

Note that you must first the correct Lib path "C:\yourPathTo" after
importing lynntest.bas. (Or you could edit lynntest.bas using Notepad.)

You will need to execute the VBA "doit" procedure manually by putting the
cursor somewhere in the procedure, then pressing F5.

Alternatively, you could set up an Excel "button", if you prefer.

Execute "doit" twice.

The first time should demonstrate the chptst computation with VBA's FPU
settings, presumably _PC_64 + _RC_NEAR.

The second time should demonstrate the chptst computation with the FPU
setting altered by the previous execution.

Note: Do not change the setMathCoprocessorStatus routine. I left a lot
#elif directives to make it easy to experiment with your mistakes of the
past. But in hindsight, we are only interested in how _control87(_PC_53,
_MCW_PC) behaves in your environment.

If your initial results do not match my expectations (see a previous
response), change _control87 to _controlfp in all places.
 
J

joeu2004

Lynn McGuire said:
TestDLL.dll is created using my testdll.c code and
the Open Watcom C compiler and linker.

Okay. My mistake.

There is a difference(!) when calling InitTestDLL from XL2003 VBA v. XL2010
VBA. And I misinterpreted the results from XL2003 VBA.

When InitTestDLL is called from XL2003 VBA, the last MsgBox displayed by
checkMathCoprocessorStatus says "was 0x127f" and "is now 0x127f" the first
time.

But subsequent times, it says "was 0x137f" and "is now 0x137f".

That led me to conclude that your DLL is changing the FPU control word in
some way completely unrelated to the code we see in your testdll.c.

But when InitTestDLL is called from XL2010 VBA, we see "was 0x137f" and "is
now 0x137f" for the first call and for all subsequent calls.

That is consistent with the code in your testdll.c

Ergo, the change that I see with XL2003 VBA is probably due to VBA, not your
code.

(Although I had used XL2003 for my initial test of your code, I had switched
to XL2010 unconsciously for my subsequent testing. Mea culpa!)

Based on Martin's assumption that your version of _control87 is displaying
the actual FPU control word, 0x127f corresponds to _PC_53 + _RC_NEAR, and
0x137f corresponds to _PC_64 + _RC_NEAR.

That would indeed explain the results that we observe with your testdll.dll,
namely: chptst is zero the first time, but about 2.85E-10 subsequent times
when using XL2003.

But when using XL2010, you see about 2.85E-10 consistently.

As Martin and I have said repeatedly, the remedy is for you to call
_control87(_PC53 + _RC_NEAR, _MCW_PC + _MCW_RC) at the __beginning__ of
InitTestDLL.

(Technically, _RC_NEAR and _MCW_RC are not needed since it appears that
_RC_NEAR is already set. But it is good "defensive programming" to set both
modes.)

Alternatively, as I explained previously, you can call a DLL routine
directly from the VBA procedure CommandButton1_Click. The DLL routine would
call _control87(_PC53 + _RCNEAR, _MCW_PC + _MCW_RC).

That would obviate the need to call _control87 from each DLL routine that
you might call from CommandButton1_Click.

But you would need to call that DLL routine from every VBA procedure that
you initiate, either with an Excel "button" or by calling a VBA function
from an Excel formula.

So arguably, it is more reliable to call _control87 (or a DLL routine) from
each entry point that allow to be called from VBA.
 
L

lhkittle

I have a large Win32 DLL (10 MB) that is called from
my user interface (written in C++) or from VBA in MS
Excel. In my user interface, the DLL runs in its
own space and calculates correctly. Under Excel VBA,
my DLL is having problems with double precision
accuracy. The following test passes in my user
interface but fails under my bad pentium test:

double precision chptst
double precision divtwo
double precision top
double precision bottom
data top / 4195835.0D0 /
data bottom / 3145727.0D0 /
DIVTWO = top / bottom
CHPTST = (DIVTWO * bottom) - top

In my user interface, the chptst result is zero.
Under Excel VBA, the chptst result is 0.2851266E-09.

I have tried resetting the math coprocessor in my
DLL with the following code but it is not working:

unsigned old87Status = 0;
unsigned new87ControlWord = 0;
unsigned new87ControlMask = 0;
unsigned new87result = 0;
old87Status = _status87 ();
if (old87Status != 0)
new87result = _control87 (new87ControlWord, new87ControlMask);

I have verified this behavior in both Excel 2003
and 2010. Does anyone have any ideas here ?

Sincerely,
Lynn McGuire
 
L

Lynn McGuire

Okay. My mistake.

There is a difference(!) when calling InitTestDLL from XL2003 VBA v.
XL2010 VBA. And I misinterpreted the results from XL2003 VBA.

When InitTestDLL is called from XL2003 VBA, the last MsgBox displayed by
checkMathCoprocessorStatus says "was 0x127f" and "is now 0x127f" the
first time.

But subsequent times, it says "was 0x137f" and "is now 0x137f".

That led me to conclude that your DLL is changing the FPU control word
in some way completely unrelated to the code we see in your testdll.c.

But when InitTestDLL is called from XL2010 VBA, we see "was 0x137f" and
"is now 0x137f" for the first call and for all subsequent calls.

That is consistent with the code in your testdll.c

Ergo, the change that I see with XL2003 VBA is probably due to VBA, not
your code.

(Although I had used XL2003 for my initial test of your code, I had
switched to XL2010 unconsciously for my subsequent testing. Mea culpa!)

Based on Martin's assumption that your version of _control87 is
displaying the actual FPU control word, 0x127f corresponds to _PC_53 +
_RC_NEAR, and 0x137f corresponds to _PC_64 + _RC_NEAR.

That would indeed explain the results that we observe with your
testdll.dll, namely: chptst is zero the first time, but about 2.85E-10
subsequent times when using XL2003.

But when using XL2010, you see about 2.85E-10 consistently.

As Martin and I have said repeatedly, the remedy is for you to call
_control87(_PC53 + _RC_NEAR, _MCW_PC + _MCW_RC) at the __beginning__ of
InitTestDLL.

(Technically, _RC_NEAR and _MCW_RC are not needed since it appears that
_RC_NEAR is already set. But it is good "defensive programming" to set
both modes.)

Alternatively, as I explained previously, you can call a DLL routine
directly from the VBA procedure CommandButton1_Click. The DLL routine
would call _control87(_PC53 + _RCNEAR, _MCW_PC + _MCW_RC).

That would obviate the need to call _control87 from each DLL routine
that you might call from CommandButton1_Click.

But you would need to call that DLL routine from every VBA procedure
that you initiate, either with an Excel "button" or by calling a VBA
function from an Excel formula.

So arguably, it is more reliable to call _control87 (or a DLL routine)
from each entry point that allow to be called from VBA.

I have tried this already when y'all first suggested
it. But I will try it again tomorrow in case I
screwed it up (totally possible !).

I also have a new theory that passing two character
strings to my DLL in the argument list are causing
problems. I intend to try that out also.

Thanks,
Lynn
 
J

joeu2004

I said:
There is a difference(!) when calling InitTestDLL
from XL2003 VBA v. XL2010 VBA. [....]
When InitTestDLL is called from XL2003 VBA, the last
MsgBox displayed by checkMathCoprocessorStatus says
"was 0x127f" and "is now 0x127f" the first time. But subsequent times, it
says "was 0x137f" and
"is now 0x137f". [....]
But when InitTestDLL is called from XL2010 VBA, we
see "was 0x137f" and "is now 0x137f" for the first
call and for all subsequent calls.

Okay, this is all just a little too weird even for me.

The difference in XL2010 VBA that I saw happens when I click on the
testdll.xls file icon, which opens with XL2010 on my system.

But when I open XL2010 and open testdll.xls from the Recent Workbooks list,
it behaves the same as XL2003 VBA above.

Moreover, despite displaying "was 0x137f" and "is now 0x137f", indicative of
_PC_64 if Martin is correct, the chptst test returns zero the first time,
indicative of _PC_53.

But that is inconsistent with the results of the following test, which I put
in the beginning of CommandButton1_Click:

Const factor As Double = 2 ^ -24 + 2 ^ -53 + 2 ^ -55
Const z As Double = 1 + factor + factor
Dim x As Double
x = 1 + factor: x = x + factor
MsgBox Format(z - 1, "0.0000000000E+00") & vbNewLine & _
Format(x - 1, "0.0000000000E+00")

The result of x-1 is about 1.1920928999E-7, which emulates _PC_53 precision.

The result of z-1 is about 1.1920928977E-7, which is indicative of _PC_64
precision.

I cannot explain these inconsistencies.

On the other hand, when I compile your testdll.c using VC++, I get
consistent results with XL2003 and XL2010 VBA, no matter how I start
testdll.xls (click on the file and open in XL2010, or start XL2003 and
XL2010 and open testdll.xls within).

With the recompiled testdll.c, chptst is always about 2.85E-10, indicative
of _PC_64 precision. And checkMathCoprocessorStatus always displays "was
0x0" and "is now 0x0", indicative of _PC64 + _RC_NEAR (based on VC++
values).

Notes:

1. When I recompiled testdll.c, I changed
sprintf(...,old87Status,old87Control,new87result) to
sprintf(...,old87Status,old87Control & mask,new87result & mask), where mask
is _MCW_PC + _MCW_RC. In other words, checkMathCoprocessorStatus only
displays the "PC" and "RC" states.)

2. Also, I commented out the call to capsmn in InitTestDLL, since I did not
know how to link to it. (I am new to VC++.)

Any conclusion on my part would be just wild speculation.
 
M

Martin Brown

Okay. My mistake.

There is a difference(!) when calling InitTestDLL from XL2003 VBA v.
XL2010 VBA. And I misinterpreted the results from XL2003 VBA.

When InitTestDLL is called from XL2003 VBA, the last MsgBox displayed by
checkMathCoprocessorStatus says "was 0x127f" and "is now 0x127f" the
first time.

But subsequent times, it says "was 0x137f" and "is now 0x137f".

That led me to conclude that your DLL is changing the FPU control word
in some way completely unrelated to the code we see in your testdll.c.

This is also the behaviour in XL2007 so it looks like XL2010 is the
point where MS went back to using full 64 bit mantissas (80 bit reals).
But when InitTestDLL is called from XL2010 VBA, we see "was 0x137f" and
"is now 0x137f" for the first call and for all subsequent calls.

That is consistent with the code in your testdll.c

If the compiler will spit out assembler and you delete everything but
the test code it should be possible to work out exactly what is going
on. I think this is a flaw in the Watcom C compilers treatment of FP
inside a DLL.
Ergo, the change that I see with XL2003 VBA is probably due to VBA, not
your code.

First time around the VBA/Excel environment hands the system over in the
state that it prefers, but I reckon that after the initialisation call
it subsequently returns it in the state that it thinks the client
routine actually wants to see it. IOW it is the C runtime of Watcom that
has forced 64bit mantissa somehow probably by FPINIT.

Writing a routine to return the result of reading the control word and
status words might well highlight the problems and portability issues.
(Although I had used XL2003 for my initial test of your code, I had
switched to XL2010 unconsciously for my subsequent testing. Mea culpa!)

Based on Martin's assumption that your version of _control87 is
displaying the actual FPU control word, 0x127f corresponds to _PC_53 +
_RC_NEAR, and 0x137f corresponds to _PC_64 + _RC_NEAR.

That would indeed explain the results that we observe with your
testdll.dll, namely: chptst is zero the first time, but about 2.85E-10
subsequent times when using XL2003.

But when using XL2010, you see about 2.85E-10 consistently.

As Martin and I have said repeatedly, the remedy is for you to call
_control87(_PC53 + _RC_NEAR, _MCW_PC + _MCW_RC) at the __beginning__ of
InitTestDLL.

Whilst this should mask the problem there are other issues. The test
will only work at all in the Microsoft compiler if all optimisation is
disabled for debug mode. With the optimiser on VC++ 2008 compiles to

flags87 = InitTestDLL();
008B1081 fldz

Which is going to work unconditionally under all circumstances.
(Technically, _RC_NEAR and _MCW_RC are not needed since it appears that
_RC_NEAR is already set. But it is good "defensive programming" to set
both modes.)

Alternatively, as I explained previously, you can call a DLL routine
directly from the VBA procedure CommandButton1_Click. The DLL routine
would call _control87(_PC53 + _RCNEAR, _MCW_PC + _MCW_RC).

That would obviate the need to call _control87 from each DLL routine
that you might call from CommandButton1_Click.

But you would need to call that DLL routine from every VBA procedure
that you initiate, either with an Excel "button" or by calling a VBA
function from an Excel formula.

So arguably, it is more reliable to call _control87 (or a DLL routine)
from each entry point that allow to be called from VBA.

Much more reliable. It is also disastrous to allow the optimising
compiler to attack the code when running simple minded numerical tests
with constant values. The division supposedly being tested *never*
occurs at runtime which is why I couldn't find it in the DLL.

I have now identified what passes for the test at location 4F4E in debug

To demonstrate this is the right place the following will do

DEBUG TESTDLL.DLL
u 4F42
a 4F50
FLDE

W
Q

You can also use FLD1, FLDZ, FLDPI and most of them will work exactly in
both modes which is disappointing but e forces a visibly different
numerical rounding error.

You can find the places where FP calcs are done by the incantation

s0l7fff,db,28
(might be other ways but this catches enough to be useful)

NB It works the first time through because the Watcom compiler has
special cased the calculation for the CW settings it expects to have as
follows (from 3C86):

U 3C86 gives...

FLD1
FLDZ
FDIVP ST(1),ST
FLD ST(0)
FCHS
FCOMPP ST(1)

Nothing like as aggressive as the Microsoft optimiser but it is not
testing the FP division instruction on the arguments you supplied!

And I think it is the final FINIT at the end of this section that puts
the x87 into the default 64bit mantissa mode.
 

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

Top