Какие операции и функции на +0.0 и -0.0 дают разные арифметические результаты?
В C, когда поддерживается ±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)
. Но они не дают различных арифметических результатов. Также 2 результата не должны быть NaN
.
Ответы
Ответ 1
Существует несколько стандартных операций и функций, которые формируют численно разные ответы между 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()
, AKA 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://stackoverflow.com/help/self-answer.
Ответ 2
Функция IEEE 754-2008 rsqrt
(которая будет в будущем стандарту ISO C) возвращает ± ∞ на ± 0, что довольно удивительно. И tgamma
также возвращает ± ∞ на ± 0. С MPFR mpfr_digamma
возвращает противоположность ± ∞ на ± 0.
Ответ 3
Я думаю об этом методе, но я не могу проверить до уик-энда, поэтому кто-то может сделать некоторые эксперименты на этом, если он или она, или просто скажите мне, что это ерунда:
-
Создать -0.0f. Должно быть возможным генерировать статично путем назначения крошечной отрицательной константы, которая недопустима для представления float.
-
Назначьте эту константу для летучего двойника и обратно для float.
Изменяя представление битов 2 раза, я предполагаю, что
стандартное битное представление компилятора для -0.0f теперь находится в
переменная. Компилятор не может перехитрить меня там, потому что полностью
другое значение может быть в переменной volatile между этими двумя копиями.
-
сравнить входные данные с 0.0f. Чтобы определить, есть ли у нас файл 0.0f/-0.0f
-
если он равен, назначьте двойную переменную ввода volitale, а затем вернитесь к 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.0 f (точное) в формате FP, мы могли бы отбросить кастинг до летучего двойника. (Но, возможно, сохранить float volatile, чтобы гарантировать, что с дезактивированными денормалами компилятор не может сделать какой-либо причудливый трюк, чтобы игнорировать операции, которые еще больше уменьшают абсолютное значение вещей, сравнивающихся с 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.