Минимум и максимум подписанного нуля
Я обеспокоен следующими случаями
min(-0.0,0.0)
max(-0.0,0.0)
minmag(-x,x)
maxmag(-x,x)
Согласно Википедии IEEE 754-2008 говорит в отношении мин и макс
Операции min и max определены, но оставляют некоторую свободу для случая, когда входные данные равны по значению, но различаются по представлению. Особенно:
min (+ 0, −0) или min(−0,+0) должны производить что-то со значением ноль, но всегда могут возвращать первый аргумент.
Я сделал несколько тестов сравнить fmin
, fmax
мин и макс, как определено ниже
#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 и 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? Из комментария Wikepdia к 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
Редактировать:
Что касается C++, я тестировал std::min(-0.0,0.0)
а также std::max(-0.0,0.0)
и оба возвращаются -0.0
, Что показывает, что это std::min
это не то же самое, что fmin
а также std::max
это не то же самое, что fmax
,
2 ответа
Почему бы не прочитать стандарт самостоятельно? Статья в Википедии для IEEE содержит ссылки на стандарт.
Примечание: стандартный документ C не доступен свободно. Но окончательный вариант (это то, что я связал, поиск, чтобы найти PDF-версию). Однако я не видел, чтобы здесь был процитирован окончательный документ, и AFAIK, в основном, были исправлены некоторые опечатки; Ничего не изменилось. IEEE, однако, доступен бесплатно.
Обратите внимание, что компилятору не нужно придерживаться стандартов (например, некоторые встроенные компиляторы / версии не реализуют значения с плавающей запятой, соответствующие IEEE, но все еще соответствуют C- просто прочитайте стандарт для подробностей). Так что смотрите документацию компилятора, чтобы увидеть совместимость. Например, MS-VC даже не совместим с C99 (и никогда не будет ben), в то время как gcc и clang/llvm (в основном) совместимы с C11 в текущих версиях (по крайней мере, gcc начиная с 4.9.2, по частям с 4.7).
В общем, при использовании MS-VC проверьте , поддерживает ли он все стандартные функции. На самом деле он не полностью соответствует ни текущему стандарту, ни C99.
Фундаментальная проблема в этом случае - фактическая математика, игнорирующая репрезентативные проблемы. В вашем вопросе есть несколько следствий, которые я считаю ошибочными. -0.0 <0.0 ложно. -0.0 отрицательное число ложно. 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).
Для любой вычислительной цели бесполезно беспокоиться о том, установлен ли знаковый бит или сброшен, когда значение равно нулю.