Nano Hash - криптовалюты, майнинг, программирование

Как написать цикл, вычисляющий мощность?

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

int Fast_Power(int base, int exp){
    int i = 2;
    int result;

    if(exp == 0){
        result = 1;
        }
    if(exp == 1){
        result = base;
        }
    else{
        for(i = 2; i < exp; i++){
            base *= base; 
            result = base;
        }
    }
    return result;
}
02.02.2020

  • result = base; if (!exp) return 1; for(i = 1; i < exp; i++) result *= base; return result; - но нужно проверить целочисленное переполнение. Даже относительно небольшой показатель степени может привести к переполнению большого целого числа ... 02.02.2020
  • Выполнение base * = base работает для четных степеней до 4 - ›какое значение base использовалось? 02.02.2020
  • @chux, степень четырех работает случайно, например, 3^4. Вы хотите 3*3*3*3 = 81, но получаете base, начиная с 3, затем дважды возводя в квадрат (для i из 2 и 3), давая 9, а затем 81. Степень двойки фактически приводит к неинициализированному результату. 02.02.2020
  • Вы не допускаете того факта, что диапазон значений, которые может представлять int, конечен. Стандарт не гарантирует, что int может представлять больше 32767, поэтому значение base больше 13 может вызвать переполнение, если оно возведено в степень 4. Даже для 32-битного int произойдет переполнение, если значение больше 215 будет возведено в степень 4. Когда происходит переполнение int, поведение не определено. 02.02.2020

Ответы:


1
base *= base;

Ваша проблема заключается в этом утверждении, вам вообще не следует изменять base. Скорее, вам следует регулировать result на основе постоянного значения base.

Чтобы выполнить полномочия, вам нужно повторное умножение, но base *= base дает вам повторное возведение в квадрат значения, и поэтому вы получите гораздо большее значение, чем хотелось бы. Это действительно работает для степеней четырех, поскольку вы выполняете итерацию 4 - 2 раз, возводя каждую итерацию в квадрат, и x4 == (x2)2.

Это не сработает для более высоких степеней, таких как шесть, поскольку вы выполняете итерацию 6 - 2 раз и x6 != (((x2)2)2)2. Последнее значение фактически эквивалентно x16.

Кстати (несмотря на ваше утверждение), на самом деле это не гарантированно работает при степени двойки. Если вы следуете коду в этом случае, вы увидите, что result никогда не присваивается значение, поэтому возвращаемое значение будет произвольным. Если это работает для вас, это случайно и может в какой-то момент вас укусить.


Алгоритм, который вы можете использовать, должен быть примерно таким:

float power(float base, int exponent):
    # 0^0 is undefined.

    if base == 0 and exponent == 0:
        throw bad_input

    # Handle negative exponents.

    if exponent < 0:
        return 1 / power(base, -exponent)

    # Repeated multiplication to get power.

    float result = 1
    while exponent > 0:
        # Use checks to detect overflow.

        float oldResult = result
        result *= base
        if result / base is not close to oldResult:
            throw overflow
        exponent -= 1

    return result

Этот алгоритм обрабатывает:

  • отрицательные целые показатели (начиная с x-y = 1/xy);
  • неопределенный случай 00; и
  • переполнение, если у вас нет значений произвольной точности (в основном, если (x * y) / y != x, вы можете быть достаточно уверены в том, что произошло переполнение). Обратите внимание на использование «не близко к», неразумно проверять точное равенство с плавающей запятой из-за возможности ошибок из-за ограничений точности - гораздо лучше реализовать проверку «достаточно близко к» некоторого описания.

Одна вещь, которую следует иметь в виду при переводе на C или C ++, реализация дополнения 2 вызовет проблемы при использовании самого отрицательного целого числа, поскольку его отрицание часто снова является тем же значением снова из-за дисбаланса между положительными и отрицательными значениями. Это может привести к бесконечной рекурсии.

Вы можете исправить это, просто обнаружив случай на ранней стадии (прежде чем что-либо еще), с помощью чего-то вроде:

if INT_MIN == -INT_MAX - 1 and exp == INT_MIN:
    throw bad_input

