Подтвердить что ты не робот

Есть ли предпочтительный способ упорядочения операндов с плавающей запятой?

Предположим, что у меня очень маленький float a (например, a=0.5), который входит в следующее выражение:

6000.f * a * a;

Оправдывает ли порядок операндов? Лучше ли писать

6000.f * (a*a);

Или даже

float result = a*a;
result *= 6000.f;

Я проверил классический Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой, но ничего не мог найти.

Существует ли оптимальный способ упорядочения операндов в операции с плавающей запятой?

4b9b3361

Ответ 1

Это действительно зависит от ценностей и ваших целей. Например, если a очень мало, a*a может быть нулевым, тогда как 6000.0*a*a (что означает (6000.0*a)*a) все равно может быть отличным от нуля. Для избежания переполнения и недоиспользования общим правилом является применение ассоциативного закона для первого выполнения умножений, когда журналы операндов имеют противоположный знак, что означает, что сначала квадрат является худшей стратегией. С другой стороны, по соображениям производительности квадрат сначала может быть очень хорошей стратегией, если вы можете повторно использовать значение квадрата. Вы можете столкнуться с еще одной проблемой, которая может иметь большее значение для правильности, чем проблемы с переполнением/недостаточным потоком, если ваши номера никогда не будут очень близки к нулю или бесконечности: некоторые умножения могут быть гарантированы точными ответами, а другие - округлением. В общем, вы получите самые точные результаты, минимизируя количество шагов округления, которые происходят.

Ответ 2

Не типично, нет.

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

Ответ 3

Оптимальный способ зависит от цели, действительно.

Прежде всего, умножение происходит быстрее, чем деление.

Итак, если вам нужно написать a = a / 2;, лучше написать a = a * 0.5f;. Ваш компилятор обычно достаточно умный, чтобы заменить деление на умножение на константы, если результаты одинаковы, но он не будет делать этого с переменными, конечно.

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

Некоторые другие операции могут быть более быстрыми, но менее точными. Возьмем пример.

float f = (a * 100000) / (b * 10);
float g = (a / b) * (100000 / 10);

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

Затем... если у вас есть несколько констант, и вы хотите скорость, группируйте группы вместе.

float a = 6.3f * a * 2.0f * 3.1f;

Просто напишите

a = a * (6.3f * 2.0f * 3.1f);

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

После того, как мы это скажем, мы должны часами говорить о том, как работают процессоры. Даже одна и та же семья, такая как intel, работает по-другому между поколениями! Некоторые компиляторы используют инструкции SSE, другие - нет. Некоторые процессоры поддерживают SSE2, некоторые SSE, некоторые только MMX... в какой-то системе нет FPU! Каждая система лучше выполняет некоторые вычисления, чем другие, найти общую вещь сложно.

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

Если ваше выражение выглядит сложным, сделайте некоторую алгебру и\или перейдите в поисковую систему wolframalpha и попросите его оптимизировать это для вас:)

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

a = 5 + b;
a /= 2 * c;
a += 2 - c;
a *= 7;

просто напишите свое выражение, избегая этого беспорядка:)

a = ((5 + b) / (2 * c) + 2 - c) * 7;

В вашем конкретном примере 6000.f * a * a просто напишите его, как вы его пишете, не нужно его менять; это нормально, как есть.