【问题标题】:strtod() and sprintf() inconsistency under GCC and MSVCGCC 和 MSVC 下的 strtod() 和 sprintf() 不一致
【发布时间】:2010-03-23 11:10:43
【问题描述】:

我正在开发适用于 Windows 和 Mac OS X 的跨平台应用程序,但我遇到了两个标准 C 库函数的问题:

  • strtod() - 字符串到双精度转换
  • sprintf() - 用于输出双精度浮点数时)

他们的 GCC 和 MSVC 版本返回不同的结果,以尾数的一些数字表示。但是如果指数值很大,它就起着至关重要的作用。一个例子:

MSVC: 9,999999999999999500000000000000e+032
GCC:  9,999999999999999455752309870428e+32
MSVC: 9,999999999999999500000000000000e+033
GCC:  9,999999999999999455752309870428e+33
MSVC: 9,999999999999999700000000000000e+034
GCC:  9,999999999999999686336610791798e+34

输入的测试编号在 MSVC 和 GCC 下具有相同的二进制表示。

我正在寻找这些函数的经过良好测试的跨平台开源实现,或者只是寻找能够正确一致地将双精度转换为字符串并返回的一对函数。

我已经尝试过clib GCC的实现,但是代码太长而且太依赖其他源文件,所以我预计适配会比较困难。

你会推荐哪些字符串到双精度和双精度到字符串函数的实现?

【问题讨论】:

  • 当你说“不同的结果”时,你的意思是你得到的结果不正确还是只是小数点后第n位有微小的差异?
  • 它们在尾数的某些位数上有所不同。但是如果指数值很大,它就起着至关重要的作用。 An example: MSVC: 9,999999999999999500000000000000e+032
    GCC: 9,999999999999999455752309870428e+32
    MSVC: 9,999999999999999500000000000000e+033 GCC: 9,999999999999999455752309870428e+33 MSVC: 9,999999999999999700000000000000e+034 GCC: 9,999999999999999686336610791798e+ 34 输入的测试编号在 MSVC 和 GCC 下具有相同的二进制表示。
  • 不是关键角色;这是一个微不足道的区别。你没看懂浮点数。

标签: string gcc double visual-c++


【解决方案1】:

浮点数和字符串之间的转换很难——非常难。有很多关于这个主题的论文,包括:

最后一个是浮点十进制运算的宝库。

GNU glibc 实现可能已经达到了最好的水平 - 但它不会简短或简单。


解决例子

double 通常存储 16 个(有些人可能认为是 17 个)有效十进制数字。 MSVC 正在处理 17 位数字。除此之外的任何东西都是噪音。 GCC 正在按照您的要求进行操作,但是双精度数中没有足够的位来保证您请求的额外 14 位数字。如果您有 16 字节的“long double”值(SPARC、PPC、Intel x86_64 for Mac),那么您可能需要 32 个有效数字。但是,您显示的差异是 QoI;我什至可能会争辩说,MS 在这里比 GCC/glibc 做得更好(而且我不经常这么说!)。

【讨论】:

  • 即使浮点运算的结果不精确,每个浮点值都对应一个特定的精确二进有理数。打印浮点值的好算法将能够打印这个确切的数字。一个令人满意的(根据标准)算法会打印出足够多的有效数字来唯一确定原始浮点值是什么。
  • 我和乔纳森在一起。一个令人满意的(并且符合标准的)算法将打印足够多的有效数字来唯一地确定原始值(GCC 和 MSVC 都管理这个)。一个好的算法还将十进制结果四舍五入到与二进制表示中的最低有效位大致相同的精度,以避免暗示结果比二进制表示允许的更精确。
【解决方案2】:

我所知道的以十进制打印浮点数的确切值的唯一算法如下:

  1. 将尾数转换为十进制整数。您可以通过将位分开以直接读取尾数来做到这一点,或者您可以编写一个凌乱的浮点循环,首先将值乘以 2 的幂,使其在 1
  2. 通过反复乘以或除以 2 来应用指数。这是对您生成的十进制数字 字符串 的操作。每〜3次乘法将在左侧添加一个额外的数字。每一个除法都会在右边增加一个额外的数字。

它又慢又丑,但它确实有效......

【讨论】:

    【解决方案3】:

    以下函数dtoa 返回一个字符串,该字符串无损转换回相同的double

    如果您重写 aisd 以测试您的所有 string-to-float 实现,您将在其中获得可移植的输出。

      // Return whether a string represents the given double.
      int aisd(double f, char* s) {
         double r;
         sscanf(s, "%lf", &r);
         return r == f;
      }
    
      // Return the shortest lossless string representation of an IEEE double.
      // Guaranteed to fit in 23 characters (including the final '\0').
      char* dtoa(char* res, double f) {
         int i, j, lenF = 1e9;
         char fmt[8];
         int e = floor(log10(f)) + 1;
    
         if (f > DBL_MAX) { sprintf(res, "1e999"); return res; }  // converts to Inf
         if (f < -DBL_MAX) { sprintf(res, "-1e999"); return res; }  // converts to -Inf
         if (isnan(f)) { sprintf(res, "NaN"); return res; }  // NaNs don't work under MSVCRT
    
         // compute the shortest representation without exponent ("123000", "0.15")
         if (!f || e>-4 && e<21) {
            for (i=0; i<=20; i++) {
               sprintf(fmt, "%%.%dlf", i);
               sprintf(res, fmt, f);
               if (aisd(f, res)) { lenF = strlen(res); break; }
            }
         }
    
         if (!f) return res;
    
         // compute the shortest representation with exponent ("123e3", "15e-2")
         for (i=0; i<19; i++) {
            sprintf(res, "%.0lfe%d", f * pow(10,-e), e); if (aisd(f, res)) break;
            j = strlen(res); if (j >= lenF) break;
            while (res[j] != 'e') j--;
            res[j-1]--; if (aisd(f, res)) break;   // try mantissa -1
            res[j-1]+=2; if (aisd(f, res)) break;  // try mantissa +1
            e--;
         }
         if (lenF <= strlen(res)) sprintf(res, fmt, f);
         return res;
      }
    

    有关 MSVCRT NaN 问题,请参阅 Can't get a NaN from the MSVCRT strtod/sscanf/atof functions。如果你不需要识别NaNs,你可以在得到一个时输出无穷大("1e999")。

    【讨论】:

    • 这似乎没有回答问题
    猜你喜欢
    • 1970-01-01
    • 2011-05-03
    • 2021-03-09
    • 2021-08-22
    • 1970-01-01
    • 2022-01-12
    • 2014-04-11
    • 1970-01-01
    • 2016-08-18
    相关资源
    最近更新 更多