Арифметические операции

Последнее обновление: 09.02.2023

Арифметические операции производятся над числами. Значения, которые участвуют в операции, называются операндами. В языке программирования C++ арифметические операции могут быть бинарными (производятся над двумя операндами) и унарными (выполняются над одним операндом). К бинарным операциям относят следующие:

  • +

    Операция сложения возвращает сумму двух чисел:

    int a {10};
    int b {7};
    int c {a + b};  // 17
    int d {4 + b};  // 11
    

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

    int a {10};
    int b {7};
    int c = a + b;  // 17
    int d = 4 + b;  // 11
    
  • -

    Операция вычитания возвращает разность двух чисел:

    int a {10};
    int b {7};
    int c {a - b};  // 3
    int d {4 - b};  // -3
    
  • *

    Операция умножения возвращает произведение двух чисел:

    int a {10};
    int b {7};
    int c {a * b};  // 70
    int d {4 * b};  // 28
    
  • /

    Операция деления возвращает частное двух чисел:

    int a {26};
    int b {5};
    int c {a / b};      // c = 5
    int d {4 / b};     // d = 0
    

    При делении стоит быть внимательным, так как если в операции участвуют два целых числа, то дробная часть (при ее наличии) будет отбрасываться, даже если результат присваивается переменной float или double:

    #include <iostream>
    
    int main()
    {
        int a {26};
        int b {5};
        float c {a / b};      // c = 5
        double d {4 / b};     // d = 0
        std::cout << "c = " << c << std::endl;
        std::cout << "d = " << d << std::endl;
    }
    

    Чтобы результат представлял число с плавающей точкой, один из операндов также должен представлять число с плавающей точкой:

    #include <iostream>
    
    int main()
    {
        float a {26};
        int b {5};
        float c {a / b};        // c = 5.2
        double d {4.0 / b};     // d = 0.8
        std::cout << "c = " << c << std::endl;
        std::cout << "d = " << d << std::endl;
    }
    
  • %

    Операция получения остатка от целочисленного деления:

    int a {26};
    int b {5};
    int c {a % b};      // c = 26 % 5 = 26 - 5 * 5 = 1
    int d {4 % b};     // d = 4 % 5 = 4
    

Некоторые особенности при работе с числами с плавающей точкой

При сложении или вычитании чисел с плавающей точкой, которые сильно отличаются по значению, следует проявлять осторожность. Например, сложим число 1.23E-4 (0.000123) и 3.65E+6 (3650000). Мы ожидаем, что сумма будет равна 3650000,000123. Но при преобразовании в число с плавающей запятой с точностью до семи цифр это становится следующим

3.650000E+06 + 1.230000E-04 = 3.650000E+06

Или соответствующий код на С++:

#include <iostream>

int main()
{
    float num1{ 1.23E-4  };        // 0.000123
    float num2{ 3.65E+6  };        // 3650000
    float sum {num1 + num2};       // sum =3.65e+06
    std::cout << "sum =" << sum << "\n";
}

То есть первое число никак не изменилось, поскольку для хранения точности отводится только 7 цифр.

Также стоит отметить, что стандарт IEEE, который реализуется компиляторами С++, определяет специальные значения для чисел с плавающей точкой, в которых мантисса на бинарном уровне состоит только из нулей, а экспонента, которая состоит из одних единиц, в зависимости от знака представляет значения +infinity (плюс бесконечность +∞) и -infinity (минус бесконечность -∞). И при делении положительного числа на ноль, результатом будет +infinity, а при делении отрицательного числа на ноль - -infinity.

Другое специальное значение с плавающей точкой, определенное этим стандартом, представляет значение NaN (не число). Это значение представляет результат операции, который не определяется математически, например, когда ноль делится на ноль или бесконечность на бесконечность. Результатом любой операции, в которой один или оба операнда являются NaN, также является NaN.

Для демонстрации рассмотрим следующую программу:

#include <iostream>

int main()
{
    double a{ 1.5 }, b{}, c{}, d {-1.5};
    double result { a / b };
    std::cout << a << "/" << b << " = " << result << std::endl;
    result = d / c;
    std::cout << d << "/" << c << " = " << result << std::endl;
    result = b / c;
    std::cout << b << "/" << c << " = " << result << std::endl;
    std::cout << result << " + " << a << " = " << result + a << std::endl;
}

В выражении a / b число 1.5 делится на 0, поэтому результатом будет плюс бесконечность.

Аналогично в выражении d / c число -1.5 делится на 0, поэтому результатом будет минус бесконечность.

В выражении b / c число 0 делится на 0, поэтому результатом будет NaN.

Соответственно последнее выражение result + a будет аналогично NaN + 0, соответственно результатом тоже будет NaN

Консольный вывод программы

1.5/0 = inf
-1.5/0 = -inf
0/0 = nan
nan + 1.5 = nan

Инкремент и декремент

Также есть две унарные арифметические операции, которые производятся над одним числом: ++ (инкремент) и -- (декремент). Каждая из операций имеет две разновидности: префиксная и постфиксная:

  • Префиксный инкремент.

    Увеличивает значение переменной на единицу и полученный результат используется как значение выражения ++x

    #include <iostream>
    
    int main()
    {
        int a {8};
        int b {++a};
        std::cout << "a = " << a << std::endl;  // a = 9
        std::cout << "b = " << b << std::endl;  // b = 9
    }
    
  • Постфиксный инкремент.

    Увеличивает значение переменной на единицу, но значением выражения x++ будет то, которое было до увеличения на единицу

    #include <iostream>
    
    int main()
    {
        int a {8};
        int b {a++};
        std::cout << "a = " << a << std::endl;  // a = 9
        std::cout << "b = " << b << std::endl;  // b = 8
    }
    
  • Префиксный декремент.

    Уменьшает значение переменной на единицу, и полученное значение используется как значение выражения --x

    #include <iostream>
    
    int main()
    {
        int a {8};
        int b {--a};
        std::cout << "a = " << a << std::endl;  // a = 7
        std::cout << "b = " << b << std::endl;  // b = 7
    }
    
  • Постфиксный декремент.

    Уменьшает значение переменной на единицу, но значением выражения x-- будет то, которое было до уменьшения на единицу

    #include <iostream>
    
    int main()
    {
        int a {8};
        int b {a--};
        std::cout << "a = " << a << std::endl;  // a = 7
        std::cout << "b = " << b << std::endl;  // b = 8
    }
    

Приоритет и ассоциативность операторов

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

Кроме того, одни операции имеют больший приоритет, чем другие и поэтому выполняются вначале. Операции в порядке уменьшения приоритета:

++ (инкремент), -- (декремент)
* (умножение), / (деление), % (остаток от деления)
+ (сложение), - (вычитание)

Приоритет операций следует учитывать при выполнении набора арифметических выражений:

int a = 8;
int b = 7;
int c = a + 5 * ++b;      // 48

Хотя операции выполняются слева направо, но вначале будет выполняться операция инкремента ++b, которая увеличит значение переменной b и возвратит его в качестве результата, так как эта операция имеет больший приоритет. Затем выполняется умножение 5 * ++b, и только в последнюю очередь выполняется сложение a + 5 * ++b

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

int count {1};
int result = ++count * 3 + count++ * 5;

Так, и g++, и clang++ скомпилируют данный код, и результат переменной result будет таким, как в принципе и ожидается - 16, но компилятор clang++ также сгенерирует предупреждение.

Переопределение порядка операций

Скобки позволяют переопределить порядок вычислений. Например:

#include <iostream>

int main()
{
    int a {8};
    int b {7};
    int c {(a + 5) * ++b};      // c = 104
    std::cout << "c = " << c << std::endl;
}

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

Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850