3

A little background: I was working on some data conversion from C to C# by using a C++/CLI midlayer, and I noticed a peculiarity with the way the debugger shows floats and doubles, depending on which dll the code is executing in (see code and images below). At first I thought it had something to do with managed/unmanaged differences, but then I realized that if I completely left the C# layer out of it and only used unmanaged data types, the same behaviour was exhibited.

Test Case: To further explore the issue, I created an isolated test case to clearly identify the strange behaviour. I am assuming that anyone who may be testing this code already has a working Solution and dllimport/dllexport/ macros set up. Mine is called DLL_EXPORT. If you need a minimal working header file, let me know. Here the main application is in C and calling a function from a C++/CLI dll. I am using Visual Studio 2015 and both assemblies are 32 bit.

I am a bit concerned, as I am not sure if this is something I need to worry about or it's just something the debugger is doing (I am leaning towards the latter). And to be quite honest, I am just outright curious as to what's happening here.

Question: Can anyone explain the observed behaviour or at least point me in the right direction?

C - Calling Function

void floatTest()
{
    float floatValC = 42.42f;
    double doubleValC = 42.42;
    //even if passing the address, behaviour is same as all others.
    float retFloat = 42.42f;
    double retDouble = 42.42;
    int sizeOfFloatC = sizeof(float);
    int sizeOfDoubleC = sizeof(double);

    floatTestCPP(floatValC, doubleValC, &retFloat, &retDouble);

    //do some dummy math to make compiler happy (i.e. no unsused variable warnings)
    sizeOfFloatC = sizeOfFloatC + sizeOfDoubleC;//break point here
}

C++/CLI Header

DLL_EXPORT void floatTestCPP(float floatVal, double doubleVal, 
      float *floatRet, double *doubleRet);

C++/CLI Source

//as you can see, there are no managed types in this function
void floatTestCPP(float floatVal, double doubleVal, float *floatRet, double *doubleRet)
{
    float floatLocal = floatVal;
    double doubleLocal = doubleVal;

    int sizeOfFloatCPP = sizeof(float);
    int sizeOfDoubleCPP = sizeof(double);

    *floatRet = 42.42f;
    *doubleRet = 42.42;

    //do some dummy math to make compiler happy (no warnings)
    floatLocal = (float)doubleLocal;//break point here
    sizeOfDoubleCPP = sizeOfFloatCPP;
}

Debugger in C - break point on last line of floatTest()

enter image description here

Debugger in C++/CLI - break point on the second to last line of floatTestCPP()

enter image description here

Nik
  • 1,780
  • 1
  • 14
  • 23
  • 1
    It is just a guess, but I suppose that the debug viewer for the variables are simply different and the binary representation is the same... I have also a C++/CLI module (accessed from C++) that uses doubles and I never saw any inaccuracies. – xMRi Nov 01 '17 at 07:07
  • 3
    It will be a lot more obvious when you use `float retFloat = 1 / 3.0f;` The managed debugging engine displays one more significant digit than a float or double can possibly store. Which is 7 for float, 16 for double. So you'll now see 8 and 17 digits in the displayed value. The unmanaged engine is inconsistent for float, displaying 2 extra digits. Whether that was intentional is hard to guess. – Hans Passant Nov 01 '17 at 08:18
  • @HansPassant, Here's something interesting, using `1 / 3.0f`, `floats` have identical representation in both environments of `0.333333343`, and `doubles`: `C` - `0.33333334326744080`, `C++/CLI` - `0.3333333432674408` (identical minus the least significant zero). – Nik Nov 01 '17 at 14:37
  • 1
    Reported the result of `#include ... printf(%d\n", FLT_EVAL_METHOD);` in your various environments. That provides useful information as to what the underlying code may be doing here. – chux - Reinstate Monica Nov 01 '17 at 19:47
  • @chux, the preprocessor evaluates `FLT_EVAL_METHOD` to `0` in both `C` and `C++/CLI` environments. Thanks for your comment, it was really interesting to learn about this, but doesn't look like the answer is there. – Nik Nov 01 '17 at 23:54

1 Answers1

1

Consider Debugger in C++/CLI itself is not necessarily coded in C, C# or C++.

MS libraries support the "R" format: A string that can round-trip to an identical number. I suspect this or a g format was used.

Without MS source code, the following is only a good supposition:

The debug output is enough to distinguish the double from other nearby double. So code need not print "42.420000000000002", but "42.42" is sufficient - whatever format is used.

42.42 as an IEEE double is about 42.4200000000000017053025658242404460906982... and the debugger certainly need not print the exact value.

Potential; similar C code

int main(void) {
  puts("12.34567890123456");
  double d = 42.42;
  printf("%.16g\n", nextafter(d,0));
  printf("%.16g\n", d);
  printf("%.17g\n", d);
  printf("%.16g\n", nextafter(d,2*d));
  d = 1 / 3.0f;
  printf("%.9g\n", nextafterf(d,0));
  printf("%.9g\n", d);
  printf("%.9g\n", nextafterf(d,2*d));
  d = 1 / 3.0f;
  printf("%.16g\n", nextafter(d,0));
  printf("%.16g\n", d);
  printf("%.16g\n", nextafter(d,2*d));
}

output

12.34567890123456
42.41999999999999
42.42
42.420000000000002   // this level of precision not needed.
42.42000000000001
0.333333313
0.333333343
0.333333373
0.3333333432674407
0.3333333432674408
0.3333333432674409

For your code to convert a double to text with sufficient textual precision and back to double to "round-trip" the number, see Printf width specifier to maintain precision of floating-point value.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Excellent insight! Although, as you said, this is at best a supposition, your answer is completely consistent with what I am observing. – Nik Nov 04 '17 at 00:22
  • I would like to make a revision suggestion. 1) Your answer naturally leads from Hans Passant's comment, yet your answer only implies it. For any future readers, it may take some research to fully understand what your answer is getting at. 2) I also think that the way you used `FLT_EVAL_METHOD` to craft your response is not entirely obvious. For any future readers for whom it does not evaluate to `0` may not get their answer without thoroughly reading and researching all comments. I am happy to do it, but I thought you are a much better person for the job! – Nik Nov 04 '17 at 00:30