While at work today I hit a problem that I’ve never hit before (which is quite rare these days
), and while it was frustrating it was also good to learn about something that I never knew was a problem. If you’re having some issues marshalling double-precision floating-point information through managed components to unmanaged components, or you’re just interested in learning something new, then read on
Let me start by explaining the scenario:
One of our VB6 applications passes a variant array of 64-bit doubles to a managed C++ component. The managed code does a bit of munging, before passing the same information on to another component which is an unmanaged C++ DLL written and maintained by a third party. This second DLL is responsible for doing boolean operations on 3D solids. When this component is invoked, the application dies with a nice obscure “screwed-up memory” error. Unfortunately, since the component is closed-source, we needed to pass the information on to the author of the code and wait for a response.
After the author had investigated the problem he informed us that the reason for the crash was that the values that are being passed in are 80-bit extended-precision values, and not the expected IEEE 64-bit double-precision values.
Wierd! That didn’t make any sense to me. On hearing this I started to investigate where the process was falling down. The original data was 64-bit, so how come it wasn’t by the time it hit the final DLL? Something must be converting the 64-bit double-precision values to their 80-bit extended-precision equivalents. I spent a fair bit of time trying to find out as much as I could about the various floating-point representations, and where they’re used, but it took me an eternity to locate the information I was searching for.
In the deep, dark depths of the web, I stumbled across this little nugget of information (thanks to ExtremeOptimization.com):
… the ‘extended’ format, for which the IEC 60559 standard defines minimum specifications, and which is used by the floating-point unit on Intel processors, and is also used internally by the CLR.
Ah ha! You bastards
So we’re always dealing with 80-bit values when we use double in the CLR.
Now that we know this, we can take steps to sort it out. In a nutshell, we need to set the FPU state to force the use of 64-bit values before invoking the unmanaged component. Thankfully there’s an API function that we can use to do this, it’s called _controlfp(). In VS 2005, this function is deprecated, so we’re forced to use the ’secure’ equivalent _controlfp_s(). Here’s how you do it:
#include <float.h> . . void ManagedClass::ManagedFunction() { double dooby[128]; . unsigned int prevState; // set the FPU state to 64-bit _controlfp_s( &prevState, _PC_53, MCW_PC); . // invoke unmanaged DLL - these values wil be 64-bit, not 80-bit ExternalFunction( dooby ); . // reset FPU state to previous value _controlfp_s( NULL, prevState, MCW_PC); }
So there you have it. I hope this information is useful










July 3, 2007
Is that just doubles in the CLR or are floats handled the same way?
July 4, 2007
Good question
I think floats are standard single-precision, not extended precision, but I’m going to have to verify that - don’t quote me on it (yet).
July 6, 2007
Either way, cheers for the heads up.
July 16, 2007
The floating point unit only deals with 80bit values, so if CLR uses the FPU for all floating point calculations, it doesn’t matter what the original size is. It’ll convert them to 80bits no matter what. Native C++ works this way as well. It’ll convert both 32bit and 64bit to 80bit and back again. This has a long history that the Intel FPU will sometimes give different results (precision wise) than other architectures.
However, the SSE unit (that allows the same operation on multiple values 4×32bit on PIII and 2×64bit on PIV) can handle 32bit and 64bit floating point values directly. Unfortunately, it has limited arithmetic instructions (+,-,*,/,min,max,sqrt,rcp and rcpsqrt) and older processors come without SSE.
July 17, 2007
Hi and thanks for the information Vorlath.
The main issue we have is the size of the data that is being passed when managed code calls unmanaged code without .NET being involved in the call. Regardless of what C++ does under the hood when it uses the FPU, it expects only 64-bit doubles when the function is called. The values that we have in memory are actually 80-bit numbers, so there is a discrepancy. When the first 64 bits of the number are “converted” to 80-bit for use with the FPU, the number is already screwed. The next number (which is part of the array of doubles, and hence a contiguous block of memory) is converted as well, but the value is created from 16 bits of the first number, and 48 bits of the second, which again is totally wrong.
So regardless of what happens under the hood with the languages, the interfaces are still expecting a certain value size for doubles and require some manipulation of the data before passing between the components.
Thanks again for your comment! I learned something new
July 21, 2007
hello. it is interesting of read your post. is it a really hardly things to declation of floating value with managed c++?
July 23, 2007
Hi Shuray,
I’m not sure I understand your question. Are you wondering if it’s difficult to declare floating point values in Managed C++?? If so, then the answer is no. It’s not difficult declaring floating point variables, nor is it tough to work with them.
The only issue I have found is when passing between managed and unmanaged code. Everything else is fine.
I hope that helps!