Минимум и максимум подписанного нуля

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

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).

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

Другие вопросы по тегам