Обнаружение и настройка отрицательного нуля

У меня есть код, который был портирован с Java на C++.

// since this point is a vector from (0,0,0), we can just take the
// dot product and compare
double r = point.dot(normal);
return (r>=0.0);

Но в C++ r может быть либо +0.0, либо -0.0, когда r равно -0.0, проверка не проходит.

Я попытался настроить отрицательный ноль в приведенном ниже коде, но он никогда не попадает в строку DEBUG («Отрицательный ноль»). Но r2 выводится как +0.0.

// since this point is a vector from (0,0,0), we can just take the
// dot product and compare
double r = point.dot(normal);
if (std::signbit(r)){
    double r2 = r*-1;
    DEBUG("r=%f r=%f", r,r2);
    if (r2==0.0) {
        DEBUG("Negative zero");
        r = 0.0; //Handle negative zero
    }
}
return (r>=0.0);

Любое предложение?

ПРОВЕРОЧНЫЙ КОД:

DEBUG("point=%s", point.toString().c_str());
DEBUG("normal=%s", normal->toString().c_str());
double r = point.dot(normal);
DEBUG("r=%f", r);
bool b = (r>=0.0);
DEBUG("b=%u", b);

РЕЗУЛЬТАТЫ ИСПЫТАНИЙ:

DEBUG - point=Vector3D[ x=1,y=0,z=0 ]
DEBUG - normal=Vector3D[ x=0,y=-0.0348995,z=0.0348782 ]
DEBUG - r=0.000000
DEBUG - b=1
DEBUG - point=Vector3D[ x=1,y=0,z=0 ]
DEBUG - normal=Vector3D[ x=-2.78269e-07,y=0.0174577,z=-0.0174391 ]
DEBUG - r=-0.000000
DEBUG - b=0

ССЗ:

Target: x86_64-linux-gnu
--enable-languages=c,c++,fortran,objc,obj-c++ 
--prefix=/usr 
--program-suffix=-4.6 
--enable-shared 
--enable-linker-build-id 
--with-system-zlib 
--libexecdir=/usr/lib 
--without-included-gettext 
--enable-threads=posix 
--with-gxx-include-dir=/usr/include/c++/4.6 
--libdir=/usr/lib 
--enable-nls 
--with-sysroot=/ 
--enable-clocale=gnu 
--enable-libstdcxx-debug 
--enable-libstdcxx-time=yes 
--enable-gnu-unique-object 
--enable-plugin 
--enable-objc-gc 
--disable-werror 
--with-arch-32=i686 
--with-tune=generic 
--enable-checking=release 
--build=x86_64-linux-gnu 
--host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) 

ФЛАГИ:

CXXFLAGS += -g -Wall -fPIC

ОТВЕЧАТЬ:

Я использовал ответ @amit, чтобы сделать следующее.

return (r>=(0.0-std::numeric_limits<double>::epsilon()));

Что, кажется, работает.


person Justin    schedule 07.12.2012    source источник
comment
поведение нестандартное. какие параметры компилятора и математики с плавающей запятой вы используете. я подозреваю g++ с fastmath   -  person Cheers and hth. - Alf    schedule 07.12.2012
comment
Кроме того, опубликуйте полный и не слишком большой пример, демонстрирующий проблему, с полными инструкциями по сборке, чтобы его можно было воспроизвести.   -  person Cheers and hth. - Alf    schedule 07.12.2012
comment
Благодарю. как оказалось (см. мой ответ), нестандартным было только описанное поведение, относительно отрицательного нуля, сравнивающего неравное нулю. на самом деле, по-видимому, нет отрицательного нуля, а есть только очень маленькое отрицательное значение, которое при заданном формате вывода представлено как все нулевые цифры со знаком минус впереди.   -  person Cheers and hth. - Alf    schedule 07.12.2012
comment
@Cheersandhth.-Альф и hth Да, это была проблема. Я использовал std::numeric_limits‹double›::epsilon(), чтобы решить эту проблему.   -  person Justin    schedule 07.12.2012
comment
о, я забыл упомянуть: поскольку проблема заключалась только в восприятии того, что происходило, наиболее вероятно, что лучшим способом действий было бы ничего не делать (кроме, возможно, о презентации). сравнивая с эпсилон и обнулением, вы, возможно, внесли ошибку. что, по иронии судьбы, может выглядеть как правильное поведение при оценке в контексте ошибочного восприятия.   -  person Cheers and hth. - Alf    schedule 07.12.2012
comment
Принятый ответ и ваш r>(0.0-std::numeric_limits<double>::epsilon()) не делают различия между -0 и 0; оба меньше, чем эпсилон. Значения, которые уменьшаются до -0 (если установлены флаги flow-to-zero/denormals-are-zero), лежат между 0 и эпсилон.   -  person RJFalconer    schedule 04.06.2014


