2016-08-31 1 views
5

Я прочитал это в популярной книге о компьютерной графике,Пропуска «тест на ноль» чеки в IEEE 754

Есть много численных расчетов, которые становятся гораздо проще, если программист используют преимущества IEEE правил. Например, рассмотрим выражение:

а = 1/(1/B + 1/с)

Такие выражения возникают с резисторами и линзами. Если деление на ноль привело к сбою программы (как это было во многих системах перед плавающей точкой IEEE), тогда для проверки малых или нулевых значений b или c потребуется два оператора if. Вместо этого, с плавающей точкой IEEE, если b или c равно нулю, мы получим нулевое значение для a по желанию.

А как насчет случая, когда b=+0 и c=-0? Затем a=1/inf-inf=nan. Является ли книга неправильной в отношении этой оптимизации, или я что-то не понял? Похоже, нам все равно понадобится хотя бы одна проверка на наличие признаков b & c, а не на отсутствие проверок.

Редактировать Одно из предложений в комментариях состояло в том, чтобы сделать пост-проверку для NaN. Это идиоматический способ «воспользоваться» этим типом номера?

bool is_nan(float x) { return x != x; } 

float optic_thing(float b, float c) { 
    float result = 1.0f/(1.0f/b + 1.0f/c); 
    if (is_nan(result)) result = 0; 
    return result; 
} 
+1

Не хватит ли простого почтового чека для NaN? – bipll

+1

Вероятно, я что-то пропустил, но непонятно, почему это утверждение было бы истинным. Если либо b, либо c равно нулю, то это выражение становится равным 1/бесконечности, что не равно нулю. –

+0

Вам понадобится * три * проверки 0 для вашего выражения, если 'b' или' c' могут быть отрицательными. Потому что '1/b + 1/c' будет' 0', если 'b == -c' –

ответ

2

Но как насчет случая, когда b=+0 и c=-0?

Преобразование -0.0 к +0.0 без ветвления путем добавления 0.0.

int main(void) { 
    double b = +0.0; 
    double c = -0.0; 
    printf("%e\n", b); 
    printf("%e\n", c); 
    printf("%e\n", 1.0/(1.0/b + 1.0/c)); 
    b += 0.0; 
    c += 0.0; 
    printf("%e\n", 1.0/(1.0/b + 1.0/c)); 
    return 0; 
} 

Выход

0.000000e+00 
-0.000000e+00 
nan 
0.000000e+00 

[править] С другой стороны, любые значения вблизи 0.0, но не 0.0, вероятно, числовые артефакты и не точны сопротивления значения. Вышеизложенное все еще имеет проблемы с крошечные пары значений, такие как b = DBL_TRUE_MIN; c = -b; выпуск 1.0/tiny ->Infinity и +Infinity + -Infinity ->NAN. Можно использовать либо более широкую плавающую точку для вычисления частного, либо узкие операнды.

b += 0.0; 
    c += 0.0; 
    printf("%Le\n", 1.0/(1.0L/b + 1.0L/c)); 

    // Lose some precision, but we are talking about real resistors/lenses here. 
    b = (float) b + 0.0f; 
    c = (float) c + 0.0f; 
    printf("%e\n", 1.0/(1.0/b + 1.0/c)); 
+1

Добавление + ноль действительно канонический способ «очистки», нежелательные экземпляры -zero. Его также можно комбинировать с умножением, если необходимо, например. 'fma (a, b, +0.0)'. Конечно, все это предполагает, что один работает с компилятором (и с коммутаторами компилятора), которые в первую очередь уважают поведение IEEE-754 (т. Е. Не оптимизируют дополнения с нулем). – njuffa

+0

@njuffa Правда о «уважении надлежащего IEEE-754». К счастью, сообщение OP помечено и озаглавлено IEEE 754. BTW: вы можете наслаждаться [+0.0 и -0.0 давать разные арифметические результаты] (http://stackoverflow.com/q/25332133/2410359), что несколько противоположно тому, что OP пытаясь сделать. – chux

+0

Я, видимо, знаю вопрос и ваш ответ (теперь сообщество wiki), потому что я вижу, что я поддержал ответ. Поскольку я не комментировал и не редактировал ответ, я предполагаю, что я не мог вспомнить пример, который уже не упоминался. – njuffa

Смежные вопросы