Безопасно ли е да проверявате стойностите с плаваща запетая за равенство на 0?

Знам, че обикновено не можете да разчитате на равенство между двойни или десетични стойности, но се чудя дали 0 е специален случай.

Въпреки че мога да разбера неточностите между 0,00000000000001 и 0,00000000000002, самата 0 изглежда доста трудна за объркване, тъй като е просто нищо. Ако не сте неточен за нищо, това вече не е нищо.

Но не знам много по тази тема, така че не е моя работа да казвам.

double x = 0.0;
return (x == 0.0) ? true : false;

Това винаги ли ще се връща вярно?


person Gene Roberts    schedule 27.01.2009    source източник
comment
Троичният оператор е излишен в този код :)   -  person Joel Coehoorn    schedule 28.01.2009
comment
LOL прав си. Върви при мен   -  person Gene Roberts    schedule 28.01.2009
comment
Не бих го направил, защото не знаеш как x е зададен на нула. Ако все пак искате да го направите, вероятно искате да закръглите или да намалите x, за да се отървете от 1e-12 или такива, които може да са маркирани в края.   -  person Rex Logan    schedule 28.01.2009


Отговори (9)


Безопасно е да очаквате, че сравнението ще върне true ако и само ако двойната променлива има стойност точно 0.0 (което в оригиналния кодов фрагмент е, разбира се). Това е в съответствие със семантиката на оператора ==. a == b означава "a е равно на b".

Не е безопасно (тъй като е неправилно) да очаквате, че резултатът от някое изчисление ще бъде нула в двойна (или по-общо, с плаваща запетая) аритметика, когато резултатът на същото изчисление в чистата математика е нула. Това е така, защото когато изчисленията влязат в основата, се появява грешка при прецизност с плаваща запетая - концепция, която не съществува в аритметиката на реалните числа в математиката.

person Daniel Daranas    schedule 27.01.2009

Ако трябва да правите много сравнения на „равенство“, може да е добра идея да напишете малка помощна функция или метод за разширение в .NET 3.5 за сравняване:

public static bool AlmostEquals(this double double1, double double2, double precision)
{
    return (Math.Abs(double1 - double2) <= precision);
}

Това може да се използва по следния начин:

double d1 = 10.0 * .1;
bool equals = d1.AlmostEquals(0.0, 0.0000001);
person Dirk Vollmar    schedule 27.01.2009
comment
Възможно е да имате грешка при анулиране при изваждане, като сравнявате double1 и double2, в случай че тези числа имат стойности, много близки една до друга. Бих премахнал Math.Abs ​​и бих проверил всеки клон поотделно d1 ›= d2 - e и d1 ‹= d2 + e - person Theodore Zographos; 15.06.2012
comment
Тъй като Epsilon определя минималния израз на положителна стойност, чийто обхват е близо до нула, маржът на разликата между две подобни стойности трябва да бъде по-голям от Epsilon. Обикновено той е многократно по-голям от Epsilon. Поради това ви препоръчваме да не използвате Epsilon, когато сравнявате Double стойности за равенство. - msdn.microsoft.com/en-gb/ библиотека/ya2zha7s(v=vs.110).aspx - person Rafael Costa; 14.03.2018

За вашата проста проба този тест е ок. Но какво да кажем за това:

bool b = ( 10.0 * .1 - 1.0 == 0.0 );

Не забравяйте, че .1 е повтарящ се десетичен знак в двоична система и не може да бъде представен точно, същото като да се опитвате да напишете 1/3 като десетичен знак с основа 10. Сега сравнете това с този код:

double d1 = 10.0 * .1; // make sure the compiler hasn't optimized the .1 issue away
bool b = ( d1 - 1.0 == 0.0 );

Ще ви оставя да проведете тест, за да видите действителните резултати: по-вероятно е да го запомните по този начин.

person Joel Coehoorn    schedule 27.01.2009
comment
Всъщност това връща true по някаква причина (поне в LINQPad). - person Alexey Romanov; 25.06.2009
comment
Какъв е проблемът с .1, за който говорите? - person Teejay; 05.03.2020
comment
Не забравяйте, че .1 е повтарящ се десетичен знак в двоична система и не може да бъде представен точно. Благодаря, че ми напомни нещо, което не знаех като начало ???? - person ecv; 26.02.2021

От записа в MSDN за Double.Equals:

