Какие операции и функции на +0.0 и -0.0 дают разные арифметические результаты?

В С, когда ±0.0 поддерживается, -0.0 или же +0.0 назначен на double как правило, не имеет никакого арифметического значения. Хотя они имеют разные битовые комбинации, они арифметически сравниваются как равные.

double zp = +0.0;
double zn = -0.0;
printf("0 == memcmp %d\n", 0 == memcmp(&zn, &zp, sizeof zp));// --> 0 == memcmp 0
printf("==          %d\n", zn == zp);                        // --> ==          1

Вдохновленный комментарием @Pascal Cuoq, я ищу еще несколько функций в стандарте C, которые обеспечивают арифметически разные результаты.

Примечание: многие функции, такие как sin(), вернуть +0.0 от f(+0.0) а также -0.0 от f(-0.0), Но они не дают разных арифметических результатов. Также оба результата не должны быть NaN,

4 ответа

Решение

Есть несколько стандартных операций и функций, которые формируют численно разные ответы между f(+0.0) а также f(-0.0),

Различные режимы округления или другие реализации с плавающей запятой могут давать разные результаты.

#include <math.h>

double inverse(double x) { return 1/x; }

double atan2m1(double y) { return atan2(y, -1.0); }

double sprintf_d(double x) {
  char buf[20];
  // sprintf(buf, "%+f", x);   Changed to e
  sprintf(buf, "%+e", x);
  return buf[0];  // returns `+` or `-`
}

double copysign_1(double x) { return copysign(1.0, x); }

double signbit_d(double x) {
  int sign = signbit(x);  // my compile returns 0 or INT_MIN
  return sign;
}

double pow_m1(double x) { return pow(x, -1.0); }

void zero_test(const char *name, double (*f)(double)) {
  double fzp = (f)(+0.0);
  double fzn = (f)(-0.0);
  int differ = fzp != fzn;
  if (fzp != fzp && fzn != fzn) differ = 0;  // if both NAN
  printf("%-15s  f(+0):%-+15e %s  f(-0):%-+15e\n", 
      name, fzp, differ ? "!=" : "==", fzn);
}

void zero_tests(void) {
  zero_test("1/x",             inverse);
  zero_test("atan2(x,-1)",     atan2m1);
  zero_test("printf(\"%+e\")", sprintf_d);
  zero_test("copysign(x,1)",   copysign_1);
  zero_test("signbit()",       signbit_d);
  zero_test("pow(x,-odd)",     pow_m1);;  // @Pascal Cuoq
  zero_test("tgamma(x)",       tgamma);  // @vinc17 @Pascal Cuoq
}

Output:
1/x              f(+0):+inf             !=  f(-0):-inf           
atan2(x,-1)      f(+0):+3.141593e+00    !=  f(-0):-3.141593e+00  
printf("%+e")    f(+0):+4.300000e+01    !=  f(-0):+4.500000e+01   
copysign(x,1)    f(+0):+1.000000e+00    !=  f(-0):-1.000000e+00  
signbit()        f(+0):+0.000000e+00    !=  f(-0):-2.147484e+09 
pow(x,-odd)      f(+0):+inf             !=  f(-0):-inf           
tgamma(x)        f(+0):+inf             !=  f(-0):+inf  

Заметки:
tgamma(x) подошел == на моей машине gcc 4.8.2, но правильно != на других.

rsqrt()АКА 1/sqrt() это, возможно, будущая стандартная функция C. Может / не может также работать.

double zero = +0.0; memcpy(&zero, &x, sizeof x) может показать x это другая битовая комбинация, чем +0.0 но x все еще может быть +0.0, Я думаю, что некоторые форматы FP имеют много битовых шаблонов, которые +0.0 а также -0.0, TBD.

Это ответ на свой вопрос, предоставленный https://stackru.com/help/self-answer.

Функция IEEE 754-2008 rsqrt (это будет в будущем стандарте ISO C) возвращает ±∞ на ±0, что довольно удивительно. А также tgamma также возвращает ±∞ на ±0. С MPFR, mpfr_digamma возвращает противоположность ±∞ на ±0.