Ответы (6)


Ну, общее предложение при использовании doubles помнить, что они не точны. Таким образом, если важно равенство, обычно рекомендуется использовать какой-либо коэффициент допуска.

В твоем случае:

if (|r - 0.0| >= EPSILON)

где EPSILON — ваш коэффициент допуска, даст true, если r не равно 0,0, с интервалом не менее EPSILON.

person amit    schedule 07.12.2012
comment
Спасибо. Но я надеялся на более общее решение. Это может быть лучший подход, мне придется продолжать искать. - person Justin; 07.12.2012
comment
Изменить мой вопрос, чтобы включить ответ - person Justin; 07.12.2012
comment
Хотя это правильно и примечательно, это не отвечает на заданный вопрос, поскольку не делает различий между 0.0 и -0.0; они равны друг другу, и оба будут меньше эпсилон. - person RJFalconer; 04.06.2014
comment
@RJFalconer Спасибо, я как-то пропустил абсолютное значение, оно должно быть там. (фиксированный). Также обратите внимание, что для r==0.0 и r== -0.0, r < 0.0 - EPSILON (конечно, при положительном EPSILON). - person amit; 04.06.2014
comment
Я не думаю, что это правильно. Для r==-0.0, r > 0.0 - EPSILON. Даже самый маленький эпсилон (std::numeric_limits<double>::epsilon) меньше -0.0. - person RJFalconer; 04.06.2014
comment
@RJFalconer Я указывал, что находка не равна, обратите внимание, что (-EPSILON < 0.0) == (-EPSILON < -0.0), и в этом суть - неважно, какой это ноль, вы можете работать с ними обоими одинаково, используя этот подход. (Нахождение равного — это просто выполнение not вышеизложенного, и довольно просто). - person amit; 04.06.2014
comment
if (r >= |0.0 - EPSILON|) Это необязательно; if (r >= EPSILON) достаточно. - person Don Larynx; 01.05.2015

В некоторых старых системах (например, до IEE754) вы можете обнаружить, что проверки на равенство с 0 не работают для отрицательного-0:

if (a == 0.0) // when a==-0.0, fails

вы можете обойти это, добавив 0.0 к значению перед сравнением:

if ((a+0.0) == 0.0) // when a == -0.0, succeeds

Однако я хотел бы предупредить, что комбинации аппаратного и программного обеспечения, которые действительно требуют этого, весьма необычны. Последний раз мне приходилось это делать на мэйнфрейме Control Data. Даже там он возник только при несколько необычных обстоятельствах: компилятор Фортрана позволял генерировать отрицательные нули и умел их компенсировать при сравнениях. Компилятор Pascal сгенерировал код для преобразования отрицательных нулей в обычные нули в ходе вычислений.

Следовательно, если вы написали подпрограмму на Фортране и вызвали ее из Паскаля, вы можете столкнуться с этой проблемой и предотвратить ее, как описано выше, добавив 0.0 перед выполнением сравнения.

Я собираюсь поставить довольно хорошие шансы на то, что ваша проблема на самом деле не связана со сравнением с отрицательным нулем. Все достаточно современное оборудование, о котором я знаю, обрабатывает это полностью автоматически, поэтому программное обеспечение никогда не должно учитывать это.

