Могут ли произойти плохие вещи при делении 1/очень маленького поплавка?

Если я хочу проверить, что положительное число с плавающей запятой A меньше обратного квадрата другого положительного числа с плавающей запятой B (в C99), может ли что-то пойти не так, если B очень маленькое?

Я мог бы представить, как это проверить

if(A<1/(B*B))

Но если B достаточно мало, может ли это привести к бесконечности? Если бы это произошло, код по-прежнему работал бы правильно во всех ситуациях?

В том же духе я мог бы сделать

if(1/A>B*B)

... что может быть немного лучше, потому что B * B может быть равно нулю, если B мало (это правда?)

Наконец, решение, которое я не могу себе представить неправильным,

if(sqrt(1/A)>B)

что, я думаю, никогда не приведет к делению на ноль, но все же может быть проблематичным, если A близко к нулю.

Итак, в основном, мои вопросы:

  • Может ли 1/X быть бесконечностью, если X больше нуля (но мало)?
  • Может ли X*X быть равным нулю, если X больше нуля?
  • Будут ли сравнения с бесконечностью работать так, как я ожидаю?

РЕДАКТИРОВАТЬ: для тех из вас, кому интересно, я закончил тем, что сделал

if(B*A*B<1) 

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


person Jeremy Salwen    schedule 02.06.2010    source источник
comment
Как мало вы говорите? У вас могут быть, так сказать, некоторые проблемы с 0.0000000000000000000000003, но трудно ответить на вопрос, не зная, какой точности будут ваши входные числа с плавающей запятой.   -  person Corey    schedule 02.06.2010
comment
Насколько я знаю, вы можете получить арифметическое переполнение с маленькими делителями, но не деление на ноль.   -  person Dave Markle    schedule 02.06.2010
comment
Как насчет if ( A*B*B < 1 )?   -  person Nikolai Fetissov    schedule 02.06.2010
comment
Ввод B поступает от пользовательского графического ползунка или текстового ввода, поэтому я должен быть в состоянии изящно обрабатывать любой размер, даже если какой-нибудь озорной человек введет 0,000000000000000000000000003. Ввод A происходит из вычисления вычитания с плавающей запятой чисел больше десяти или около того, поэтому я также ожидаю, что он, возможно, будет очень маленьким, но смехотворно маленьким, потому что он должен был потерять точность.   -  person Jeremy Salwen    schedule 02.06.2010
comment
Николай, я думаю, у вас есть решение. Это никогда не должно приводить к переполнению или делению на ноль любого рода, и это именно тот ответ, который я ищу. Вы должны были опубликовать это как ответ!   -  person Jeremy Salwen    schedule 02.06.2010
comment
Просто чтобы было ясно, 0.0000000000000000000000003 даже близко не находится на приблизительном уровне, где это могло бы стать проблемой (по крайней мере, для двойной точности). На самом деле, вы даже не можете ввести число, которое вызывает проблему, в поле комментария stackoverflow (если вы не разрешаете экспоненциальное обозначение).   -  person Stephen Canon    schedule 02.06.2010


Ответы (4)


Если вы хотите обрабатывать весь диапазон возможных значений A и B, вам нужно быть немного осторожным, но на самом деле это не так уж сложно.

Предложение использовать a*b*b < 1. является хорошим; если b настолько мал, что a*b*b уменьшается до нуля, то a обязательно меньше, чем 1./(b*b). И наоборот, если b настолько велико, что a*b*b достигает бесконечности, то условие (правильно) не будет выполнено. (Potatoswatter правильно указывает в комментарии к другому сообщению, что это не работает должным образом, если вы пишете это b*b*a, потому что b*b может переполниться до бесконечности, даже если условие должно быть истинным. , если a оказывается денормальным.Однако в C умножение ассоциируется слева направо, так что это не проблема, если вы пишете его a*b*b и ваша платформа придерживается разумной числовой модели.)

