Минимальный и максимальный ноль
Меня беспокоят следующие случаи
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).
Для любой вычислительной цели бесполезно беспокоиться о том, установлен ли или бит знака, когда значение равно нулю.