Какие операции и функции на +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
не восстанавливается.
Взято отсюда.