Ноль с плавающей запятой в реализациях C (инварианты IEEE 754?)

У меня есть следующее выражение C (переменные - 32-битные числа с плавающей запятой)

float result = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)

Предполагая, что x0==x1 и y0==y1 (и под == я имею в виду идентичность двоичного представления), могу ли я полагаться на тот факт, что выражение обязательно будет оцениваться как ноль (например, все биты числа с плавающей запятой установлены в 0)? Другими словами, могу ли я предположить, что всегда выполняются следующие инварианты?

memcmp(&a, &b, sizeof(float) == 0 => memcmp(a-b, (uint32_t)0, sizeof(float)) == 0
0*a == 0.0

Можно с уверенностью предположить, что все значения являются конечными числами (никаких INFINITY или NaN).

Изменить: как указано в ответах, умножение на 0 может давать нули со знаком. Могу ли я все-таки рассчитывать на то, что результат выражения будет равен 0.0 по правилам FP-сравнения, т.е.:

(result == 0.0) 

Редактировать 1: Заменены приведения типов вызовами memcmp, чтобы лучше проиллюстрировать вопрос.

P.S. Я ограничиваю свой код только совместимыми компиляторами C11, если это имеет значение. Я также готов полагаться на поддержку STDC_IEC_559, если это поможет в моем случае.


person MrMobster    schedule 13.07.2016    source источник
comment
Можем ли мы также предположить, что y2 - y0 и x2 - x0 будут конечными?   -  person Oliver Charlesworth    schedule 13.07.2016
comment
@OliverCharlesworth: да. Если это не так, я в порядке с неопределенными результатами.   -  person MrMobster    schedule 13.07.2016
comment
Какой тип a и b? Если они не uint32_t, ваш код вызывает неопределенное поведение (нарушение эффективного правила типа). Так что все разрешено стандартом. То же самое для ZERO   -  person too honest for this site    schedule 13.07.2016
comment
@Olaf: хорошо, тогда предположим, что у нас есть union {uint32_t ival, float fval} вместо приведения типов. Или memcmp(&a, &b, sizeof(float))==0. Идея состоит в том, чтобы сказать, что a и b содержат точно такое же двоичное представление. Я редактирую вопрос, чтобы сделать это более понятным.   -  person MrMobster    schedule 13.07.2016
comment
Есть еще одна проблема: вы спрашиваете о плавающей запятой C или о плавающей запятой IEEE754?   -  person too honest for this site    schedule 13.07.2016
comment
Я спрашиваю о возможных гарантиях поведения, которое описываю. Если IEEE754 дает мне эту гарантию, я готов ограничить свой код только целями, совместимыми с IEEE754. Если цель не соответствует стандарту IEEE754, я всегда могу добавить дополнительные проверки безопасности.   -  person MrMobster    schedule 13.07.2016


Ответы (1)


Упоминание C11 просто сбивает с толку ваш вопрос, потому что IEEE 754 не требуется ни одним стандартом C.

При этом, просто предполагая 32-битные числа с плавающей запятой IEEE 754 и не делая никаких предположений о x2 и y2 (кроме того, что они не бесконечны или NaN), вы не можете предположить, что все биты результата будут равны 0. Числа IEEE 754 имеют два нуля, один отрицательный и один положительный, и если выражение (y2 - y0) - (x2 - x0) отрицательное, результатом умножения его на ноль будет отрицательный нуль.

Мы можем проверить это на этом коротком примере:

#include <stdio.h>
#include <stdint.h>

int
main(int argc, char **argv)
{
    union {
        float f;
        uint32_t i;
    } foo;

    float a = 0;
    float b = -1;

    foo.f = a * b;
    printf("0x%x\n", foo.i);

    return 0;
}

Результат (заметьте, никаких оптимизаций, так как я не хочу, чтобы компилятор был умным):

$ cc -o foo foo.c && ./foo
0x80000000

О, я только что заметил, что вторая часть вашего вопроса, которую вы назвали «другими словами», на самом деле не является другими словами, потому что это другой вопрос.

Начать с:

(*(uint32_t*)(&a) == *(uint32_t*)(&b))

не эквивалентен a == b для чисел с плавающей запятой, потому что -0 == 0. А вместе с этим другая часть предположения разваливается, потому что -0 - 0 дает вам -0. Я не могу заставить любое другое вычитание равных чисел генерировать отрицательный ноль, но это не значит, что это невозможно, я почти уверен, что стандарты не применяют один и тот же алгоритм для вычитания во всех реализациях, поэтому знаковый бит мог как-то проникнуть туда.

person Art    schedule 13.07.2016
comment
Хороший улов с этим отрицательным нулем. На самом деле я бы предположил, что ответ будет «Да» (до прочтения вашего). - person barak manos; 13.07.2016
comment
Спасибо, Арт! Вы, конечно, правы, что упоминание стандарта IEEE немного вводит в заблуждение. Что меня больше всего интересует, так это знать, следует ли мне встраивать дополнительные проверки личности на обычном оборудовании, с обычными компиляторами C11, или я могу безопасно пропустить проверки. Подписанный нулевой комментарий очень полезен! Я был бы в порядке, если бы выполнял сравнение FP с 0 вместо нуля с нулевым битовым шаблоном. Будет ли это безопасно? - person MrMobster; 13.07.2016
comment
@MrMobster Ваша самая безопасная ставка для сравнения числа с плавающей запятой с нулем - это a == 0.0. Это будет работать независимо от того, является ли a отрицательным или положительным. - person Art; 13.07.2016
comment
Стоит отметить, что согласно C11 §6.10.8.3 Conditional feature macros - ... __STDC_IEC_559__ The integer constant 1, intended to indicate conformance to the specifications in annex F (IEC 60559 floating-point arithmetic). и C11 §Annex F.2 ... The float type matches the IEC 60559 single format. .... Спецификация IEC 60559 идентична IEEE 754-2008. - person pah; 13.07.2016
comment
Линии приведения фактически вызывают UB. Все может случиться. С точки зрения стандарта любое поведение является законным. - person too honest for this site; 13.07.2016