OJ's rants

It's not about you, it's about the software

Extended-Precision Floating-Point Values in the CLR

| Comments

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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 :)

Comments