Прецизност в сравненията

Методът Equals трябва да се използва с повишено внимание, тъй като две привидно еквивалентни стойности може да не са равни поради различната точност на двете стойности. Следващият пример съобщава, че стойността Double .3333 и Double, върнати чрез разделяне на 1 на 3, са неравни.

...

Вместо да се сравнява за равенство, една препоръчителна техника включва определяне на приемлива разлика между две стойности (като 0,01% от една от стойностите). Ако абсолютната стойност на разликата между двете стойности е по-малка или равна на тази граница, разликата вероятно се дължи на разлики в прецизността и следователно стойностите вероятно са равни. Следващият пример използва тази техника за сравняване на .33333 и 1/3, двете стойности на Double, за които предишният пример с код установи, че са неравни.

Вижте също Double.Epsilon.

person Stu Mackellar    schedule 27.01.2009
comment
Също така е възможно не съвсем еквивалентните стойности да се сравняват като равни. Човек би очаквал, че ако x.Equals(y), тогава (1/x).Equals(1/y), но това не е така, ако x е 0 и y е 1/Double.NegativeInfinity. Тези стойности се декларират като равни, въпреки че техните реципрочни не са. - person supercat; 27.09.2012
comment
@supercat: Те са еквивалентни. И те нямат реципрочни. Бихте могли да стартирате теста си отново с x = 0 и y = 0 и пак ще намерите това 1/x != 1/y. - person Ben Voigt; 05.08.2016
comment
@BenVoigt: С x и y като тип double? Как сравнявате резултатите, за да ги направите неравни? Имайте предвид, че 1/0.0 не е NaN. - person supercat; 05.08.2016
comment
@supercat: Добре, това е едно от нещата, които IEEE-754 греши. (Първо, че 1.0/0.0 не успява да бъде NaN, както трябва да бъде, тъй като границата не е уникална. Второ, че безкрайностите се сравняват еднакви една с друга, без да се обръща внимание на степените на безкрайност) - person Ben Voigt; 05.08.2016
comment
@BenVoigt: Ако нулата е резултат от умножаване на две много малки числа, тогава разделянето на 1,0 на това трябва да даде стойност, която сравнява по-голям от който и да е брой малки числа с един и същ знак и по-малък от всяко число, ако едно от малките числата имаха противоположни знаци. IMHO, IEEE-754 би бил по-добър, ако имаше нула без знак, но положителни и отрицателни безкрайно малки. - person supercat; 05.08.2016
comment
@supercat: Да, това би бил един от начините да се коригира двусмислието около reciprocal-after-underflow. Все още не помага на проблема, че +Inf е решение на x*x == x, когато наистина не трябва да бъде (безкрайностите трябва да бъдат подредени по отношение на всички крайни числа и техния двойник с противоположен знак, но неподредени по отношение на друга безкрайност от същото знак). - person Ben Voigt; 05.08.2016
comment
@BenVoigt: Правилното използване на плаваща запетая изисква множество видове сравнения. За някои видове математически изчисления непоследователните подреждания могат да имат смисъл, но за операции за обработка на данни като сортиране (което може да е част от някои математически процеси като вземане на медиана) пълнотата и последователността са по-важни от математическата коректност. За повечето цели най-важният аспект на оператора == е, че той се държи като релация на еквивалентност; по този стандарт оператор ==, който прави нещо неравно на себе си, е повреден. - person supercat; 05.08.2016
comment
@supercat: Вече можете да направите isfinite(x)? x == y : fpclassify(x) == fpclassify(y) за връзка на еквивалентност (би било хубаво да имате стандартна функция за това, разбира се). Но като се има предвид, че x == x е невярно за NaN, изглежда ясно, че трябва да е невярно и за другите неограничени класове. - person Ben Voigt; 05.08.2016
comment
@BenVoigt: Авторите на IEEE-754 искаха да позволят сравнения, използвайки само вградените релационни оператори, така че да може да се използва дори при реализации без функции като isfinite(x), fpclassify(x) и т.н. Ако само една стойност сравнява неравномерно към себе си x==y || (x!=x && y!=y) ще бъде релация на еквивалентност. Ако нито +INF, нито -INF се сравнят със себе си, ще бъде необходимо да се добавят още термини към тази чудовищност. - person supercat; 05.08.2016