Поскольку вы априори знаете, что a и b являются положительными числами, a*b*b не может сгенерировать NaN, поэтому вам не нужно беспокоиться об этом условии. Переполнение и недополнение — единственные возможные нарушения, и мы их уже учитывали. Если вам нужно поддерживать случай, когда a или b могут быть нулем или бесконечностью, вам нужно быть несколько более осторожным.

Чтобы ответить на ваши прямые вопросы: (ответы предполагают арифметику IEEE-754)

Может ли 1/X быть бесконечностью, если X больше нуля (но мало)?

Да! Если x — небольшое положительное денормальное значение, то 1/x может переполниться и дать бесконечность. Например, при двойной точности в режиме округления по умолчанию 1 / 0x1.0p-1024 будет переполняться.

Может ли X*X быть равным нулю, если X больше нуля?

Да! В режиме двойной точности в режиме округления по умолчанию все значения x меньше 0x1.0p-538 (это 2**-578 в шестнадцатеричном формате C99) или около того имеют это свойство.

Будут ли сравнения с бесконечностью работать так, как я ожидаю?

Да! Это одна из лучших особенностей IEEE-754.

person Stephen Canon    schedule 02.06.2010
comment
Спасибо! Я буду больше изучать денормальные значения (не потому, что я беспокоюсь, просто интересно) - person Jeremy Salwen; 02.06.2010

ОК, репост в качестве ответа.

Попробуйте использовать арифметически эквивалентное сравнение, например if ( A*B*B < 1. ). Однако у вас могут возникнуть проблемы с действительно большими числами.

Внимательно изучите IEEE 754, чтобы найти нужные варианты.

person Nikolai Fetissov    schedule 02.06.2010
comment
На самом деле, до тех пор, пока ему не нужно поддерживать ноль или бесконечность в качестве значений для a или b (похоже, что он этого не делает), тогда проблем с крайним случаем нет; все правильно выпадают. - person Stephen Canon; 02.06.2010

Вы хотите избежать делений, поэтому хитрость заключается в том, чтобы изменить уравнение. Вы можете умножить обе части вашего первого уравнения на (b * b), чтобы получить:

b*b*a < 1.0

Здесь не будет никаких делений, так что все должно быть в порядке.

person Ben Robbins    schedule 02.06.2010
comment
Ницца! Дважды отмерь, один раз отрежь. Чтобы спросить: если этот код не говорит сам за себя, добавьте необходимые комментарии, чтобы сделать это понятным. - person Hamish Grubijan; 02.06.2010
comment
Деление несколько медленное, но не токсичное. Проблема здесь в том, что bb может легко переполниться. ab, где a›1 и b‹1 — гораздо лучшее место для начала. - person Potatoswatter; 02.06.2010
comment
@Potatoswatter: если предположить, что IEEE-754 с плавающей запятой, переполнение безвредно, и это все равно даст правильный ответ. То же самое для нижнего потока. - person Stephen Canon; 02.06.2010
comment
@stephen: Не совсем… если a денормально, то b*b может переполниться до бесконечности и сбросить результат. - person Potatoswatter; 02.06.2010
comment
@Potatoswatter: ах, не понял, что это было b*b*a вместо правильного a*b*b (что действительно работает правильно, потому что умножение ассоциируется слева направо в C). - person Stephen Canon; 02.06.2010

Дивизия сама по себе не так уж и плоха. Однако стандартные типы IEEE 754 FP допускают больший отрицательный диапазон показателей степени, чем положительный, из-за денормализованных чисел. Например, float находится в диапазоне от 1,410-45 до 3,410-38, поэтому нельзя взять обратное значение 210-44.

Поэтому, как предлагает Джереми, начните с умножения A на B, где у одного показатель степени положительный, а у другого — отрицательный, чтобы избежать переполнения.

Вот почему A*B*B<1 является правильным ответом.

person Potatoswatter    schedule 02.06.2010