Note: This is a long article. Please do not quote the entire content in
any response. Instead, quote only the parts relevant to your respond, per
Usenet netiquette.
Lynn McGuire said:
I tried _PC_53. Did not help.
Works fine for me.
Since you provide no specifics about exactly what you did that did not work,
we could only speculate (wildly) about the mistakes you might be making.
That would not be fruitful.
First and foremost, I want to reiterate a point that I made earlier in this
thread: generally, you cannot expect an expression like (num/denom)*denom -
num to result in exactly zero. (Unless num is zero <wink>.)
I assume your objective is only for the DLL code called from Excel/VBA to
have the same result as the DLL code (or the exact same source) called from
an exe file, zero or not.
But the point again is: if you rounded floating-point arithmetic to the
precision that you expect, which is the correct thing to do in general, you
probably would not have this problem in the first place, notwithstanding
differences in FP modes in the two environments.
(However, arguably, explicit rounding might be difficult or might not apply
at all in your application. No way for us to know.)
So much for my "PSA". Now let's compare notes....
-----
I am using Visual C++ Express 2010 to create a DLL.
It is true that when the DLL code is executed in the VC++ IDE, the FP
control word is set to _PC_53 + _RC_NEAR.
(That surprises me. So I suspect that all Visual Studio languages behave
the same way in this respect.)
In contrast, the FP control word is set to _PC_64 + _RC_NEAR when Excel
executes.
AFAIK, there is no way to alter that, in part because I believe that Excel
and VBA execute in different threads, and the FP control word is
thread-specific like all CPU registers.
VBA also does start with the FP control word set to _PC_64 + _RC_NEAR.
And based on my experiments, it appears that VBA does reset the FP control
word to _PC_64 each time Excel calls a VBA function and when we first start
to execute a "macro" (sub).
(Alternatively, VBA might reset the FP control word to _PC_64 when "an
execution" exits to Excel or terminates. I don't believe we can tell the
difference.)
Aside.... Interestingly, VBA does not change the rounding mode (_MCW_RC).
If we change it from _RC_NEAR, it remains in effect until we change it again
(or terminate Excel of course).
In any case, the important take-away is: if we set the FP control word
during "an execution" in VBA, it remains in effect until "the execution"
terminates or exits.
So we can set the FP control word to _RC_53 at the beginning of "an
execution" in VBA by calling a DLL function for that specific purpose (see
setpr below).
Alternatively, we can set the FP control word to _RC_53 in each DLL routine
or just one or two "key" DLL routines called during "the execution" (see
lynntest2 below).
Consider the following VBA example....
Note: Be sure to see some important comments about the DLL code below.
They might be pertinent to your problems.
------
Const num As Double = 4195835
Const denom As Double = 3145727
Private Sub testit1()
Debug.Print "--------------------"
Debug.Print "fpcw: "; myHex(getfpcw())
Call lynn1
Call lynn2 ' sets _PC_53
Call lynn1
End Sub
Private Sub testit2()
Debug.Print "--------------------"
Debug.Print "fpcw: "; myHex(getfpcw())
Debug.Print "fpcw: "; myHex(setpr(2, 0, 1)) ' sets _PC_53
Call lynn1
Call lynn1
End Sub
Private Sub lynn1()
Dim res As Double
res = lynntest1(num, denom)
Debug.Print "num: "; num; " denom: "; denom; _
" res: "; Format(res, "0.0000E+00")
Debug.Print "fpcw: "; myHex(getfpcw())
End Sub
Private Sub lynn2()
Dim res As Double
res = lynntest2(num, denom)
Debug.Print "num: "; num; " denom: "; denom; _
" res: "; Format(res, "0.0000E+00")
Debug.Print "fpcw: "; myHex(getfpcw())
End Sub
-----
The output from testit1 is:
fpcw: 0x00000000
num: 4195835 denom: 3145727 res: 2.8513E-10
fpcw: 0x00000000
num: 4195835 denom: 3145727 res: 0.0000E+00
fpcw: 0x00010000
num: 4195835 denom: 3145727 res: 0.0000E+00
fpcw: 0x00010000
The output from testit2 is:
fpcw: 0x00000000
fpcw: 0x00010000
num: 4195835 denom: 3145727 res: 0.0000E+00
fpcw: 0x00010000
num: 4195835 denom: 3145727 res: 0.0000E+00
fpcw: 0x00010000
Note that "res" is non-zero only when the FP control word is _PC_64 +
_RC_NEAR (0x00000000).
In contrast, "res" is zero when the FP control word is _PC_53 + _RC_NEAR
(0x00010000), as it is when the DLL code is executed in an exe or the VC++
IDE.
-----
myHex is an irrelevant formatting function.
getfpcw, setpr, lynntest1 and lynntest2 are DLL routines implemented in
VC++. They are declared in VBA as follows:
Declare Function getfpcw Lib "C:\...\fpcwlib.dll" () As Double
Declare Function setpr Lib "C:\...\fpcwlib.dll" _
(rc As Double, pc As Double, flag As Double) As Double
' lynntest1 does not change fpcw
Declare Function lynntest1 Lib "C:\...\fpcwlib.dll" _
(num As Double, denom As Double) As Double
' lynntest2 sets fpcw to _PC_53
Declare Function lynntest2 Lib "C:\...\fpcwlib.dll" _
(num As Double, denom As Double) As Double
Note: I use ByRef and Double so that the DLL functions can be called
"directly" from an Excel worksheet (for academic, not practical purposes),
even though the FP control word is an unsigned int (32 bits). It's an
irrelevant personal design choice. You might prefer something different.
The VC++ code is (see some important comments below)....
-----
#include <float.h>
#pragma fenv_access (on) // disable fp compile-time optimization
double __stdcall lynntest1(double &num, double &denom)
{
double div;
div = num / denom;
return div*denom - num;
}
double __stdcall lynntest2(double &num, double &denom)
{
// same as lynntest1, but sets _PC_53 itself
unsigned int curfpcw;
_controlfp_s(&curfpcw, 0, 0);
_controlfp_s(&curfpcw, curfpcw, _MCW_PC + _MCW_RC); // see footnotes
_controlfp_s(&curfpcw, _PC_53, _MCW_PC);
return lynntest1(num, denom);
}
double __stdcall getfpcw()
{
unsigned int curfpcw;
_controlfp_s(&curfpcw, 0, 0);
_controlfp_s(&curfpcw, curfpcw, _MCW_PC + _MCW_RC); // see footnotes
return curfpcw & (_MCW_PC + _MCW_RC);
}
double __stdcall setpr(double &pc, double &rc, double &flag0)
{
// pc = 1, 2, 3 for _PC_64, _PC_53, _PC_24}
// rc = 1, 2, 3, 4 for _RC_NEAR, _RC_DOWN, _RC_UP, _RC_CHOP
// flag0 = 1, 2, 3 for MCW_PC, MCW_RC, MCW_PC + MCW_RC
unsigned int fpcw, mask, flag;
unsigned int curfpcw;
flag = (unsigned int)flag0;
fpcw = 0;
mask = 0;
if (flag & 1)
{
switch ((int)pc)
{
case 1: fpcw = _PC_64; mask = _MCW_PC; break;
case 2: fpcw = _PC_53; mask = _MCW_PC; break;
case 3: fpcw = _PC_24; mask = _MCW_PC; break;
}
}
if (flag & 2)
{
switch ((int)rc) {
case 1: fpcw += _RC_NEAR; mask += _MCW_RC; break;
case 2: fpcw += _RC_DOWN; mask += _MCW_RC; break;
case 3: fpcw += _RC_UP; mask += _MCW_RC; break;
case 4: fpcw += _RC_CHOP; mask += _MCW_RC; break;
}
}
_controlfp_s(&curfpcw, 0, 0);
_controlfp_s(&curfpcw, curfpcw, _MCW_PC + _MCW_RC); // see footnotes
if (mask != 0) _controlfp_s(&curfpcw, fpcw, mask);
return curfpcw & (_MCW_PC + _MCW_RC);
}
-----
Notes on DLL code....
1. I use _controlfp_s because _control87 and _controlfp are deprecated. I
also tested _controlfp, and I saw no difference. I did not test _control87.
2. For reliability reasons, I always get and re-set the current FP control
word before setting it per requirements. I discovered an inexplicable
problem when setting only _PC_64 when _PC_64 is already set and the rounding
mode is not _RC_NEAR. FP arithmetic behaves as if _PC_64 + _RC_NEAR is set,
even though getfpcw shows that _RC_NEAR is not set(!).
Based on my experiments, this is not a problem when setting only _PC_53.
But I leave the reliable implementation in place for your edification and
just in case you use this code to set other FP modes.
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 (of course).
I don't know if or how you can avoid compile-time FP optimization in other
Visual Studio languages.
4. setpr is designed to abstract (hide) the details of the FP control word.
It seems easier than remembering, combining and passing the equivalent hex
constants in VBA. This is an irrelevant personal choice. You might prefer
something else.