Минимальный и максимальный ноль

Меня беспокоят следующие случаи

min(-0.0,0.0)
max(-0.0,0.0)
minmag(-x,x) 
maxmag(-x,x)

Согласно Wikipedia IEEE 754-2008 говорится о минимальном и максимальном

Операции min и max определены, но оставляют некоторую свободу действий для случая, когда входы равны по значению, но отличаются в представлении. В частности:

min (+ 0, -0) или min (-0, + 0) должно производить что-то со значением нуля, но всегда может возвращать первый аргумент.

Я несколько тестов сравнивал fmin, fmax, min и max, как определено ниже

#define max(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a > _b ? _a : _b; })
#define min(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a < _b ? _a : _b; })

и _mm_min_ps и _mm_max_ps, которые вызывают инструкцию SSE minps и maxps.

Вот результаты (код, который я использовал для проверки этого, размещен ниже)

fmin(-0.0,0.0)       = -0.0
fmax(-0.0,0.0)       =  0.0
min(-0.0,0.0)        =  0.0
max(-0.0,0.0)        =  0.0
_mm_min_ps(-0.0,0.0) =  0.0
_mm_max_ps(-0.0,0.0) = -0.0

Как вы можете видеть, каждый случай возвращает разные результаты. Итак, мой главный вопрос - что говорят стандартные библиотеки C и С++?. fmin(-0.0,0.0) должен равняться -0.0 и fmax(-0.0,0.0) должен равняться 0.0 или могут быть разными реализациями, позволяющими определять его иначе? Если это реализована реализация, это означает, что для обеспечения совместимости кода с другой реализацией стандартной библиотеки C (.eg от разных компиляторов) необходимо выполнить проверку, чтобы определить, как они реализуют min и max?

Как насчет minmag(-x,x) и maxmag(-x,x)? Они определены в IEEE 754-2008. Являются ли эти реализации определенными, по крайней мере, в IEEE 754-2008? Я делаю вывод из комментария Wikipdia о min и max, что они определены в реализации. Но стандартная библиотека C не определяет эти функции, насколько я знаю. В OpenCL эти функции определяются как

maxmag Возвращает x, если | х | > | y |, или y, если | y | > | x |, в противном случае fmax (x, y).

minmag Возвращает x, если | x | < | y |, или y, если | y | < | x |, в противном случае fmin (x, y).

Набор инструкций x86 не содержит инструкций minmag и maxmag, поэтому я должен был реализовать их. Но в моем случае мне нужна производительность и создание ветки для случая, когда величины равны, неэффективны.

Набор команд Itaninum содержит команды minmag и maxmag (famin и famax), и в этом случае, насколько я могу сказать (от чтения), в этом случае он возвращает второй аргумент. Однако это не то, что minps и maxps. Странно, что _mm_min_ps(-0.0,0.0) = 0.0 и _mm_max_ps(-0.0,0.0) = -0.0. Я бы ожидал, что они вернут первый аргумент в обоих случаях или второй. Почему инструкции minps и maxps определены таким образом?

#include <stdio.h>
#include <x86intrin.h>
#include <math.h>

#define max(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a > _b ? _a : _b; })

#define min(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a < _b ? _a : _b; })

int main(void) {
    float a[4] = {-0.0, -1.0, -2.0, -3.0};   
    float b[4] = {0.0, 1.0, 2.0, 3.0};
    __m128 a4 = _mm_load_ps(a);
    __m128 b4 = _mm_load_ps(b);
    __m128 c4 = _mm_min_ps(a4,b4);
    __m128 d4 = _mm_max_ps(a4,b4);
    { float c[4]; _mm_store_ps(c,c4); printf("%f %f %f %f\n", c[0], c[1], c[2], c[3]); }
    { float c[4]; _mm_store_ps(c,d4); printf("%f %f %f %f\n", c[0], c[1], c[2], c[3]); }

    printf("%f %f %f %f\n", fmin(a[0],b[0]), fmin(a[1],b[1]), fmin(a[2],b[2]), fmin(a[3],b[3]));
    printf("%f %f %f %f\n", fmax(a[0],b[0]), fmax(a[1],b[1]), fmax(a[2],b[2]), fmax(a[3],b[3]));

    printf("%f %f %f %f\n", min(a[0],b[0]), min(a[1],b[1]), min(a[2],b[2]), min(a[3],b[3]));
    printf("%f %f %f %f\n", max(a[0],b[0]), max(a[1],b[1]), max(a[2],b[2]), max(a[3],b[3]));    
}
//_mm_min_ps: 0.000000, -1.000000, -2.000000, -3.000000
//_mm_max_ps: -0.000000, 1.000000, 2.000000, 3.000000
//fmin: -0.000000, -1.000000, -2.000000, -3.000000
//fmax: 0.000000, 1.000000, 2.000000, 3.000000
//min: 0.000000, -1.000000, -2.000000, -3.000000
//max: 0.000000, 1.000000, 2.000000, 3.000000