Проблемът идва, когато сравнявате различни видове имплементация на стойност с плаваща запетая, напр. сравняване на float с double. Но със същия тип не би трябвало да е проблем.

float f = 0.1F;
bool b1 = (f == 0.1); //returns false
bool b2 = (f == 0.1F); //returns true

Проблемът е, че програмистът понякога забравя, че неявното преобразуване на типа (double to float) се случва за сравнението и това води до грешка.

person Yogee    schedule 08.09.2011

Ако числото е директно присвоено на float или double, тогава е безопасно да се тества срещу нула или всяко цяло число, което може да бъде представено в 53 бита за double или 24 бита за float.

Или казано по друг начин, винаги можете да присвоите и цяло число на двойно и след това да сравните двойното обратно със същото цяло число и да сте сигурни, че ще бъде равно.

Можете също така да започнете, като зададете цяло число и простите сравнения да продължат да работят, като се придържате към добавяне, изваждане или умножение с цели числа (ако приемем, че резултатът е по-малък от 24 бита за число с плаваща единица и 53 бита за двойно). Така че можете да третирате float и double като цели числа при определени контролирани условия.

person Kevin Gale    schedule 27.01.2009
comment
Като цяло съм съгласен с вашето твърдение (и го гласувах в подкрепа), но вярвам, че наистина зависи дали се използва внедряването на IEEE 754 с плаваща запетая или не. И аз вярвам, че всеки съвременен компютър използва IEEE 754, поне за съхранение на плаващи числа (има странни правила за закръгляване, които се различават). - person Mark Lakata; 20.03.2017

Не, не е наред. Така наречените денормализирани стойности (субнормални), когато се сравняват, равни на 0,0, биха се сравнили като неверни (различни от нула), но когато се използват в уравнение, биха се нормализирали (станали 0,0). Следователно използването на това като механизъм за избягване на деление на нула не е безопасно. Вместо това добавете 1.0 и сравнете с 1.0. Това ще гарантира, че всички субнормални стойности се третират като нула.

person Community    schedule 10.04.2009
comment
Поднормалните са известни също като денормални - person Manuel; 30.09.2009
comment
Поднормалните стойности не стават равни на нула, когато се използват, въпреки че могат или не могат да дадат същия резултат в зависимост от точната операция. - person wnoise; 18.03.2011

Опитайте това и ще откриете, че == не е надеждно за double/float.
double d = 0.1 + 0.2; bool b = d == 0.3;

Ето отговор от Quora.

person rickyuu    schedule 19.06.2018

Всъщност мисля, че е по-добре да използвате следните кодове, за да сравните двойна стойност с 0,0:

double x = 0.0;
return (Math.Abs(x) < double.Epsilon) ? true : false;

Същото за float:

float x = 0.0f;
return (Math.Abs(x) < float.Epsilon) ? true : false;
person David.Chu.ca    schedule 15.12.2011
comment
Не. От документите от double.Epsilon: Ако създадете персонализиран алгоритъм, който определя дали две числа с плаваща запетая могат да се считат за равни, трябва да използвате стойност, която е по-голяма от константата Epsilon, за да установите приемливия абсолютен марж на разликата за двете стойности да се считат за равни. (Обикновено този марж на разлика е многократно по-голям от Epsilon.) - person Alastair Maw; 06.06.2012
comment
@AlastairMaw това се отнася за проверка на две двойки от произволен размер за равенство. За проверка на равенството на нула, double.Epsilon е добре. - person jwg; 03.01.2013
comment
Не, не е. Много е вероятно стойността, до която сте стигнали чрез някакво изчисление, да е много пъти по епсилон от нулата, но все пак трябва да се счита за нула. Вие не постигате магически цял куп допълнителна прецизност във вашия междинен резултат от някъде, само защото случайно е близо до нула. - person Alastair Maw; 03.01.2013
comment
Например: (1,0/5,0 + 1,0/5,0 - 1,0/10,0 - 1,0/10,0 - 1,0/10,0 - 1,0/10,0) ‹ двойно. Епсилон == невярно (и значително по отношение на величината: 2,78E-17 срещу 4,94E -324) - person Alastair Maw; 03.01.2013
comment
И така, каква е препоръчителната точност, ако double.Epsilon не е добре? Добре ли е 10 пъти по епсилон? 100 пъти? - person liang; 14.05.2013