Можно ли в следующем примере получить деление на 0 (или бесконечность)?
public double calculation(double a, double b)
{
if (a == b)
{
return 0;
}
else
{
return 2 / (a - b);
}
}
В обычных случаях, конечно, не будет. Но что, если a
и b
очень близки, может(a-b)
быть 0
связано с точностью расчета?
Обратите внимание, что этот вопрос касается Java, но я думаю, что он будет применяться к большинству языков программирования.
Ответы:
В Java
a - b
никогда не бывает равным0
ifa != b
. Это связано с тем, что Java требует операций с плавающей запятой IEEE 754, которые поддерживают денормализованные числа. Из спецификации :Если FPU работает с денормализованными числами , вычитание неравных чисел никогда не может привести к нулю (в отличие от умножения), также см. Этот вопрос .
Для других языков это зависит. Например, в C или C ++ поддержка IEEE 754 не является обязательной.
Тем не менее, это возможно для выражения
2 / (a - b)
к переполнению, например , сa = 5e-308
иb = 4e-308
.источник
(a,b) = (3,1)
=>2/(a-b) = 2/(3-1) = 2/2 = 1
Верно ли это с плавающей запятой IEEE, я не знаюКак насчет следующего?
Таким образом, вы не зависите от поддержки IEEE на любом языке.
источник
a=b
, не надо возвращаться0
. Деление на0
в IEEE 754 дает вам бесконечность, а не исключение. Вы избегаете проблемы, поэтому возвращение0
- это ожидаемая ошибка. Посмотрим1/x + 1
. Еслиx=0
это приведет1
к неправильному значению: бесконечность.0
- не проблема. Это то, что делает OP в вопросе. Вы можете поместить исключение или что-то другое, подходящее для ситуации в этой части блока. Если вам не нравится возвращаться0
, это должна быть критика вопроса. Конечно, выполнение того, что сделал OP, не гарантирует отрицательного ответа. Этот вопрос не имеет ничего общего с дальнейшими вычислениями после завершения данной функции. Насколько вам известно, требования программы требуют возврата0
.Вы не получите деления на ноль независимо от значения
a - b
, поскольку деление с плавающей запятой на 0 не вызывает исключения. Возвращает бесконечность.Теперь единственный способ
a == b
вернуть истину - это еслиa
иb
содержать точно такие же биты. Если они отличаются только младшим битом, разница между ними не будет равна 0.РЕДАКТИРОВАТЬ :
Как правильно прокомментировала Вирсавия, есть некоторые исключения:
«Не число сравнивает» ложь с собой, но будет иметь идентичные битовые комбинации.
-0.0 определен для сравнения истины с +0.0, и их битовые шаблоны отличаются.
Таким образом, если оба
a
иb
естьDouble.NaN
, вы достигнете предложения else, но, посколькуNaN - NaN
также возвращаетNaN
, вы не будете делить на ноль.источник
Здесь не может быть деления на ноль.
SMT Solver Z3 поддерживает точную IEEE арифметику с плавающей точкой. Давайте попросим Z3 найти числа
a
иb
такие, чтоa != b && (a - b) == 0
:Результат
UNSAT
. Таких номеров нет.Вышеупомянутая строка SMTLIB также позволяет Z3 выбрать произвольный режим округления (
rm
). Это означает, что результат верен для всех возможных режимов округления (их пять). Результат также включает возможность того, что любая из переменных в игре может иметь значениеNaN
или бесконечность.a == b
реализовано какfp.eq
качество, так что+0f
и-0f
сравнивать равно. Сравнение с нулем осуществляется с помощьюfp.eq
. Поскольку вопрос направлен на то, чтобы избежать деления на ноль, это подходящее сравнение.Если бы проверка на равенство была реализована с использованием побитового равенства,
+0f
и-0f
это был бы способ сделатьa - b
ноль. Некорректная предыдущая версия этого ответа содержит подробные сведения о режиме для любопытных.Z3 Online пока не поддерживает теорию FPA. Этот результат был получен с использованием последней нестабильной ветки. Его можно воспроизвести с помощью привязок .NET следующим образом:
Использование Z3 , чтобы ответить на вопросы IEEE с плавающей точкой приятно , потому что это трудно игнорировать случаи (например
NaN
,-0f
,+-inf
) , и вы можете задать произвольные вопросы. Нет необходимости интерпретировать и цитировать спецификации. Вы даже можете задавать смешанные вопросы с числами с плавающей запятой и целыми числами, такие как «int log2(float)
правильный ли этот алгоритм?».источник
Предоставленная функция действительно может возвращать бесконечность:
Выход есть
Result: -Infinity
.Когда результат деления слишком велик для сохранения в двойном формате, возвращается бесконечность, даже если знаменатель не равен нулю.
источник
В реализации с плавающей запятой, соответствующей IEEE-754, каждый тип с плавающей запятой может содержать числа в двух форматах. Один («нормализованный») используется для большинства значений с плавающей запятой, но второе наименьшее число, которое оно может представлять, лишь немного больше наименьшего, поэтому разница между ними не представляется в том же формате. Другой («денормализованный») формат используется только для очень маленьких чисел, которые не могут быть представлены в первом формате.
Схема для эффективной обработки денормализованного формата с плавающей запятой стоит дорого, и не все процессоры включают ее. Некоторые процессоры предлагают выбор между операциями с действительно небольшими числами или большим количеством операций. медленнее, чем операции с другими значениями, или когда процессор просто рассматривал числа, которые слишком малы для нормализованного формата, как ноль.
Спецификации Java подразумевают, что реализации должны поддерживать денормализованный формат даже на машинах, где это приведет к замедлению выполнения кода. С другой стороны, возможно, что некоторые реализации могут предлагать варианты, позволяющие коду работать быстрее в обмен на немного небрежную обработку значений, которые для большинства целей были бы слишком малы, чтобы иметь значение (в случаях, когда значения слишком малы, чтобы иметь значение, это может раздражать, что вычисления с ними занимают в десять раз больше времени, чем вычисления, которые действительно имеют значение, поэтому во многих практических ситуациях сброс до нуля более полезен, чем медленная, но точная арифметика).
источник
Раньше, до IEEE 754, было вполне возможно, что a! = B не означало ab! = 0 и наоборот. Это было одной из причин создания IEEE 754.
С IEEE 754 это почти гарантировано. Компиляторам C или C ++ разрешено выполнять операции с более высокой точностью, чем необходимо. Итак, если a и b не переменные, а выражения, тогда (a + b)! = C не подразумевает (a + b) - c! = 0, потому что a + b может быть вычислено один раз с более высокой точностью, а один раз без более высокая точность.
Многие FPU можно переключить в режим, в котором они не возвращают денормализованные числа, а заменяют их на 0. В этом режиме, если a и b являются крошечными нормализованными числами, где разница меньше наименьшего нормализованного числа, но больше 0, a ! = b также не гарантирует a == b.
«Никогда не сравнивайте числа с плавающей запятой» - это культовое программирование. Среди людей, у которых есть мантра «вам нужен эпсилон», большинство не знают, как правильно выбрать этот эпсилон.
источник
Я могу вспомнить случай, когда вы могли бы это сделать. Вот аналогичный образец в базе 10 - действительно, это, конечно, произошло бы в базе 2.
Числа с плавающей запятой хранятся более или менее в экспоненциальной нотации - то есть вместо 35,2 сохраняемое число будет больше похоже на 3,52e2.
Для удобства представьте, что у нас есть блок с плавающей запятой, который работает по основанию 10 и имеет 3-значную точность. Что произойдет, если вычесть 9,99 из 10,0?
1.00e2-9.99e1
Shift, чтобы присвоить каждому значению одинаковую экспоненту
1.00e2-0.999e2
Округлить до трех цифр
1.00e2-1.00e2
Ой!
Может ли это случиться в конечном итоге, зависит от конструкции FPU. Поскольку диапазон показателей для двойного числа очень велик, аппаратное обеспечение должно в какой-то момент выполнить внутреннее округление, но в приведенном выше случае всего 1 дополнительная внутренняя цифра предотвратит любую проблему.
источник
strictfp
он не включен, вычисления могут давать значения, которые слишком малы для,double
но будут соответствовать значению с плавающей запятой расширенной точности.strictfp
влияет только на значения «промежуточных результатов», и я цитирую docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.4 .a
иb
являютсяdouble
переменными, а не промежуточными результатами, поэтому их значения являются значениями с двойной точностью, поэтому кратны 2 ^ -1074. Следовательно, вычитание этих двух значений двойной точности кратно 2 ^ -1074, поэтому более широкий диапазон экспоненты изменяет свойство, заключающееся в том, что разница равна 0, если a == b.Вы никогда не должны сравнивать числа с плавающей запятой или удвоения на равенство; потому что вы не можете гарантировать, что число, которое вы присваиваете float или double, является точным.
Чтобы разумно сравнить числа с плавающей запятой на равенство, вам нужно проверить, является ли значение "достаточно близким" к тому же значению:
источник
abs(first - second) < error
(или<= error
) проще и лаконичнее.Деление на ноль не определено, так как предел от положительных чисел стремится к бесконечности, ограниченный от отрицательных чисел стремится к отрицательной бесконечности.
Не уверен, C ++ это или Java, поскольку языкового тега нет.
источник
Основная проблема заключается в том, что компьютерное представление числа типа double (также известного как число с плавающей запятой или действительное число на математическом языке) неверно, когда у вас "слишком много" десятичного числа, например, когда вы имеете дело с double, которое нельзя записать как числовое значение ( пи или результат 1/3).
Итак, a == b не может быть выполнено с любым двойным значением a и b, как вы поступаете с a == b, когда a = 0,333 и b = 1/3? В зависимости от вашей ОС, FPU, числа, языка, количества 3 после 0, у вас будет true или false.
В любом случае, если вы выполняете «вычисление двойного значения» на компьютере, вам нужно иметь дело с точностью, поэтому вместо того, чтобы делать
a==b
, вы должны делатьabsolute_value(a-b)<epsilon
, и эпсилон относится к тому, что вы моделируете в это время в своем алгоритме. У вас не может быть эпсилон-значения для всего вашего двойного сравнения.Короче говоря, когда вы вводите a == b, вы получаете математическое выражение, которое нельзя перевести на компьютере (для любого числа с плавающей запятой).
PS: гул, все, что я здесь отвечаю, более или менее в других ответах и комментариях.
источник
Основываясь на ответе @malarres и комментарии @Taemyr, вот мой небольшой вклад:
Я хочу сказать: самый простой способ узнать, является ли результат деления nan или inf, действительно для выполнения деления.
источник