Edit:

В отношении С++ я тестировал std::min(-0.0,0.0) и std::max(-0.0,0.0), и оба возвращали -0.0. Что показывает, что std::min не совпадает с fmin, а std::max не совпадает с fmax.

Ответы

Ответ 1

Почему бы не прочитать стандарт самостоятельно? Статья в Wikipedia для IEEE содержит ссылки на стандарт.

Примечание. Стандартный документ C недоступен. Но окончательный проект (что я связал, поиск, чтобы найти версию pdf). Тем не менее, я не видел, чтобы последний документ был приведен здесь, и AFAIK там в основном были исправлены некоторые опечатки; Ничего не изменилось. IEEE, однако, доступен бесплатно.

Обратите внимание, что компилятор не должен придерживаться стандартов (некоторые встроенные компиляторы/версии, например, не реализуют IEEE-совместимые значения с плавающей запятой, но по-прежнему совместимы с C), просто прочитайте стандарт для деталей). Поэтому см. Документацию по компилятору, чтобы увидеть совместимость. MS-VC, например, даже не совместим с C99 (и никогда не будет ben), тогда как gcc и clang/llvm (в основном) совместимы с C11 в текущих версиях (gcc с 4.9.2 по крайней мере, в частях с 4.7).

В общем, при использовании MS-VC проверьте, действительно ли он поддерживает все используемые стандартные функции. Он фактически не полностью соответствует текущему стандарту, а не C99.

Ответ 2

Основной проблемой в этом случае является фактическая базовая математика, игнорирующая репрезентативные проблемы. В вашем вопросе есть несколько последствий, которые, я считаю, ошибочны. -0,0 < 0.0 - false. -0.0 - отрицательное число - false. 0.0 - это положительное число. На самом деле, нет такой вещи, как -0.0, хотя есть представление IEEE 754 нуля с установленным битом знака.

Кроме того, поведение функций min/max является лишь небольшим фрагментом законных операций с плавающей запятой, которые могут давать нули с разными знаковыми битами. Поскольку единицы с плавающей запятой могут возвращать (-) 0,0 для выражений типа -7 - -7, вам также нужно выяснить, что с этим делать. Я также хотел бы отметить, что | 0.0 | может фактически вернуть 0.0 с установленным битом знака, поскольку -0.0 является абсолютным значением 0.0. Проще говоря, что касается математики, то 0.0 составляет -0.0. Это одно и то же.

Единственный способ, которым вы можете протестировать 0.0 с установленным битом знака, - это отказаться от математических выражений и вместо этого изучить двоичное представление таких значений. Но в чем смысл? Есть только один законный случай, о котором я могу думать: генерация двоичных данных с двух разных машин, которые должны быть бит-бит-бит идентичными. В этом случае вам также нужно будет беспокоиться о сигнальных и спокойных значениях NaN, так как имеется очень много алиасов этих значений (10 ^ 22-1 SNaN и 10 ^ 22 QNaN для плавающих по одиночной точности и около 10 ^ 51 значений для двойной точности).

В таких ситуациях, когда бинарное представление критично (это абсолютно НЕ для математических вычислений), тогда вам придется писать код для условия всех поплавков при записи (нули, тихие NaN и сигнализацию NaN).

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