Я думаю об этом методе, но я не могу проверить до выходных, поэтому кто-то может провести некоторые эксперименты на этом, если он / она любит, или просто сказать мне, что это чепуха:

  • Создайте -0.0f. Должна быть возможность генерировать статически, назначая крошечную отрицательную константу, которая не соответствует представлению с плавающей точкой.

  • Присвойте эту константу изменчивому двойнику и вернитесь к плавающей точке.

    Изменяя битовое представление 2 раза, я предполагаю, что специфичное для компилятора стандартное битовое представление для -0.0f теперь находится в переменной. Компилятор не может перехитрить меня там, потому что между этими двумя копиями может быть совершенно другое значение в переменной volatile.

  • сравните ввод с 0.0f. Чтобы определить, есть ли у нас случай 0.0f/-0.0f

  • если он равен, присвойте переменную input volitale double, а затем вернитесь к float.

    Я снова предполагаю, что теперь он имеет стандартное представление компилятора для 0.0f

  • получить доступ к битовым шаблонам по объединению и сравнить их, чтобы определить, равен ли он -0.0f

Код может быть что-то вроде:

typedef union
{
  float fvalue;
  /* assuming int has at least the same number of bits as float */
  unsigned int bitpat;
} tBitAccess;

float my_signf(float x)
{
  /* assuming double has smaller min and 
     other bit representation than float */

  volatile double refitbits;
  tBitAccess tmp;
  unsigned int pat0, patX;

  if (x < 0.0f) return -1.0f;
  if (x > 0.0f) return 1.0f;

  refitbits = (double) (float) -DBL_MIN;
  tmp.fvalue = (float) refitbits;
  pat0 = tmp.bitpat;

  refitbits = (double) x; 
  tmp.fvalue = (float) refitbits;
  patX = tmp.bitpat;

  return (patX == pat0)? -1.0f : 1.0f;

}
  • Это не стандартная функция или оператор, а функция, которая должна различать знаки -0,0 и 0,0.
  • Он основан (главным образом) на предположении, что поставщик компилятора не использует разные битовые комбинации для -0.0f в результате изменения форматов, даже если формат с плавающей запятой это разрешит, и если это имеет место, он не зависит от выбранного битовый паттерн.
  • Для форматов с плавающей запятой, которые имеют точно один шаблон для -0.0f, эта функция должна выполнять свои задачи без знания порядка следования битов в этом шаблоне.
  • Другие предположения (о размере типов и т. Д.) Могут быть обработаны с помощью переключателей прекомпилятора для констант float.h.

Редактировать: Подумав еще раз: если мы сможем заставить значение, сравниваемое с (0.0 || -0.0), ниже наименьшего представимого ненормального (субнормального) числа с плавающей запятой или его отрицательного аналога, и второго шаблона для -0.0f (точного) не существует) в формате FP мы можем отбросить приведение к volatile double. (Но, возможно, сохраните переменную float, чтобы гарантировать, что с деактивированными денормальными значениями компилятор не сможет сделать какой-нибудь причудливый трюк, чтобы игнорировать операции, которые еще больше уменьшают абсолютную ценность сравнения, равного 0,0.)

Код может выглядеть так:

typedef union
{
  float fvalue;
  /* assuming int has at least the same number of bits as float */
  unsigned int bitpat;
} tBitAccess;

float my_signf(float x)
{

  volatile tBitAccess tmp;
  unsigned int pat0, patX;

  if (x < 0.0f) return -1.0f;
  if (x > 0.0f) return 1.0f;

  tmp.fvalue = -DBL_MIN;

  /* forcing something compares equal to 0.0f below smallest subnormal 
     - not sure if one abs()-factor is enough */
  tmp.fvalue = tmp.fvalue * fabsf(tmp.fvalue);
  pat0 = tmp.bitpat;

  tmp.fvalue = x; 
  tmp.fvalue = tmp.fvalue * fabsf(tmp.fvalue);
  patX = tmp.bitpat;

  return (patX == pat0)? -1.0f : 1.0f;

}

Это может не работать с причудливыми методами округления, которые предотвращают округление от отрицательных значений до -0.0.

Не совсем ответ на вопрос, но может быть полезно знать:

Только что столкнулся с этим случаем a - b = c => b = a - c, что не выполняется, если a является 0.0 а также bявляется . У нас есть 0.0 - (-0.0) = 0.0 => b = 0.0 - 0.0 = 0.0. Знак утерян. В -0.0 не восстанавливается.

Взято отсюда.

Другие вопросы по тегам