非正规毛刺的strtod边缘

问题描述 投票:0回答:1

我一直在查看一些代码,它们有一个奇怪的优化错误,在这个过程中,偶然发现了 strtod() 中的错误条件,它与 strtof() 具有不同的行为,就在非正规值的边缘。 strtof() 的行为对我来说似乎完全合理,但 strtod() 则不然!具体来说,它对于输入值“-0x1.ffffffffffffffp-1023”返回-0.0。

这是在“-0x1.ffffffffffffep-1023”表示中设置为 1 的一个额外位,它可以正确解码。更奇怪的是,添加额外的尾随数字会得到一个值 2^{-1018},我无法解释。在我看来,从分母到普通浮点数转换的特殊边缘情况处理不正确,导致值为零。

任何人都可以解释额外的额外数字引起的另一个奇怪的数字吗?

MSC 2022 和 Intel 2023 上的故障相同

用于双打和输出的示例代码 MRE(浮点数如您所期望的那样工作)

// strtod() fails to handle edge case overflow from denorms correctly
// 
// Problem manifests on both MS 2022 and Intel 2023 compilers so by design? but why???
// 
// using Windows 11 and Microsoft Visual Studio Community 2022 (64-bit) - Version 17.1.0
 
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>


void Show(const char* name, int err, double arg)
{
  unsigned iarg[2];
  memcpy(&iarg, &arg, sizeof arg);
  printf("\n\"%25s\" decoded errno=%i as 0x%08x%08x %.13a %30.22g", name, err, iarg[1], iarg[0], arg, arg);
}

void DecodeShow(const char* value)
{
  char* stopstr;
  double arg;
  errno = 0;
  arg = strtod(value, &stopstr);
  Show(value, errno, arg);
}

int main(void)
{

  printf("Test of doubles near denorm boundary\n");
  DecodeShow("-0x1.ffffffffffffp-1023");
  DecodeShow("-0x1.ffffffffffffep-1023");
  DecodeShow("-0x1.ffffffffffffe8p-1023");
  DecodeShow("-0x1.fffffffffffff0p-1023"); // hard fail == -0.0 !
  DecodeShow("-0x1.fffffffffffff8p-1023");
  DecodeShow("-0x1.ffffffffffffffp-1023");
  DecodeShow("-0x1.fffffffffffffffp-1023");
  printf("\n");
  DecodeShow("0x1.ffffffffffffe8p-1023");
  DecodeShow("0x1.fffffffffffff0p-1023"); // hard fail == +0.0
  DecodeShow("0x1.fffffffffffff8p-1023");
  DecodeShow("0x1.ffffffffffffffp-1023");
  printf("\n");
  DecodeShow("0x1.ffffffffffffep-1022");
  DecodeShow("-0x1.fffffffffffffp-1022");
  DecodeShow("-0x1.fffffffffffff8p-1022");
  DecodeShow("-0x1.ffffffffffffffp-1022");
}

故障边界周围的选定输出:

" -0x1.ffffffffffffep-1023" decoded errno=0 as 0x800fffffffffffff -0x0.fffffffffffffp-1022  -2.225073858507200889025e-308
"-0x1.ffffffffffffe8p-1023" decoded errno=0 as 0x800fffffffffffff -0x0.fffffffffffffp-1022  -2.225073858507200889025e-308
"-0x1.fffffffffffff0p-1023" decoded errno=0 as 0x8000000000000000 -0x0.0000000000000p+0                             -0
"-0x1.fffffffffffff8p-1023" decoded errno=0 as 0x8040000000000000 -0x1.0000000000000p-1019  -1.780059086805761106472e-30

我认为超出机器精度的额外尾随数字不应从根本上改变 strtod 像这样解码的浮点值。我无法通过使用十进制数字输入字符串引发同样的失败 - 跨越边界的转换似乎表现良好(尽管我不能排除一个我尚未找到的点值不起作用)。

c floating-point double
1个回答
0
投票

[...] strtod() 的行为[似乎不合理]!具体来说,它对于输入值“-0x1.ffffffffffffffp-1023”返回-0.0。

我不同意这个具体结果是不合理的,但我倾向于同意你提出的结果组合似乎不合理。

"-0x1.fffffffffffffp-1023"
不应转换为其大小小于
"-0x1.ffffffffffffep-1023"
转换为的值的值。然而,鉴于前者不能完全用 IEEE-754 二进制 64 格式表示,而后者则可以(作为次正规),因此将两者转换为相同的结果是合理的。如果结果是
-0.0
,那将是合理的——而且肯定不违反语言规范。这是包含精确算术结果的两个归一化双精度之一。事实上,
-0.0
正是我所期望的,当舍入模式为
FE_TOWARDZERO
时,舍入到标准化数字。

然而,您的

"-0x1.fffffffffffff8p-1023"
结果更令人担忧。规范要求结果正确舍入,并且我不认为您报告的结果可以满足该要求。

任何人都可以解释额外的额外数字引起的另一个奇怪的数字吗?

从某种意义上说:

strtod()
"-0x1.fffffffffffff8p-1023"
转换为
-0x1.0000000000000p-1019
是C实现中错误的表现。可能与
-0.0
"-0x1.fffffffffffffp-1023"
结果相同。我对可能存在的实现缺陷有一些模糊的想法,但这并不重要,我选择不推测这个答案。

© www.soinside.com 2019 - 2024. All rights reserved.