关于NaN和+/- Infinity的编译器行为

时间:2016-01-28 21:23:08

标签: c nan ieee-754

为了避免链接libmath,我编写了我自己需要的两个函数。这不是一个大问题,但它在魔鬼引诱的细节中:现在我有问题,我需要定义和测试NaN和+/- Infinity没有bit-juggling(不能直接设置位,需要< em> completly endian-agnostic)。也没有那么大的问题,因为所有的现代编译器都支持IEEE-754,或者它们不支持?他们不。当然,他们没有。至少不是以一致和可测试的方式。如果他们这样做,你可以关闭它。有时他们会定义一个宏,有时候不会,有时候我不知道,这就是我的咆哮结束而我的问题开始的地方:

哪些编译器支持构建和测试NaN和NaN的方法 以下列方式+/-无限。

#include <stdio.h>
#include <stdlib.h>

#ifdef FALSEONLY
#   define PUTS(x)      // noop
#else
#   define PUTS(x) puts(x)
#endif

#define DOUBLE_MAX 1.7976931348623158e308

int main()
{
  double positive_infinity, negative_infinity, nan;
  int num_errors;

/*
 * Some compilers need that workaround.
 * 
 * Known for such behaviour are Sun C 5.0,  Compaq (formerly DEC) 6.4 and MSVC 9
 * https://lists.gnu.org/archive/html/bug-gnulib/2007-03/msg00360.html
 * https://github.com/rofl0r/gnulib/blob/master/tests/nan.h
 * 
 * There are compilers which do not know a NaN like the
 * C2000 CLA C Compiler (Texas Instruments). But I have older data only (from 2013),
 * that behaviour might have changed in the meantime.
 * https://github.com/cpputest/cpputest/issues/132
 */
// #if defined __SUNPRO_C ||  defined __DECC || defined _MSC_VER
#ifdef USE_ZERO
  double zero;
  zero = 0.0;
  positive_infinity = 1.0 / zero;
  negative_infinity = -1.0 / zero;
  nan = zero / zero;
#else
  /*
   * "For division, when the divisor is zero and the dividend is a finite
   * non-zero number, the sign of the infinity is the exclusive OR of
   * the operands’ signs"
   * 
   *    -- IEEE-754 2008 7.3 Division by zero
   */
  positive_infinity = 1.0 / 0.0;
  negative_infinity = -1.0 / 0.0;
  /*
   * "For operations producing results in floating-point format, the default
   * result of an operation that signals the invalid operation exception
   * shall be a quiet NaN that should provide some diagnostic information (see 6.2).
   * These operations are:
   *    [...]
   *    e) division: division(0, 0) or division(∞, ∞)
   *    [...]"
   * 
   *    -- IEEE-754 2008 7.2(e) Invalid operation
   */
  nan = 0.0 / 0.0;
#endif

  num_errors = 0;
  /*
   * " Every NaN shall compare unordered with everything, including itself."
   * 
   *    -- IEEE-754 2008 5.11 Details of comparison predicates
   * 
   * 
   * Does not work with  MSVC and GCC if the option to do "fast math" is
   * switched on.
   * 
   * GCC defines __FAST_MATH__ in that case.
   * 
   * How to check for it with MSVC?
   * 
   * What's with Intel compilers? The author doesn't have seven hundred bucks to burn
   * and cannot sign the license agreement for the "free" version with a clear conscience.
   * 
   * At least icpc 12.1.4 seems to work.
   * http://rosettacode.org/wiki/Extreme_floating_point_values#C
   */
  if (nan != nan) {
    PUTS("TRUE: nan != nan");
  } else {
    num_errors++;
    puts("FALSE: nan != nan");
  }
#ifdef USE_ZERO
  if (nan == 0.0 / zero) {
    num_errors++;
    PUTS("TRUE: nan == 0.0/0.0 (WRONG acc. to IEEE-754)");
  } else {
    puts("FALSE: nan == 0.0/0.0 (CORRECT acc. to IEEE-754)");
  }
#else
  if (nan == 0.0 / 0.0) {
    num_errors++;
    PUTS("TRUE: nan == 0.0/0.0 (WRONG acc. to IEEE-754)");
  } else {
    puts("FALSE: nan == 0.0/0.0 (CORRECT acc. to IEEE-754)");
  }
#endif
  /*
   * Outcome depends on rounding mode, see "IEEE-754 2008 7.4 Overflow" for details.
   * Must both hold if the rounding mode is either roundTiesToEven or roundTiesToAway.
   */
  if (positive_infinity == 2 * DOUBLE_MAX) {
    PUTS("TRUE: positive_infinity == 2 * DOUBLE_MAX");
  } else {
    num_errors++;
    puts("FALSE: positive_infinity == 2 * DOUBLE_MAX");
  }
  if (negative_infinity == -(2 * DOUBLE_MAX)) {
    PUTS("TRUE: negative_infinity == -(2 * DOUBLE_MAX)");
  } else {
    num_errors++;
    puts("FALSE: negative_infinity == -(2 * DOUBLE_MAX)");
  }
  /*
   * "Infinities shall be interpreted in the affine sense, that is:
   * −∞ < {every finite number} < +∞"
   * 
   *    -- IEEE-754 2008 6.1 Infinity arithmetic
   */
  if (negative_infinity < positive_infinity) {
    PUTS("TRUE: negative_infinity < positive_infinity");
  } else {
    num_errors++;
    puts("FALSE: negative_infinity < positive_infinity");
  }
  if (negative_infinity < 0) {
    PUTS("TRUE: negative_infinity < 0");
  } else {
    num_errors++;
    puts("FALSE: negative_infinity < 0");
  }
  if (positive_infinity > 0) {
    PUTS("TRUE: positive_infinity > 0");
  } else {
    num_errors++;
    puts("FALSE: positive_infinity > 0");
  }

  if (num_errors != 0) {
    printf("%d test%s failed\n", num_errors, (num_errors == 1) ? "" : "s");
  } else {
    puts("ALL OK.");
  }
  exit(EXIT_SUCCESS);
}

如果某些编译器未通过这些测试(已知的异常列在上面的代码中的注释中),这些编译器是什么,是否可以以不同的方式进行?

我已经测试过,没有问题(全部选项:-W -Wall -O3):

  • gcc-4.6.real(Ubuntu / Linaro 4.6.4-6ubuntu2)4.6.4
  • gcc-4.8.real(Ubuntu 4.8.4-2ubuntu1~14.04)4.8.4
  • Ubuntu clang版本3.4-1ubuntu3(标签/ RELEASE_34 / final)(基于LLVM 3.4)
  • tcc version 0.9.25

所有GCC都使用选项nan != nan使第一个测试-ffast-math失败,但这是记录的行为,如果设置了该选项,GCC定义了一个宏,所以没问题。 MSVC显示与/fp:fast相同的行为,并且也有记录,但我找不到一种简单的方法来检测它,是否有一个?

1 个答案:

答案 0 :(得分:0)

对于Array,可能会启用“快速数学运算”,编译器只是对操作数进行位测试,而不是进行正确的浮点测试。

NaN永远不应该等于NaN,因为如果操作数不是数字,我们应该如何确定它们的等价数字?因此,绝不应该尝试使用nan != nan来确定值的NaN-ness。一个人需要一个特殊的功能来做到这一点,可能包括查看位,或让FPU在内部查看位。

如您的代码所示,检测作弊的方法是进行受控测试。