C 与 C++ 调试器中的浮点表示法 (CLI)

Floating Point Representation in Debugger in C vs C++(CLI)

一些背景知识:我正在使用 C++/CLI 中间层将一些数据从 C 转换为 C#,我注意到调试器显示 floatsdoubles 的方式有一个特点,具体取决于代码在哪个 dll 中执行(请参见下面的代码和图像)。起初我认为它与 managed/unmanaged 差异有关,但后来我意识到,如果我完全将 C# 层排除在外并仅使用非托管数据类型,则会表现出相同的行为。

测试用例:为了进一步探讨这个问题,我创建了一个独立的测试用例来清楚地识别奇怪的行为。我假设任何可能正在测试此代码的人都已经有一个有效的解决方案和 dllimport/dllexport/macros 设置。我的名字叫 DLL_EXPORT。如果您需要最小的工作头文件,请告诉我。这里的主要应用程序在 C 中,并从 C++/CLI dll 中调用一个函数。我正在使用 Visual Studio 2015 并且两个程序集都是 32 bit.

我有点担心,因为我不确定这是我需要担心的事情还是调试器正在做的事情(我倾向于后者)。老实说,我对这里发生的事情非常好奇。

问题:任何人都可以解释观察到的行为或至少指出正确的方向吗?

C - 调用函数

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 头文件

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

C++/CLI 源代码

//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;
}

C 中的调试器 - floatTest()

最后一行的断点

C++/CLI 中的调试器 - floatTestCPP()

倒数第二行的断点

考虑 C++/CLI 中的调试器 本身不一定用 C、C# 或 C++ 编码。

MS 库支持 "R" format:可以往返于相同数字的字符串。我怀疑使用了这个或 g 格式。

没有MS源码,以下只是一个很好的假设:

调试输出足以区分 double 和附近的其他 double 所以代码不需要打印 "42.420000000000002",但是 "42.42" 就足够了——无论使用什么格式。

42.42 作为 IEEE double 大约是 42.4200000000000017053025658242404460906982...,调试器当然不需要打印确切的值。

潜力;类似的 C 代码

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));
}

输出

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

有关将 double 转换为具有足够文本精度的文本并返回到 double 到 "round-trip" 数字的代码,请参阅 Printf width specifier to maintain precision of floating-point value