person Jerry Coffin    schedule 07.12.2012
comment
Учитывая новый более подробный пример OP, я думаю, вы выиграли эту ставку :-) - person Cheers and hth. - Alf; 07.12.2012
comment
if (a == 0.0) // when a==-0.0, fails где? Это нарушает IEEE 754. - person RJFalconer; 04.06.2014
comment
@RJFalconer: Как указано в ответе, конкретным рассматриваемым примером был мэйнфрейм Control Data (который на несколько лет предшествовал IEEE 754). Как сказано в моем заключительном предложении, это выполняется автоматически на любом достаточно современном оборудовании. В следующий раз вы, возможно, захотите прочитать ответ, прежде чем голосовать против него. - person Jerry Coffin; 04.06.2014
comment
На самом деле это не совсем то, что вы утверждаете в своем ответе. Вы просто говорите, что это последний раз, когда вам приходилось это делать. Если вы уточните свое открытие, я удалю отрицательный голос. - person RJFalconer; 04.06.2014
comment
@RJFalconer: я не понимаю, что, по вашему мнению, требует пояснений. Что касается того, что я сказал, вот CnP: все достаточно современное оборудование, о котором я знаю, обрабатывает это полностью автоматически, поэтому программное обеспечение вообще никогда не должно учитывать это. - person Jerry Coffin; 04.06.2014
comment
Чтобы избежать спама в комментариях, я внесу правку; пожалуйста, верните / обновите, как вы считаете нужным. - person RJFalconer; 04.06.2014
comment
Делает ли добавление 0.0 вычисление заметно медленнее? Я столкнулся с ситуацией, когда -0 возвращается, когда этого не должно быть. - person Aaron Franke; 31.05.2018

Предположительно, вы имели в виду что-то вроде if (r2==-0.0). Тем не менее, как отрицательный 0, так и положительный 0 будут сравниваться равными. Для всех намерений и целей нет никакой разницы между ними. Вероятно, вам не нужен специальный регистр для отрицательного 0. Ваше сравнение r >= 0 должно быть верным< /a> для отрицательного или положительного 0.

person Joseph Mansfield    schedule 07.12.2012
comment
Я предполагал то же самое, но при тестировании я столкнулся со случаями, когда r равно -0.0, а оператор return не действует так же, как для 0.0. Странно, я знаю. - person Justin; 07.12.2012
comment
@Justin У вас есть пример этого? Звучит странно. - person Joseph Mansfield; 07.12.2012
comment
Но что заставляет вас думать, что r — это отрицательный нуль, а не просто какое-то другое отрицательное число, близкое к нулю? Если это те выходные данные отладки, попробуйте использовать %g вместо %f... - person aschepler; 07.12.2012
comment
интересно. %g действительно дает разные результаты. r=-2.78269e-07 вместо -0.000000. - person Justin; 07.12.2012
comment
@Justin Тогда это просто очень маленькое отрицательное число. :) - person Joseph Mansfield; 07.12.2012

Рассмотреть возможность:

#include <stdio.h>
#include <iostream>
using namespace std;

int main()
{
    double const x = -2.78269e-07;

    printf( "printf: x=%f\n", x );
    cout << "cout: x=" << x << endl;
}

С результатом (с использованием Visual C++ 11.0):

[D:\dev\test]
> cl foo.cpp
foo.cpp

[D:\dev\test]
> foo
printf: x=-0.000000
cout: x=-2.78269e-007

[D:\dev\test]
> _

Это кажется ужасно похожим на загадочный результат в вопросе.

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

Итак, я считаю, что не показанный код вычисления произвел значение 2.78269e-007.


Итак, в заключение, видимо, нестандартным было только описанное поведение, про отрицательный ноль по сравнению с неравным нулю. На самом деле отрицательного нуля, по-видимому, нет, а есть только очень маленькое отрицательное значение. Что при заданном формате вывода представляется как все нулевые цифры со знаком минус впереди.

person Cheers and hth. - Alf    schedule 07.12.2012

ОП упоминает std::signbit в своем вопросе, но он должен быть в ответе, который нужно быстро найти, так что вот он:

Это работает с С++ 11, чтобы различать -0.0 и +0.0:

#include <cmath>
std::signbit(x) // true iif sign bit of x is set i.e. x is negative

std::signbit на cppreference

person Gabriel Devillers    schedule 17.08.2020

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

std::cout << std::setprecision(7) << (abs(value) < 0.0000005f ? 0 : value);

Обратите внимание, как я добавил 7 разрядов к плавающей точности, как я указал с помощью std::setprecision().

Это зависит от того, насколько точно вы хотите напечатать свои поплавки/двойники.

person Beyondo    schedule 19.07.2018