Home |
Search |
Today's Posts |
#1
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
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 |
#2
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
Lynn,
I'm thinking that if your UI calls the DLL then it runs in-process rather than in its own space. If your UI is a C++ EXE then the same methodology is being applied to handle the DLL's returned value. As I stated in your previous post on 3/15, Excel/VBA handles double precision in its own way and so the return from your DLL got grabbed up in that process. As JoeU suggests, I suspect Excel is modifying the return for use with VBA. I use VB6 or PowerBasic DLLs and have no FPU discrepancies. You might get more help if you ask this in "comp.lang.C++"... -- Garry Free usenet access at http://www.eternal-september.org ClassicVB Users Regroup! comp.lang.basic.visual.misc microsoft.public.vb.general.discussion |
#3
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 21/03/2012 16:53, Lynn McGuire wrote:
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 think the problem is that your call to _control87(0,0) is a NOOP. Untested but I think _control87( _PC_64+_RC_NEAR, _MCW_PC+_MCW_RC); Ought to do the trick. Force 64 bit computation and nearest rounding. It could also be the case that in a pure C/C++ environment the final pass of the optimising compiler is smart enough to notice that your expression is identically zero at compile time. I have verified this behavior in both Excel 2003 and 2010. Does anyone have any ideas here ? Hope this helps. See the following for details http://msdn.microsoft.com/en-us/libr...(v=VS.60).aspx -- Regards, Martin Brown |
#4
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
"Lynn McGuire" wrote:
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. You are certainly looking at a floating-point anomaly; that is, sensitivity to 64-bit rounding. But I do not believe it can be explained by the FPU rounding mode alone. And while I might be able to suggest a solution for this particular example, in general, it is better to bullet-proof your arithmetic to correctly all numerical examples and computations. In general, you cannot expect 64-bit floating-point arithmetic to exactly equal a mathematical solution. All computer arithmetic is limited by a finite number of bits, whereas mathematics effectively relies on an infinite representation of information. In deference to the finite limitations of computer arithmetic in general, and 64-bit floating-point in particular, it is prudent to explicitly round any arithmetic result to the precision of accuracy that you require. That is, any arithmetic result involving non-integer operands, division, or integer operands or integer results greater than 2^53 in magnitude. ----- To address your particular example.... We can simulate the two different results using VBA in a manner that sheds some light on the problem. Consider the following two VBA procedures: Sub testit1() Const top As Double = 4195835# Const bottom As Double = 3145727# Dim chptst As Double Dim divtwo As Double divtwo = top / bottom chptst = (divtwo * bottom) - top MsgBox Format(chptst, "0.000E+00") End Sub Sub testit2() Const top As Double = 4195835# Const bottom As Double = 3145727# Dim chptst As Double Dim divtwo As Double chptst = ((top / bottom) * bottom) - top MsgBox Format(chptst, "0.000E+00") End Sub testit1 displays about 2.851E-10, whereas testit2 display 0.000E+00 -- exactly zero. The difference is that in testit1, the 80-bit floating-point result of top/bottom (the FPU of Intel CPUs use 80-bit floating-point internally) is rounded to a 64-bit floating-point result stored into divtwo. Then VBA uses the 64-bit divtwo in the computation of chptst. But in testit2, VBA does all of the computation with 80-bit precision, rounding to 64 bits only when storing the result into chptst. I do not know anything about Microsoft C++ or how C++ DLLs might work when called from VBA. But based on your observations, I suspect that when the code is compiled and linked in a C++ program, a better C++ compiler is used that optimizes the computation of chptst to use the 80-bit result of top/bottom despite the fact that you stored it into the 64-bit divtwo. However, when the DLL is compiled and linked into VBA, obviously C++ is using the 64-bit divtwo, just as testit1 does. (Although I refer to "a better compiler" as if there are two, the difference might actually be a difference in the behavior of __the__ so-called "back-end compiler"; that is, a phase of the C++ compiler.) Honestly, that does not make all that much sense to me based on my experience with (Unix) compilers. But that conclusion seems to be supported by my experiments below. The more reasonable assumption is that the 80-bit rounding to 64-bit is handled differently when the C++ DLL is called from VBA. However, I cannot duplicate the results of testit2 even when I modify testit1 in either of the following manners: 1. divtwo = top / bottom + 2^-52 ' add 1 to the least-significant bit 2. divtwo = top / bottom - 2^-52 ' sub 1 from the least-significant bit Those modifications do have the intended effect, which we can see when we look at the binary representation: 1. Original divtwo is 3FF55754,1C7C6B43. 2. Adding 2^-52, divtwo is 3FF55754,1C7C6B44. 3. Subtracting 2^-52, divtwo is 3FF55754,1C7C6B42. But the testit1 results in chptst a 1. With original divtwo, chptst is about 2.851E-10. 2. With divtwo + 2^-52, chptst is about 9.836E-10. 3. With divtwo - 2^-52, chptst is about -4.134E-10. Since none is exactly zero, as we see in testit2, I conclude that altering the rounding to 64-bit alone does determine the result in testit1, but the additional precision of 80-bit representation does. However, arguably, that is only conjecture. "Lynn McGuire" wrote: 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 presume that the intended purpose of this code is to change the FPU rounding mode. My conclusion above should explain why that does not work. Unfortunately, I do not know how to set and read the FPU control word in VBA. So I can offer a dispositive explanation. |
#5
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
"GS" wrote:
As JoeU suggests, I suspect Excel is modifying the return for use with VBA. I don't believe I "suggested" any such thing. What I believe I did say is: I would expect that each application sets the FPU rounding mode according to its own requirements. Whether Excel does that only one time at start-up or Excel and VBA restore it after returning from each call to a DLL, I cannot say. That would be the "defensive" thing to do. |
#6
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 3/21/2012 12:16 PM, GS wrote:
Lynn, I'm thinking that if your UI calls the DLL then it runs in-process rather than in its own space. If your UI is a C++ EXE then the same methodology is being applied to handle the DLL's returned value. As I stated in your previous post on 3/15, Excel/VBA handles double precision in its own way and so the return from your DLL got grabbed up in that process. As JoeU suggests, I suspect Excel is modifying the return for use with VBA. I use VB6 or PowerBasic DLLs and have no FPU discrepancies. You might get more help if you ask this in "comp.lang.C++"... My user interface calls a Win32 exe program which in turn calls the DLL. I am seeing different results for the internal calculations in my DLL when called by Excel VBA. So, it does not matter how the floating values returned from my DLL are handled. comp.lang.c++ is for C++ questions. Not apps like Excel nor specific operating systems. Thanks, Lynn |
#7
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 3/21/2012 1:38 PM, joeu2004 wrote:
"Lynn McGuire" wrote: 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. You are certainly looking at a floating-point anomaly; that is, sensitivity to 64-bit rounding. But I do not believe it can be explained by the FPU rounding mode alone. And while I might be able to suggest a solution for this particular example, in general, it is better to bullet-proof your arithmetic to correctly all numerical examples and computations. In general, you cannot expect 64-bit floating-point arithmetic to exactly equal a mathematical solution. All computer arithmetic is limited by a finite number of bits, whereas mathematics effectively relies on an infinite representation of information. In deference to the finite limitations of computer arithmetic in general, and 64-bit floating-point in particular, it is prudent to explicitly round any arithmetic result to the precision of accuracy that you require. That is, any arithmetic result involving non-integer operands, division, or integer operands or integer results greater than 2^53 in magnitude. ----- To address your particular example.... We can simulate the two different results using VBA in a manner that sheds some light on the problem. Consider the following two VBA procedures: Sub testit1() Const top As Double = 4195835# Const bottom As Double = 3145727# Dim chptst As Double Dim divtwo As Double divtwo = top / bottom chptst = (divtwo * bottom) - top MsgBox Format(chptst, "0.000E+00") End Sub Sub testit2() Const top As Double = 4195835# Const bottom As Double = 3145727# Dim chptst As Double Dim divtwo As Double chptst = ((top / bottom) * bottom) - top MsgBox Format(chptst, "0.000E+00") End Sub testit1 displays about 2.851E-10, whereas testit2 display 0.000E+00 -- exactly zero. The difference is that in testit1, the 80-bit floating-point result of top/bottom (the FPU of Intel CPUs use 80-bit floating-point internally) is rounded to a 64-bit floating-point result stored into divtwo. Then VBA uses the 64-bit divtwo in the computation of chptst. But in testit2, VBA does all of the computation with 80-bit precision, rounding to 64 bits only when storing the result into chptst. I do not know anything about Microsoft C++ or how C++ DLLs might work when called from VBA. But based on your observations, I suspect that when the code is compiled and linked in a C++ program, a better C++ compiler is used that optimizes the computation of chptst to use the 80-bit result of top/bottom despite the fact that you stored it into the 64-bit divtwo. However, when the DLL is compiled and linked into VBA, obviously C++ is using the 64-bit divtwo, just as testit1 does. (Although I refer to "a better compiler" as if there are two, the difference might actually be a difference in the behavior of __the__ so-called "back-end compiler"; that is, a phase of the C++ compiler.) Honestly, that does not make all that much sense to me based on my experience with (Unix) compilers. But that conclusion seems to be supported by my experiments below. The more reasonable assumption is that the 80-bit rounding to 64-bit is handled differently when the C++ DLL is called from VBA. However, I cannot duplicate the results of testit2 even when I modify testit1 in either of the following manners: 1. divtwo = top / bottom + 2^-52 ' add 1 to the least-significant bit 2. divtwo = top / bottom - 2^-52 ' sub 1 from the least-significant bit Those modifications do have the intended effect, which we can see when we look at the binary representation: 1. Original divtwo is 3FF55754,1C7C6B43. 2. Adding 2^-52, divtwo is 3FF55754,1C7C6B44. 3. Subtracting 2^-52, divtwo is 3FF55754,1C7C6B42. But the testit1 results in chptst a 1. With original divtwo, chptst is about 2.851E-10. 2. With divtwo + 2^-52, chptst is about 9.836E-10. 3. With divtwo - 2^-52, chptst is about -4.134E-10. Since none is exactly zero, as we see in testit2, I conclude that altering the rounding to 64-bit alone does determine the result in testit1, but the additional precision of 80-bit representation does. However, arguably, that is only conjecture. "Lynn McGuire" wrote: 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 presume that the intended purpose of this code is to change the FPU rounding mode. My conclusion above should explain why that does not work. Unfortunately, I do not know how to set and read the FPU control word in VBA. So I can offer a dispositive explanation. That particular test is for detection of the Pentium FPU FDIV bug: http://en.wikipedia.org/wiki/Pentium_FDIV_bug If that test does not round to exactly zero then the FPU is having problems. I am beginning to think that you are correct about the rounding mode. Thanks, Lynn |
#8
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 3/21/2012 1:37 PM, Martin Brown wrote:
On 21/03/2012 16:53, Lynn McGuire wrote: 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 think the problem is that your call to _control87(0,0) is a NOOP. Untested but I think _control87( _PC_64+_RC_NEAR, _MCW_PC+_MCW_RC); Ought to do the trick. Force 64 bit computation and nearest rounding. It could also be the case that in a pure C/C++ environment the final pass of the optimising compiler is smart enough to notice that your expression is identically zero at compile time. I have verified this behavior in both Excel 2003 and 2010. Does anyone have any ideas here ? Hope this helps. See the following for details http://msdn.microsoft.com/en-us/libr...(v=VS.60).aspx I will try this as I am thinking this way also. Thanks, Lynn |
#9
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
"Lynn McGuire" wrote:
That particular test is for detection of the Pentium FPU FDIV bug: http://en.wikipedia.org/wiki/Pentium_FDIV_bug Yes, I remember that defect quite well. That bug appeared and was fixed long ago (c. 1993 according to the wiki article). No modern Intel x32 or x64 processor has that defect. "Lynn McGuire" wrote: If that test does not round to exactly zero then the FPU is having problems. I don't see that test or that statement in the wiki article. I suspect you are relying on information you found by following one of the many links on that webpage. If you can point me to it, I'd appreciate it. In any case, I am quite sure that it is referring to 80-bit FPU operations, not rounding to 64-bit. And as I demonstrated with the VBA procedure "testit2", the 80-bit FPU operations do indeed result in exactly zero. Note that the wiki article does clearly state that 4195835 / 3145727 should result in 1.333820449136241002, and the FPU bug results in 1.333739068902037589 (forgive any typos). First, I hasten to point out that that is beyond the formatting limits of VBA, which will only format the first 15 significant digits. But VBA arithmetic does indeed result in 1.33382044913624, essentially the same as the corrected FPU result and not at all like the result due to the FPU bug. So the difference you see has nothing to do with the FPU bug. (But perhaps you did not intend to imply otherwise.) Actually, the exact 64-bit representation is 1.33382044913624,109305771980871213600039482116699 21875. Note the difference in the 4 digits following the first 15: 1093 v. 1002. That reinforces my assertion that any expectations of zero for the complete test is based on the 80-bit FPU arithemetic, not as it is represented in 64-bit storage. Moreover, I can tell you that 1.333820449136241002 is only an approximation. An exact conversion of any binary fractional part will end in 5, as demonstrated by the exact conversion of the 64-bit representation above. "Lynn McGuire" wrote: I am beginning to think that you are correct about the rounding mode. For my edification, please articulate what you "beginning to think" I am correct about. I have made a number of assertions. I hope the point you have come to understand is: the difference between the DLL called from C++ and the DLL called from VBA is not related to the FPU rounding mode; instead, it is probably related to 80-bit operands v. a mix of 80-bit and 64-bit operands. Certainly the rounding to 64-bit causes the difference. But I do not believe that any change in FPU rounding mode would make a difference. However, I can only speculate. I could be wrong. So I will be very interested in your results after applying Martin's corrections. |
#10
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
It happens that Lynn McGuire formulated :
comp.lang.c++ is for C++ questions. Not apps like Excel nor specific operating systems. Correct! However, it's highly possible that someone there has had similar issues using C++ DLLs with MS Office automation. Same may be the case in a Classic VB forum where people have used C++ DLLs and had similar issues. No harm asking... -- Garry Free usenet access at http://www.eternal-september.org ClassicVB Users Regroup! comp.lang.basic.visual.misc microsoft.public.vb.general.discussion |
#11
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 3/21/2012 1:37 PM, Martin Brown wrote:
On 21/03/2012 16:53, Lynn McGuire wrote: 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 think the problem is that your call to _control87(0,0) is a NOOP. Untested but I think _control87( _PC_64+_RC_NEAR, _MCW_PC+_MCW_RC); Ought to do the trick. Force 64 bit computation and nearest rounding. It could also be the case that in a pure C/C++ environment the final pass of the optimising compiler is smart enough to notice that your expression is identically zero at compile time. I have verified this behavior in both Excel 2003 and 2010. Does anyone have any ideas here ? Hope this helps. See the following for details http://msdn.microsoft.com/en-us/libr...(v=VS.60).aspx Bummer, neither of _control87( _PC_64+_RC_NEAR, _MCW_PC+_MCW_RC); _control87 (_PC_64, _MCW_PC); did not help. Something is really weird here. Thanks, Lynn |
#12
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
"Lynn McGuire" wrote:
Bummer, neither of _control87( _PC_64+_RC_NEAR, _MCW_PC+_MCW_RC); _control87 (_PC_64, _MCW_PC); did not help. Something is really weird here. The only "really weird" thing here is that apparently you are not comprehending my explanations. My bad: TMI! The outcome with _control87 should come as no surprise because I already demonstrated that no change in the rouned 64-bit value would result in exactly zero. I concluded, therefore, that it is the extended 80-bit precision that, by coincidence, causes that particular example to become zero. I say "by coincidence" because the 80-bit precision will not always cause expressions of the form (x/y)*y-x to be zero, for integer x and y, just as there are integer x and y where that expression is not zero using 64-bit precision. I never fully explained my reference to "80-bit precision". Perhaps you are unaware.... Although type Double is represented in memory by 64-bit floating-point, Intel CPUs use 80-bit floating-point registers to perform arithmetic. Since the 80-bit FP registers are accessible to the CPU, compilers can take advantage of them to store pairwise intermediate results. For example, the compiler might put x into FP1 and y into FP2, compute FP1/FP2 in FP3 (x/y), then compute FP3*FP2 ((x/y)*y) in FP3, and finally compute FP3-FP1 in FP3 ((x/y)*y-x). Finally, FP3 would be rounded to 64-bit and stored into a variable, chptst in your case. That is what is happening in my VBA example procedure called testit2(). ----- As for why the C++ compiler might optimize the DLL when linked to a C++ application, but not when linked to an Excel/VBA application, both Martin and I conjectured that that is simply how things work. Perhaps someone who understands Microsoft C++ and DLLs better can offer a more detailed factual explanation. I don't think we need any more wild speculation ;-). |
#13
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 3/21/2012 5:40 PM, joeu2004 wrote:
"Lynn McGuire" wrote: Bummer, neither of _control87( _PC_64+_RC_NEAR, _MCW_PC+_MCW_RC); _control87 (_PC_64, _MCW_PC); did not help. Something is really weird here. The only "really weird" thing here is that apparently you are not comprehending my explanations. My bad: TMI! The outcome with _control87 should come as no surprise because I already demonstrated that no change in the rouned 64-bit value would result in exactly zero. I concluded, therefore, that it is the extended 80-bit precision that, by coincidence, causes that particular example to become zero. I say "by coincidence" because the 80-bit precision will not always cause expressions of the form (x/y)*y-x to be zero, for integer x and y, just as there are integer x and y where that expression is not zero using 64-bit precision. I never fully explained my reference to "80-bit precision". Perhaps you are unaware.... Although type Double is represented in memory by 64-bit floating-point, Intel CPUs use 80-bit floating-point registers to perform arithmetic. Since the 80-bit FP registers are accessible to the CPU, compilers can take advantage of them to store pairwise intermediate results. For example, the compiler might put x into FP1 and y into FP2, compute FP1/FP2 in FP3 (x/y), then compute FP3*FP2 ((x/y)*y) in FP3, and finally compute FP3-FP1 in FP3 ((x/y)*y-x). Finally, FP3 would be rounded to 64-bit and stored into a variable, chptst in your case. That is what is happening in my VBA example procedure called testit2(). ----- As for why the C++ compiler might optimize the DLL when linked to a C++ application, but not when linked to an Excel/VBA application, both Martin and I conjectured that that is simply how things work. Perhaps someone who understands Microsoft C++ and DLLs better can offer a more detailed factual explanation. I don't think we need any more wild speculation ;-). Yes, I comprehended your explanation. And I understand the difference between 64 bit precision and 80 bit precision - that is what the _PC_64 flag is for. I started programming using 36 bit words for single precision (univac 1108). I've been down this road before unfortunately. BTW, I never said that my DLL was written in Visual C++. My DLL is written in 700K lines of Fortran, C and C++ code. But that is all built at compile and link time. The only thing that can be a difference here is how the math coprocessor is running. I am currently intrigued by this conversation: http://windowssecrets.com/forums/sho...8VB-Fortran%29 "We finally solved the problem. What happened is the floating point control of windows is set by each language according to its own set of parameters. This affects how the math processor rounds numbers and other various operations. C and Fortran sets the floating point parameters equally, VB and VBA each have their own set of parameters. We had to write a bit of Assembly code to force C, Fortran, VB, and VBA to use the same parameters for the floating point control. Now all four languages now give the same answers. This was a doosie that took 4 days to solve." I wish that they had posted the code, it sounds very interesting. Thanks, Lynn |
#14
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
Lynn McGuire was thinking very hard :
"We finally solved the problem. What happened is the floating point control of windows is set by each language according to its own set of parameters. This affects how the math processor rounds numbers and other various operations. C and Fortran sets the floating point parameters equally, VB and VBA each have their own set of parameters. We had to write a bit of Assembly code to force C, Fortran, VB, and VBA to use the same parameters for the floating point control. Now all four languages now give the same answers. This was a doosie that took 4 days to solve." This is what I was eluding to. Nice find! -- Garry Free usenet access at http://www.eternal-september.org ClassicVB Users Regroup! comp.lang.basic.visual.misc microsoft.public.vb.general.discussion |
#15
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
"Lynn McGuire" wrote:
Yes, I comprehended your explanation. And I understand the difference between 64 bit precision and 80 bit precision - that is what the _PC_64 flag is for. Okay. You said you tried these combinations: _control87( _PC_64+_RC_NEAR, _MCW_PC+_MCW_RC); _control87 (_PC_64, _MCW_PC); In both cases, you have selected 80-bit arithmetic (64-bit mantissa). And I quite sure that Excel (VBA) uses _PC_64+_RC_NEAR. So there is one combination that remains: _control87(_PC_53+_RC_NEAR, _MCW_PC+_MCW_RC) I believe that forces the FPU to round each result to 64-bit floating-point. It might even restrict the FPU to 64-bit floating-point. In either case, that might mimick this behavior in VBA: each pairwise operation is rounded to 64-bit floating-point. Sub testit3() Const top As Double = 4195835# Const bottom As Double = 3145727# Dim chptst As Double Dim divtwo As Double divtwo = top / bottom divtwo = divtwo * bottom chptst = divtwo - top MsgBox Format(chptst, "0.000E+00") End Sub In this case, chptst is exactly zero. |
#16
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 21/03/2012 22:03, Lynn McGuire wrote:
On 3/21/2012 1:37 PM, Martin Brown wrote: On 21/03/2012 16:53, Lynn McGuire wrote: 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. OK. This looks like a rounding error in the 53 bit mantissa so I suspect that the right coprocessor settings may be _PC_53. REAL*8 in FORTRAN speak. 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 think the problem is that your call to _control87(0,0) is a NOOP. Untested but I think _control87( _PC_64+_RC_NEAR, _MCW_PC+_MCW_RC); Ought to do the trick. Force 64 bit computation and nearest rounding. It could also be the case that in a pure C/C++ environment the final pass of the optimising compiler is smart enough to notice that your expression is identically zero at compile time. I have verified this behavior in both Excel 2003 and 2010. Does anyone have any ideas here ? Hope this helps. See the following for details http://msdn.microsoft.com/en-us/libr...(v=VS.60).aspx Bummer, neither of _control87( _PC_64+_RC_NEAR, _MCW_PC+_MCW_RC); _control87 (_PC_64, _MCW_PC); did not help. Try _PC_53 as the rounding mode (classic REAL*8) arithmetic. I think what may be happening is that _PC_64 guard digits on the full precision calculation using that nasty test case are causing trouble here. When the result is stored to memory it is being rounded to nearest which is not the same result computed at full 80bits (aka _PC_64) as at 64bits (aka _PC_53). It doesn't help that some compilers generate optimised code that works with intermediate results at full native width in the FPU and rounds only when storing back to memory. Something is really weird here. You might also try to replicate the fault in the controlled C compiler environment by doing it with each of the rounding modes and precisions. -- Regards, Martin Brown |
#17
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
"Lynn McGuire" wrote:
My DLL is written in 700K lines of Fortran, C and C++ code. But that is all built at compile and link time. Not necessarily. In some architectures, a "late optimization phase" is invoked when the DLL is loaded and linked to an application. This supports a single binary DLL that can be used on a variety of architectures. That is what I was alluding to earlier. However, it's a moot point. I suspect we have established that the key factor is _PC_53 v. _PC_64 mode. I am still awaiting the results of your trying _PC_53 mode. But I believe my explanation of your results with _PC_64 mode most likely point _PC_53 mode as the solution. |
#18
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 3/22/2012 5:57 PM, joeu2004 wrote:
"Lynn McGuire" wrote: My DLL is written in 700K lines of Fortran, C and C++ code. But that is all built at compile and link time. Not necessarily. In some architectures, a "late optimization phase" is invoked when the DLL is loaded and linked to an application. This supports a single binary DLL that can be used on a variety of architectures. That is what I was alluding to earlier. However, it's a moot point. I suspect we have established that the key factor is _PC_53 v. _PC_64 mode. I am still awaiting the results of your trying _PC_53 mode. But I believe my explanation of your results with _PC_64 mode most likely point _PC_53 mode as the solution. I tried _PC_53. Did not help. Thanks, Lynn |
#19
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
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" wrote: 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. |
#20
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 27/03/2012 01:37, Lynn McGuire wrote:
On 3/22/2012 5:57 PM, joeu2004 wrote: "Lynn McGuire" wrote: My DLL is written in 700K lines of Fortran, C and C++ code. But that is all built at compile and link time. Not necessarily. In some architectures, a "late optimization phase" is invoked when the DLL is loaded and linked to an application. This supports a single binary DLL that can be used on a variety of architectures. That is what I was alluding to earlier. However, it's a moot point. I suspect we have established that the key factor is _PC_53 v. _PC_64 mode. I am still awaiting the results of your trying _PC_53 mode. But I believe my explanation of your results with _PC_64 mode most likely point _PC_53 mode as the solution. I tried _PC_53. Did not help. You must have done it wrong then, or something else is afoot. When I try it with a fairly trivial but incredibly ugly toy code to force each possible state of the x87 I get the following output. // toy87.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "float.h" double testit(int tag1, int tag2) { double chptst; double divtwo; const double top = 4195835.0; const double bottom = 3145727.0; _control87( tag1+tag2, _MCW_PC+_MCW_RC); divtwo = top / bottom; chptst = (divtwo * bottom) - top ; return chptst; } int _tmain(int argc, _TCHAR* argv[]) { for (int prec = _PC_64; prec<=_PC_24; prec += _PC_53) for (int round = _RC_NEAR; round <= _RC_CHOP; round += _RC_DOWN) printf("testit(%i,%i) = %e\n", prec/_PC_53, round/_RC_DOWN, testit(prec,round)); return 0; } *NB* If I allow the optimiser any freedom at all it compiles to fldz Here is the output cycling though all rounding modes of each precision: _64_ testit(0,0) = 2.851266e-010 testit(0,1) = -4.138201e-010 testit(0,2) = 2.851266e-010 testit(0,3) = -4.138201e-010 _53_ testit(1,0) = 0.000000e+000 testit(1,1) = -9.313226e-010 testit(1,2) = 9.313226e-010 testit(1,3) = -9.313226e-010 _24_ testit(2,0) = 0.000000e+000 testit(2,1) = -5.000000e-001 testit(2,2) = 5.000000e-001 testit(2,3) = -5.000000e-001 My guess would be that you have not forced the numeric CPU into the right state. Round to nearest gives exactly 0.0 for both 32bit and 64bit floating point but for full 80bit hardware precision it gives an "error" in the 54th bit. The example was constructed to trigger a rare iteration fault in a particular CPU series on FP divide ages ago. -- Regards, Martin Brown |
#21
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
Errata.... I wrote:
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. I wrote: 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. |
#22
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
Arrgghh! I miswrote:
Errata.... I wrote: 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. 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. |
#23
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
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 |
#24
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
"Lynn McGuire" wrote:
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" wrote: 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/libr...=vs.80%29.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. |
#25
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
PS.... I wrote:
"joeu2004" wrote in message ... 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. |
#26
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 31/03/2012 17:24, Lynn McGuire wrote:
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. 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. -- Regards, Martin Brown |
#27
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 31/03/2012 23:17, joeu2004 wrote:
"Lynn McGuire" wrote: 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" wrote: 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. -- Regards, Martin Brown |
#28
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 4/2/2012 3:39 AM, Martin Brown wrote:
On 31/03/2012 17:24, Lynn McGuire wrote: 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. 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 |
#29
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
"Martin Brown" wrote:
On 31/03/2012 23:17, joeu2004 wrote: 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. 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" wrote: (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" wrote: 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/libr...=vs.80%29.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. |
#30
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
PS.... I wrote:
"Martin Brown" wrote: 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/libr...=vs.80%29.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. |
#31
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 02/04/2012 19:44, joeu2004 wrote:
PS.... I wrote: "Martin Brown" wrote: 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 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 assumed that _control87 returns the abstract values for _PC_64, _RC_NEAR et al documented in http://msdn.microsoft.com/en-us/libr...=vs.80%29.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. 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! -- Regards, Martin Brown |
#32
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
"Martin Brown" wrote:
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" wrote: 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". |
#33
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 3/31/2012 5:17 PM, joeu2004 wrote:
"Lynn McGuire" wrote: 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" wrote: 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/libr...=vs.80%29.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 |
#34
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 4/2/2012 3:14 PM, Martin Brown wrote:
On 02/04/2012 19:44, joeu2004 wrote: PS.... I wrote: "Martin Brown" wrote: 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 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 assumed that _control87 returns the abstract values for _PC_64, _RC_NEAR et al documented in http://msdn.microsoft.com/en-us/libr...=vs.80%29.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. 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 |
#35
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
"Lynn McGuire" wrote:
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. |
#36
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
"Lynn McGuire" wrote:
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. |
#37
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 4/2/2012 7:33 PM, joeu2004 wrote:
"Lynn McGuire" wrote: 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. 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 |
#38
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
I wrote:
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. |
#39
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 03/04/2012 01:33, joeu2004 wrote:
"Lynn McGuire" wrote: 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. 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. -- Regards, Martin Brown |
#40
Posted to microsoft.public.excel,microsoft.public.excel.programming
|
|||
|
|||
Excel and the Math Coprocessor for DLLs
On 4/2/2012 7:33 PM, joeu2004 wrote:
"Lynn McGuire" wrote: 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. Done. No better calcs. The Math Coprocessor status is 0x4020 and control is 0x137f The Math Coprocessor status is is now 0x127f The Math Coprocessor status is 0x4000 and control is 0x127f The Math Coprocessor status is is now 0x127f The Math Coprocessor status is 0x4000 and control is 0x127f The Math Coprocessor status is is now 0x127f I call the following code from each entry point. unsigned checkMathCoprocessorStatus () { unsigned old87Status = 0; unsigned old87Control = 0; unsigned new87result = 0; old87Status = _status87 (); old87Control = _control87 (0, 0); //if (old87Status != 0) { char msg [4096]; sprintf (msg, "\nThe Math Coprocessor status is 0x%x and control is 0x%x\n\n", old87Status, old87Control); writeLineToScreen (msg); // new87result = _control87 (_PC_64 + _RC_NEAR, _MCW_PC + _MCW_RC); // new87result = _control87 (_PC_53, _MCW_PC); // new87result = _control87 (_PC_64, _MCW_PC); // new87result = _control87 (_RC_NEAR, _MCW_RC); // _asm { FINIT }; //new87result = _control87 (0x27f, 0xffff); //new87result = _control87 (0, 0); new87result = _control87 (_PC_53 + _RC_NEAR, _MCW_PC + _MCW_RC); sprintf (msg, "\nThe Math Coprocessor status is is now 0x%x\n\n", new87result); writeLineToScreen (msg); } return old87Status; } Thanks, Lynn |
Reply |
|
Thread Tools | Search this Thread |
Display Modes | |
|
|
Similar Threads | ||||
Thread | Forum | |||
Excel is messing with the math coprocessor | Excel Programming | |||
VSTO updating excel dlls | Excel Programming | |||
How are excel function names in dlls/xlas resolved ? | Excel Programming | |||
Referencing .Net DLLs | Excel Programming | |||
DLLs | Excel Programming |