Почему я не могу воспроизвести проблемы с плавающей точкой с помощью функции pow? - PullRequest
2 голосов
/ 14 января 2020

Я недавно написал некоторый код, подобный этому:

// Calculate a ^ b
unsigned int result = 1;
for (unsigned int i = 0; i < b; i++) {
    result *= a;
}

Я получил комментарий, что я должен был использовать pow из math.h, и я был готов указать на плавающую точку проблемы точности, потому что pow возвращает double. Я даже нашел существующий вопрос переполнения стека , который иллюстрирует проблему. За исключением случаев, когда я пытался запустить код из этого другого вопроса, он "работал" просто отлично (то есть нет проблем, связанных с ошибкой 1).

Вот код, который я тестировал :

#include <math.h>
#include <stdio.h>

int main()
{
    unsigned int a = 10;
    unsigned int result;
    for (unsigned int b = 1; b <= 9; b++) {
        result = (unsigned int)pow(a, b);
        printf("%u\n", result);
    }
    return 0;
}

А вот как я собираю и запускаю его (на Ubuntu 18.04.3 с G CC версия 7.4.0):

$ gcc -O3 -std=c99 -Wall -Wextra -Werror -Wpedantic -pedantic-errors -o pow_test pow_test.c -lm
$ ./pow_test
10
100
1000
10000
100000
1000000
10000000
100000000
1000000000

Так почему (unsigned int)pow(a, b) работа? Я предполагаю, что компилятор делает некоторые маги c, чтобы предотвратить нормальные проблемы с плавающей точкой? Что он делает и почему? Похоже, плохая идея поощрять игнорирование этих проблем, если не все компиляторы делают это. И если все современные компиляторы делают делают это, разве это не проблема, о которой вам нужно беспокоиться больше?

Я также видел, как люди предлагают что-то вроде (unsigned int)(pow(a, b) + 0.1), чтобы избежать отключения. По 1 вопросу. Каковы преимущества / недостатки этого по сравнению с моим решением с l oop?

Ответы [ 2 ]

4 голосов
/ 14 января 2020

pow имеет double аргументы и возвращаемое значение ( cppreference.com ). double имеет 53-битное значение и точность ( Википедия ). С другой стороны, максимальное значение в примере составляет 1000000000, которое имеет 30 битов, поэтому идеально подходит без потери точности на 53 бита.

Обновление: играется с Godbolt , и оказалось, что компилятор может даже оптимизировать все вычисления, как в вашем примере a и все значения b известны во время компиляции, например clang v9.0. с опцией -O3 просто печатает константы (а gcc и msvc делают вызов pow)

3 голосов
/ 14 января 2020

Это зависит от реализации.

Реализация, которая использовалась в связанном вопросе, по-видимому, использует трансцендентные функции для реализации pow(), даже когда аргументы являются точными целыми числами. В результате получаются ошибки округления, поэтому результат иногда немного ниже фактического целочисленного результата.

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

...