Первая часть обнаруживает реализацию дополнения до 2, а вторая обнаруживает (проблемное) использование INT_MIN в качестве показателя степени.

02.02.2020
  • Хороший аргумент, @chux, добавил примечание, которое, надеюсь, касается этого случая. 02.02.2020

  • 2

    Что вы делали неправильно, так это base *= base каждый раз в цикле, который изменяет саму базу на каждой итерации.

    Вместо этого вы хотите, чтобы база оставалась прежней, и умножьте конечный результат на исходную базовую «exp» раз.

    int Fast_Power(int base, int exp){
        int result=1;
    
        if(exp == 0){
            result = 1;
        }
        if(exp == 1){
            result = base;
        }
        else{
            for(int i = 0; i < exp; i++){
                result *= base;
            }
        }
        return result;
    }
    
    02.02.2020

    3

    Базовый, но наивный алгоритм, который вы ищете, который ужасно подвержен целочисленному переполнению:

    int Fast_Power (int base, int exp)
    {
        int result = base;
    
        if (exp == 0)
            return result ? 1 : 0;
    
        for (int i = 1; i < exp; i++) {
            result *= base;
        }
    
        return result;
    }
    

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

    Минимальная проверка (см .: Поймать и вычислить переполнение во время умножения двух больших целых чисел), можно объединить следующим образом. Здесь вы должны использовать более широкий тип для временного расчета, а затем сравнить результаты с INT_MIN и INT_MAX (указанными в заголовке limits.h), чтобы определить, произошло ли переполнение:

    #include <limits.h>
    ...
    int Fast_Power (int base, int exp)
    {
        int result = base;
    
        if (exp == 0)
            return result ? 1 : 0;
    
        for (int i = 1; i < exp; i++) {
            long long int tmp = (long long) result * base;  /* tmp of wider type */
            if (tmp < INT_MIN || INT_MAX < tmp) {           /* check for overflow */
                fputs ("error: overflow occurred.\n", stderr);
                return 0;
            }
            result = tmp;
        }
    
        return result;
    }
    

    Теперь, если вы попытаетесь, например Fast_Power (2, 31); генерируется ошибка и возвращается ноль.

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

    02.02.2020
  • Кстати, я считаю, что ноль в степени нуля не определен, а не ноль. 02.02.2020
  • Заменен тест деления на переполнение сравнением с INT_MIN и INT_MAX в качестве более точной проверки, позволяющей избежать Стандарт C11 - 3.4.3 (p3), который C18 Standard - 3.4.3(p4) (текст экзамена идентичен в обоих), полученный в результате промежуточного result * base умножения, необходимого перед тестом на деление. 02.02.2020

  • 4

    Во-первых, я согласен, что использование base *= base, вероятно, было ошибкой. Тем не менее, это не обязательно ошибка. Мое первое впечатление заключалось в том, что OP пытался вычислить мощность так же, как человек мог бы вычислить его вручную. Например, если вы хотите вычислить 3 ^ 13, разумным способом будет начать с вычисления показателей степени, которые являются степенями двойки.

    • 3^1 = 3
    • 3^2 = 3*3 = 9
    • 3^4 = 3^2 * 3^2 = 81
    • 3^8 = 3^4 * 3^4 = 6,561

    Затем вы можете использовать эти результаты для вычисления 3 ^ 13 как

    • 3^13 = 3^1 * 3^4 * 3^8 = 1,594,323

    Как только вы поймете шаги, вы сможете это закодировать. Самая сложная часть, вероятно, определить, когда прекратить возводить основу в квадрат и какие квадраты должны быть включены в окончательный расчет. Возможно, удивительно, что (беззнаковое) двоичное представление экспоненты говорит нам об этом! Это потому, что цифры в двоичном формате представляют степени двойки, которые в сумме образуют число. Имея это в виду, мы можем написать следующее.

    int Fast_Power(int base, int exp) {
        int result = 1;
        unsigned int expu = exp;
        unsigned int power_of_two = 1;
        while (expu > 0) {
            if (power_of_two & expu) {
                result *= base;
                expu ^= power_of_two;
            }
            power_of_two <<= 1;
            base *= base;
        }
        return result;
    }
    

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

    02.02.2020
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..