[McSeem2] Про сравнение double
31.08.2007
|
McSeem2 |
Здравствуйте, gandjustas, Вы писали:
G>Нет. Вообще прямое сравнение чисел с плавающией запятой, вида (a==b) может дать неправильный результат
UN>Не 0.0000001, а EPS — это значение определено в CRT =)
. . .ну и т.д.
Так. Тут начался беспредел с ужасами про сравнение плавающей точки на равенство. Требуется ликбез. Сравнивать плавающие числа (для педантов — значения переменных типа float или double) вполне можно и даже нужно. Но надо понимать сущность этой плавающей точки. А сущность заключается в том, что числа с фиксированной точкой (целые — это частный случай чисел с фиксированной точкой) имеют абсолютное значение погрешности, в отличие от чисел с плавающей точкой, где значение погрешности находится в прямой пропорциональности от модуля числа. Во всяком случае:
Вот если этот if не сработает, то это означает, что компьютер сломался, а процессор издох.
Другое дело, когда числа вычислены разными способами — одно через синус, а другое — через экспоненту. Здесь действительно проверка на равенство скорее всего не сработает. Так же, как и не сработает сравнение с константой. Но это же относится и к целым числам, если скажем, мы нормализуем значения от 0...1 к 0...1000000000. То есть, не имеет значения, плавающие это числа или целые. В определенных ситуациях сравнивать их на строгое равенство нельзя. В этих ситуациях надо использовать некую Epsilon. И вот здесь-то и вылезает наружу вся безграмотность. Что такое DBL_EPSILON? — а это вот что. Это минимальное значение, которое при прибавлении его к единице, меняет значение этой единицы. Понимаете? — к единице! Строгой единице, числу 1.0 и ни к какому другому. Поэтому сравнивать числа с плавающей точкой на +/- DBL_EPSILON совершенно бессмысленно. Это сравнение выдает всю глубину невежества и неспособности думать мозгом. Факты таковы — плавающие числа больше 2.0 эту DBL_EPSILON просто не ощущают. Им что прибавляй ее, что что нет — ничего не меняет. Для этих чисел DBL_EPSILON является строгим нулем и просто не существует. В то же время, DBL_EPSILON имеет значение порядка 1e-16. Что это значит? А это значит, что числа в диапазоне Планковских масштабов (типа 1e-34) с точки зрения этой DBL_EPSILON будут все равны. То есть, эта 1e-16 становится слоном в посудной лавке. А ведь постоянная Планка ничуть не хуже скорости света — для этого собственно и были придуманы числа с плавающей точкой, чтобы отображать большие диапазоны значений с неким фиксированным количеством знаков.
Так для чего же все-таки нужна эта самая DBL_EPSILON (ну или FLT_EPSILON)? Нужна-ли? — нужна! Есть ситуации, когда действительно надо сравнивать числа в неком допустимом интервале. В каком? — А вот это как раз и зависит от абсолютного значения чисел и сущности вычислений. Короче говоря, надо эту Epsilon умножить на значение числа. А поскольку у нас два числа, то все усложняется — какое из них брать. То есть, корректное сравнение выглядит так:
Дорого? Да, дорого, а все остальное неправильно, такие дела. Но и это тоже неправильно! Дело в том, что этот DBL_EPSILON определяет разницу в 1 (один!) значащий бит экспоненты в приложении к числу 1.0. На практике такой разницы не встречается — числа либо строго равны, либо могут различаться больше чем на один значащий бит. Поэтому надо брать что-то типа 16*DBL_EPSILON, чтобы игнрорировать разницу в 4 младших бита (или примерно полторы последние значащие десятичные цифры из примерно 16 имеющихся).
Конечно же, есть случаи, когда диапазон чисел более-менее известен и предсказуем. Скажем, 0...1000. В этом случае, для сравнения на приблизительное равенство можно взять константу, типа 1000*16*DBL_EPSILON. Но надо иметь в виду, что такое сравнение фактически превращает всю идею плавающей точки в фиксированную точку (догадайтесь, почему).
Я вообще поражен уровню невежества — даже в весьма грамотной библиотеке GPC by Alan Murta используется тупое сравнение с константной Epsilon. На диапазонах экранных координат это все равно, что сравнение на строгое равенство, а на 1e-20 алгоритм вообще перестает работать.
И пожалуйста, имейте смелость высмеять от меня в лицо того университетского преподавателя, который скажет вам, что сравнивать плавающие числа на равенство нельзя. Такое высказывание можно простить в школе, на первом уроке информатики, но никак не в универе.
Вроде бы все сказал. Дополняйте.
G>Нет. Вообще прямое сравнение чисел с плавающией запятой, вида (a==b) может дать неправильный результат
UN>Не 0.0000001, а EPS — это значение определено в CRT =)
. . .ну и т.д.
Так. Тут начался беспредел с ужасами про сравнение плавающей точки на равенство. Требуется ликбез. Сравнивать плавающие числа (для педантов — значения переменных типа float или double) вполне можно и даже нужно. Но надо понимать сущность этой плавающей точки. А сущность заключается в том, что числа с фиксированной точкой (целые — это частный случай чисел с фиксированной точкой) имеют абсолютное значение погрешности, в отличие от чисел с плавающей точкой, где значение погрешности находится в прямой пропорциональности от модуля числа. Во всяком случае:
double a=3.1415;
double b=a;
if(a == b)
{
. . .
}
Вот если этот if не сработает, то это означает, что компьютер сломался, а процессор издох.
Другое дело, когда числа вычислены разными способами — одно через синус, а другое — через экспоненту. Здесь действительно проверка на равенство скорее всего не сработает. Так же, как и не сработает сравнение с константой. Но это же относится и к целым числам, если скажем, мы нормализуем значения от 0...1 к 0...1000000000. То есть, не имеет значения, плавающие это числа или целые. В определенных ситуациях сравнивать их на строгое равенство нельзя. В этих ситуациях надо использовать некую Epsilon. И вот здесь-то и вылезает наружу вся безграмотность. Что такое DBL_EPSILON? — а это вот что. Это минимальное значение, которое при прибавлении его к единице, меняет значение этой единицы. Понимаете? — к единице! Строгой единице, числу 1.0 и ни к какому другому. Поэтому сравнивать числа с плавающей точкой на +/- DBL_EPSILON совершенно бессмысленно. Это сравнение выдает всю глубину невежества и неспособности думать мозгом. Факты таковы — плавающие числа больше 2.0 эту DBL_EPSILON просто не ощущают. Им что прибавляй ее, что что нет — ничего не меняет. Для этих чисел DBL_EPSILON является строгим нулем и просто не существует. В то же время, DBL_EPSILON имеет значение порядка 1e-16. Что это значит? А это значит, что числа в диапазоне Планковских масштабов (типа 1e-34) с точки зрения этой DBL_EPSILON будут все равны. То есть, эта 1e-16 становится слоном в посудной лавке. А ведь постоянная Планка ничуть не хуже скорости света — для этого собственно и были придуманы числа с плавающей точкой, чтобы отображать большие диапазоны значений с неким фиксированным количеством знаков.
Так для чего же все-таки нужна эта самая DBL_EPSILON (ну или FLT_EPSILON)? Нужна-ли? — нужна! Есть ситуации, когда действительно надо сравнивать числа в неком допустимом интервале. В каком? — А вот это как раз и зависит от абсолютного значения чисел и сущности вычислений. Короче говоря, надо эту Epsilon умножить на значение числа. А поскольку у нас два числа, то все усложняется — какое из них брать. То есть, корректное сравнение выглядит так:
if (fabs(a-b) <= DBL_EPSILON * fmax(fabs(a),fabs(b)))
{
. . .Числа равны с относительной точностью DBL_EPSILON
}
Дорого? Да, дорого, а все остальное неправильно, такие дела. Но и это тоже неправильно! Дело в том, что этот DBL_EPSILON определяет разницу в 1 (один!) значащий бит экспоненты в приложении к числу 1.0. На практике такой разницы не встречается — числа либо строго равны, либо могут различаться больше чем на один значащий бит. Поэтому надо брать что-то типа 16*DBL_EPSILON, чтобы игнрорировать разницу в 4 младших бита (или примерно полторы последние значащие десятичные цифры из примерно 16 имеющихся).
Конечно же, есть случаи, когда диапазон чисел более-менее известен и предсказуем. Скажем, 0...1000. В этом случае, для сравнения на приблизительное равенство можно взять константу, типа 1000*16*DBL_EPSILON. Но надо иметь в виду, что такое сравнение фактически превращает всю идею плавающей точки в фиксированную точку (догадайтесь, почему).
Я вообще поражен уровню невежества — даже в весьма грамотной библиотеке GPC by Alan Murta используется тупое сравнение с константной Epsilon. На диапазонах экранных координат это все равно, что сравнение на строгое равенство, а на 1e-20 алгоритм вообще перестает работать.
И пожалуйста, имейте смелость высмеять от меня в лицо того университетского преподавателя, который скажет вам, что сравнивать плавающие числа на равенство нельзя. Такое высказывание можно простить в школе, на первом уроке информатики, но никак не в универе.
Вроде бы все сказал. Дополняйте.
31.08.2007 7 комментариев |
MS>И пожалуйста, имейте смелость высмеять от меня в лицо того университетского преподавателя, который скажет вам, что сравнивать плавающие числа на равенство нельзя. Такое высказывание можно простить в школе, на первом уроке информатики, но никак не в универе.
Можно сказать проще, что все стандартные эпсилоны относятся только к мантиссе числа. В большинстве задач надо вводить погрешность из проблемной области (например, 0.001 см для разметки текста на странице или 1.0E+30 г для массы звезд).
AM>Можно сказать проще, что все стандартные эпсилоны относятся только к мантиссе числа. В большинстве задач надо вводить погрешность из проблемной области (например, 0.001 см для разметки текста на странице или 1.0E+30 г для массы звезд).
Я специально написал такой развернутый ответ, за еще и "с эмоциями". Сказать проще конечно можно, но это увы не наглядно и часто не доходит.
Сравнивать плавающие числа или не сравнивать и как именно -- зависит исключительно о решаемой задачи. Вне конкретного контекста обсуждать можно лишь свойства примитивных опрераций -- в данном случае операции сравнения.
Ш>Сравнивать плавающие числа или не сравнивать и как именно -- зависит исключительно о решаемой задачи. Вне конкретного контекста обсуждать можно лишь свойства примитивных опрераций -- в данном случае операции сравнения.
Безусловно. Но надо иметь хотя бы начальную печку, от которой танцевать. А то так и будут писать ахинею типа if(fabs(a-b) <= DBL_EPSILON)...
MS>Здравствуйте, Шахтер, Вы писали:
Ш>>Сравнивать плавающие числа или не сравнивать и как именно -- зависит исключительно о решаемой задачи. Вне конкретного контекста обсуждать можно лишь свойства примитивных опрераций -- в данном случае операции сравнения.
MS>Безусловно. Но надо иметь хотя бы начальную печку, от которой танцевать. А то так и будут писать ахинею типа if(fabs(a-b) <= DBL_EPSILON)...
Вот с этим полностью согласен. (Пишу это явно на всякий случай)
MS>И пожалуйста, имейте смелость высмеять от меня в лицо того университетского преподавателя, который скажет вам, что сравнивать плавающие числа на равенство нельзя. Такое высказывание можно простить в школе, на первом уроке информатики, но никак не в универе.
Я всё-таки на это возражу Дело в том, что в вычислительном алгоритме ситуаций, которые достаточно просто сводятся к
крайне мало — я бы сказал, что их вообще не бывает.
А вот ситуаций, когда пишут код вида
много — фактически, каждому хотя бы раз приходилось писать такое (и потом удивляться результату — а куда делась последняя итерация?)
Именно поэтому устанавливать принцип "не сравнивайте на точное равенство! всегда сравнивайте величину разницы" вместе с некоторыми простыми советами типа "замените x<=20.0 на x<=20.5" имеет право на жизнь и тем более в университете. А ещё есть конверсии, когда double где-то был перенесен во float, а потом обратно... Найти случаи, когда надо сравнивать более просто, грамотный программист сможет — а если не сможет, ему вообще в вычислительной математике делать нечего. А вот сделать, чтобы правильное сравнение вошло "в плоть и кровь" — преподаватель таки должен.
По поводу вот этого:
MS> Конечно же, есть случаи, когда диапазон чисел более-менее известен и предсказуем. Скажем, 0...1000. В этом случае, для сравнения на приблизительное равенство можно взять константу, типа 1000*16*DBL_EPSILON. Но надо иметь в виду, что такое сравнение фактически превращает всю идею плавающей точки в фиксированную точку (догадайтесь, почему).
До определённой степени та же "фиксированная точка" тут совсем неплоха. По крайней мере при сравнении. Только, наверно, не при 0...1000, а хотя бы при 10...1000. Общих рецептов всё равно не будет, в вычислительной математике огромное количество тонкостей, зависящих и от специфики исходной задачи... я когда-то наигрался с этим.
N>Я всё-таки на это возражу Дело в том, что в вычислительном алгоритме ситуаций, которые достаточно просто сводятся к
N>
N>крайне мало — я бы сказал, что их вообще не бывает.
Бывает и довольно много, по крайней мере в моей практике. Например, восстановить связность графа по набору ребер и координатам их концов. Вполне практическая задача, необходимая для растеризации данных из Flash. Вообще, в вычислительной геометрии встречается сплошь и рядом.
N>А вот ситуаций, когда пишут код вида
N>
N>много — фактически, каждому хотя бы раз приходилось писать такое (и потом удивляться результату — а куда делась последняя итерация?)
Это несколько другой эффект, связанный с невозможностью точного представления десятичных дробей в двоичном виде. Числа 0.1 в двоичном виде не существует. Вышеуказанный цикл может сработать неправильно даже с фиксированной точкой. Здесь главным условием является наличие десятичной дроби (причем, не всякой), представленной в двоичном виде. Например, при обработке в BCD все сработает нормально. В случае с таким циклом надо именно что разъяснить студентам этот побочный эффект на как можно более ранних стадиях. Например:
Другими словами, требуется обеспечить понимание, что некоторые десятичные дроби не имеют точного представления в двоичном виде.
Так называемые практические советы, типа "x <= 20.05" опасны, поскольку могут только запутать и сбить с толку. И главное — они лечат симптом, а не болезнь, еще больше отдаляя понимание сущности. Вот после того, как наступило понимание, можно дать подобный совет, но не раньше. Но при этом и надобность в подобном совете отпадает — человек уже понимает сущность и сам сообразит что к чему.