Арифметические операторы
Арифметические операторы применяют стандартные математические операции к своим операндам.
| Оператор | Название оператора | Пример | Результат |
|---|---|---|---|
+ | унарный плюс | +a | значение a после преобразований |
- | унарный минус | -a | отрицание a |
+ | сложение | a + b | сумма a и b |
- | вычитание | a - b | разность a и b |
* | произведение | a * b | произведение a и b |
/ | деление | a / b | частное от деления a на b |
% | остаток от деления | a % b | остаток от деления a на b |
~ | побитовое НЕ | ~a | побитовое НЕ от a |
& | побитовое И | a & b | побитовое И от a и b |
| | побитовое ИЛИ | a | b | побитовое ИЛИ от a и b |
^ | побитовое ИСКЛЮЧАЮЩЕЕ ИЛИ | a ^ b | побитовое ИСКЛЮЧАЮЩЕЕ ИЛИ от a и b |
<< | побитовый сдвиг влево | a << b | a сдвинуто влево на b позиций |
>> | побитовый сдвиг вправо | a >> b | a сдвинуто вправо на b позиций |
Переполнения
Арифметика беззнаковых целых чисел всегда выполняется по модулю 2n, где n — число бит в этом целом числе. Например, для unsigned int, добавление единицы к UINT_MAX даёт 0, а вычитание единицы из 0 даёт UINT_MAX.
При переполнении арифметической операции со знаком (результат не помещается в тип результата), поведение не определено: может происходить перенос по правилам представления (обычно дополнительный код), может быть вызвана обработка на некоторых платформах или из-за опций компилятора (например, -ftrapv в GCC и Clang), или может быть полностью оптимизировано компилятором.
Окружение с плавающей точкой
Если #pragma STDC FENV_ACCESS установлено в ON, все арифметические операторы с плавающей точкой подчиняются текущему направлению округления с плавающей точкой и сообщают об ошибках арифметики с плавающей точкой, как указано в math_errhandling, за исключением случая, когда оператор входит в статическую инициализацию (в этом случае исключения с плавающей точкой не поднимаются, а режим округления — ближайшее значение).
Сжатие чисел с плавающей точкой
Если #pragma STDC FP_CONTRACT не установлено в OFF, все арифметические операции с плавающей точкой могут выполняться так, как если бы промежуточные результаты имели бесконечный диапазон и точность, то есть оптимизации, которые пропускают ошибки округления и исключения с плавающей точкой, которые были бы обнаружены, если бы выражение оценивалось точно, как написано. Например, это позволяет реализовать (x*y) + z с помощью одной инструкции процессора fused multiply-add или оптимизировать a = x*x*x*x; как tmp = x*x; a = tmp*tmp.
Независимо от сжатия, промежуточные результаты арифметических операций с плавающей точкой могут иметь диапазон и точность, отличные от указанных его типом, см. FLT_EVAL_METHOD
Унарная арифметика
Выражения унарных арифметических операторов имеют вид
+ выражение | (1) | |
- выражение | (2) |
Как унарный плюс, так и унарный минус сначала применяют целочисленные преобразования к своему операнду, а затем
- унарный плюс возвращает значение после преобразования
- унарный минус возвращает отрицание значения после преобразования (за исключением того, что отрицание NaN — это другой NaN)
Тип выражения — это тип после преобразования, а категория значения — не ссылка.
Примечания
Унарный минус вызывает неопределённое поведение из-за переполнения знакового целого числа при применении к INT_MIN, LONG_MIN, или LLONG_MIN, на типичных (дополнительных кодах) платформах.
В C++, унарный оператор + также может использоваться с другими встроенными типами, такими как массивы и функции, но не в C.
#include <stdio.h>
#include <complex.h>
#include <limits.h>
int main(void)
{
char c = 'a';
printf("sizeof char: %zu sizeof int: %zu\n", sizeof c, sizeof +c);
printf("-1, where 1 is signed: %d\n", -1);
// Defined behavior since arithmetic is performed for unsigned integer.
// Hence, the calculation is (-1) modulo (2 raised to n) = UINT_MAX, where n is
// the number of bits of unsigned int. If unsigned int is 32-bit long, then this
// gives (-1) modulo (2 raised to 32) = 4294967295
printf("-1, where 1 is unsigned: %u\n", -1u);
// Undefined behavior because the mathematical value of -INT_MIN = INT_MAX + 1
// (i.e. 1 more than the maximum possible value for signed int)
//
// printf("%d\n", -INT_MIN);
// Undefined behavior because the mathematical value of -LONG_MIN = LONG_MAX + 1
// (i.e. 1 more than the maximum possible value for signed long)
//
// printf("%ld\n", -LONG_MIN);
// Undefined behavior because the mathematical value of -LLONG_MIN = LLONG_MAX + 1
// (i.e. 1 more than the maximum possible value for signed long long)
//
// printf("%lld\n", -LLONG_MIN);
double complex z = 1 + 2*I;
printf("-(1+2i) = %.1f%+.1f\n", creal(-z), cimag(-z));
}Возможный вывод:
sizeof char: 1 sizeof int: 4 -1, where 1 is signed: -1 -1, where 1 is unsigned: 4294967295 -(1+2i) = -1.0-2.0
Операторы сложения
Выражения бинарных арифметических операторов сложения имеют вид
левое операнд + правое операнд | (1) | |
левое операнд - правое операнд | (2) |
- оба имеют арифметические типы, включая комплексные и мнимые
- одно — указатель на объект полного типа, другое — целого типа
- оба имеют арифметические типы, включая комплексные и мнимые
- левое операнд имеет указатель на объект полного типа, правое операнд имеет целочисленный тип
- оба — указатели на объекты полного типа совместимых типов, игнорируя квалификаторы
Арифметическое сложение и вычитание
Если оба операнда имеют арифметические типы, то
- сначала выполняются обычные арифметические преобразования
- затем значения операндов после преобразований складываются или вычитаются по обычным математическим правилам (при вычитании правый операнд вычитается из левый операнд), за исключением случаев, когда
- если один из операндов — NaN, результатом является NaN
- бесконечность минус бесконечность — NaN и
FE_INVALIDподнимается - бесконечность плюс отрицательная бесконечность — NaN и
FE_INVALIDподнимается
Комплексное и мнимое сложение и вычитание определяются следующим образом (тип результата мнимый, если оба операнда мнимые, и комплексный, если один операнд вещественный, а другой мнимый, как указано в обычных арифметических преобразованиях):
| + или - | u | iv | u + iv |
|---|---|---|---|
| x | x ± u | x ± iv | (x ± u) ± iv |
| iy | ±u + iy | i(y ± v) | ±u + i(y ± v) |
| x + iy | (x ± u) + iy | x + i(y ± v) | (x ± u) + i(y ± v) |
// work in progress // note: take part of the c/language/conversion example
Арифметика указателей
- Если указатель
Pуказывает на элемент массива с индексомI, то -
P+NиN+P— указатели, которые указывают на элемент того же массива с индексомI+N -
P-N— указатель, который указывает на элемент того же массива с индексомI-N
Поведение определено только если как исходный, так и результат указателей указывают на элементы того же массива или один за последним элементом этого массива. Обратите внимание, что выполнение p-1, когда p указывает на первый элемент массива, является неопределённым поведением и может привести к ошибке на некоторых платформах.
- Если указатель
P1указывает на элемент массива с индексомI(или один за последним элементом) иP2указывает на элемент того же массива с индексомJ(или один за последним элементом), то -
P1-P2имеет значение, равноеI-J, и типptrdiff_t(который является типом знакового целого числа, как правило, вдвое меньше размера самого большого объёкта, который может быть объявлен)
Поведение определено только если результат помещается в ptrdiff_t.
Для целей арифметики указателей указатель на объект, который не является элементом какого-либо массива, рассматривается как указатель на первый элемент массива размера 1.
// work in progress int n = 4, m = 3; int a[n][m]; // VLA of 4 VLAs of 3 ints each int (*p)[m] = a; // p == &a[0] p = p + 1; // p == &a[1] (pointer arithmetic works with VLAs just the same) (*p)[2] = 99; // changes a[1][2]
Операторы умножения
Выражения бинарных арифметических операторов умножения имеют вид
lhs * rhs | (1) | |
lhs / rhs | (2) | |
lhs % rhs | (3) |
- сначала выполняется обычное преобразование арифметических типов. Затем…
Умножение
Бинарный оператор * выполняет умножение своих операндов (после обычного преобразования арифметических типов) в соответствии с обычными арифметическими определениями, за исключением
- если один операнд — NaN, результат — NaN
- умножение бесконечности на ноль даёт NaN, и
FE_INVALID - умножение бесконечности на ненулевое значение даёт бесконечность (даже для комплексных аргументов)
Поскольку в C любой комплексный тип с хотя бы одной бесконечной частью является бесконечностью, даже если другая часть — NaN, обычные арифметические правила не применяются к умножению комплексных чисел. Другие сочетания плавающих операндов следуют из следующей таблицы:
| * | u | iv | u + iv |
|---|---|---|---|
| x | xu | i(xv) | (xu) + i(xv) |
| iy | i(yu) | −yv | (−yv) + i(yu) |
| x + iy | (xu) + i(yu) | (−yv) + i(xv) | специальные правила |
Помимо обработки бесконечности, умножение комплексных чисел не допускает переполнения промежуточных результатов, за исключением случаев, когда #pragma STDC CX_LIMITED_RANGE установлено в ON, в этом случае значение может быть вычислено как (x+iy)×(u+iv) = (xu-yv)+i(yu+xv), поскольку программист несёт ответственность за ограничение диапазона операндов и обработку бесконечностей.
Несмотря на запрет чрезмерного переполнения, умножение комплексных чисел может вызывать ложные исключения с плавающей точкой (иначе реализация без переполнения невозможна).
#include <stdio.h>
#include <stdio.h>
#include <complex.h>
#include <math.h>
int main(void)
{
// TODO simpler cases, take some from C++
double complex z = (1 + 0*I) * (INFINITY + I*INFINITY);
// textbook formula would give
// (1+i0)(∞+i∞) ⇒ (1×∞ – 0×∞) + i(0×∞+1×∞) ⇒ NaN + I*NaN
// but C gives a complex infinity
printf("%f + i*%f\n", creal(z), cimag(z));
// textbook formula would give
// cexp(∞+iNaN) ⇒ exp(∞)×(cis(NaN)) ⇒ NaN + I*NaN
// but C gives ±∞+i*nan
double complex y = cexp(INFINITY + I*NAN);
printf("%f + i*%f\n", creal(y), cimag(y));
}Возможный вывод:
inf + i*inf inf + i*nan
Деление
Бинарный оператор / делит первый операнд на второй (после обычного преобразования арифметических типов) в соответствии с обычными арифметическими определениями, за исключением
- если тип после обычного преобразования арифметических типов — целого типа, результат — алгебраическое частное (не дробь), округление в зависимости от реализации(до C99)отбрасывание до нуля(с C99)
- если один из операндов — NaN, результат — NaN
- если первый операнд — комплексная бесконечность, а второй — конечное число, результат оператора
/— комплексная бесконечность - если первый операнд — конечное число, а второй — комплексная бесконечность, результат оператора
/— ноль.
Поскольку в C любой комплексный тип с хотя бы одной бесконечной частью является бесконечностью, даже если другая часть — NaN, обычные арифметические правила не применяются к делению комплексных чисел. Другие сочетания плавающих операндов следуют из следующей таблицы:
| / | u | iv |
|---|---|---|
| x | x/u | i(−x/v) |
| iy | i(y/u) | y/v |
| x + iy | (x/u) + i(y/u) | (y/v) + i(−x/v) |
Помимо обработки бесконечности, деление комплексных чисел не допускает переполнения промежуточных результатов, за исключением случаев, когда #pragma STDC CX_LIMITED_RANGE установлено в ON, в этом случае значение может быть вычислено как (x+iy)/(u+iv) = [(xu+yv)+i(yu-xv)]/(u2+v2), поскольку программист несёт ответственность за ограничение диапазона операндов и обработку бесконечностей.
Несмотря на запрет чрезмерного переполнения, деление комплексных чисел может вызывать ложные исключения с плавающей точкой (иначе реализация без переполнения невозможна).
Если второй операнд равен нулю, поведение неопределённо, за исключением случаев, когда поддерживается IEEE плавающая арифметика, и деление с плавающей точкой:
- Деление ненулевого числа на ±0.0 даёт соответствующую по знаку бесконечность, и
FE_DIVBYZERO - Деление 0.0 на 0.0 даёт NaN и
FE_INVALID
Остаток от деления
Бинарный оператор % возвращает остаток от деления первого операнда на второй (после обычного преобразования арифметических типов).
Знак остатка определяется таким образом, что если частное a/b представимо в типе результата, то (a/b)*b + a%b == a.
Если второй операнд равен нулю, поведение неопределённо.
Если частное a/b не представимо в типе результата, поведение как a/b , так и a%b неопределённо (т.е. INT_MIN%-1 неопределённо на системах со 2-х дополнением).
Примечание: оператор остатка не работает с числами с плавающей точкой, для этой функциональности используется библиотечная функция fmod.
Битовые логические операции
Выражения битовых арифметических операторов имеют вид
~ rhs | (1) | |
lhs & rhs | (2) | |
lhs | rhs | (3) | |
lhs ^ rhs | (4) |
где
| lhs, rhs | - | выражения целочисленного типа |
Сначала операторы &, ^, и | выполняют обычное преобразование арифметических типов для обоих операндов, а оператор ~ выполняет преобразования целочисленных типов для своего единственного операнда.
Затем применяются соответствующие бинарные логические операторы побитово; то есть каждый бит результата устанавливается или сбрасывается в соответствии с логической операцией (НЕ, И, ИЛИ или ИСКЛЮЧАЮЩЕЕ ИЛИ), применённой к соответствующим битам операндов.
Примечание: битовые операторы обычно используются для манипулирования наборами битов и битовыми масками.
Примечание: для беззнаковых типов (после преобразования) выражение ~E эквивалентно максимальному значению, представимому типом результата, минус исходное значение E.
#include <stdio.h>
#include <stdint.h>
int main(void)
{
uint32_t a = 0x12345678;
uint16_t mask = 0x00f0;
printf("Promoted mask:\t%#010x\n"
"Value:\t\t%#x\n"
"Setting bits:\t%#x\n"
"Clearing bits:\t%#x\n"
"Selecting bits:\t%#010x\n"
, mask
, a
, a | mask
, a & ~mask
, a & mask
);
}Возможный вывод:
Promoted mask: 0x000000f0 Value: 0x12345678 Setting bits: 0x123456f8 Clearing bits: 0x12345608 Selecting bits: 0x00000070
Операторы сдвига
Выражения битовых операторов сдвига имеют вид
lhs << rhs | (1) | |
lhs >> rhs | (2) |
где
| lhs, rhs | - | выражения целочисленного типа |
Сначала выполняются преобразования целочисленных типов индивидуально для каждого операнда (примечание: это отличается от других бинарных арифметических операторов, которые все выполняют обычные арифметические преобразования). Тип результата — тип lhs после преобразования.
Поведение неопределено, если rhs отрицательное или больше или равно количеству битов в преобразованном lhs.
Для беззнакового lhs значение LHS << RHS — это значение LHS * 2RHS, уменьшенное по модулю максимального значения типа возврата плюс 1 (то есть выполняется побитовый сдвиг влево, и биты, которые выходят за пределы типа назначения, отбрасываются). Для знакового lhs с неотрицательными значениями значение LHS << RHS — это LHS * 2RHS, если оно представимо в преобразованном типе lhs, в противном случае поведение неопределённо.
Для беззнакового lhs и для знакового lhs со значениями неотрицательными, значение LHS >> RHS — целая часть LHS / 2RHS. Для отрицательного LHS, значение LHS >> RHS определено реализацией, где в большинстве реализаций используется арифметический сдвиг вправо (чтобы результат оставался отрицательным). Таким образом, в большинстве реализаций при сдвиге вправо знакового LHS новые разряды старшего порядка заполняются исходным разрядом знака (т. е. 0, если он был неотрицательным, и 1, если он был отрицательным).
#include <stdio.h>
enum {ONE=1, TWO=2};
int main(void)
{
char c = 0x10;
unsigned long long ulong_num = 0x123;
printf("0x123 << 1 = %#llx\n"
"0x123 << 63 = %#llx\n" // overflow truncates high bits for unsigned numbers
"0x10 << 10 = %#x\n", // char is promoted to int
ulong_num << 1, ulong_num << 63, c << 10);
long long long_num = -1000;
printf("-1000 >> 1 = %lld\n", long_num >> ONE); // implementation defined
}Возможный вывод:
0x123 << 1 = 0x246 0x123 << 63 = 0x8000000000000000 0x10 << 10 = 0x4000 -1000 >> 1 = -500
Ссылки
- Стандарт C17 (ISO/IEC 9899:2018):
- 6.5.3.3 Унарные арифметические операторы (стр. 64)
- 6.5.5 Множительные операторы (стр. 66)
- 6.5.6 Арифметические операторы (стр. 66-68)
- 6.5.7 Операторы битового сдвига (стр. 68)
- 6.5.10 Оператор побитового И (стр. 70)
- 6.5.11 Оператор побитового исключающего ИЛИ (стр. 70)
- 6.5.12 Оператор побитового ИЛИ (стр. 70-71)
- Стандарт C11 (ISO/IEC 9899:2011):
- 6.5.3.3 Унарные арифметические операторы (стр. 89)
- 6.5.5 Множительные операторы (стр. 92)
- 6.5.6 Арифметические операторы (стр. 92-94)
- 6.5.7 Операторы битового сдвига (стр. 94-95)
- 6.5.10 Оператор побитового И (стр. 97)
- 6.5.11 Оператор побитового исключающего ИЛИ (стр. 98)
- 6.5.12 Оператор побитового ИЛИ (стр. 98)
- Стандарт C99 (ISO/IEC 9899:1999):
- 6.5.3.3 Унарные арифметические операторы (стр. 79)
- 6.5.5 Множительные операторы (стр. 82)
- 6.5.6 Арифметические операторы (стр. 82-84)
- 6.5.7 Операторы битового сдвига (стр. 84-85)
- 6.5.10 Оператор побитового И (стр. 87)
- 6.5.11 Оператор побитового исключающего ИЛИ (стр. 88)
- 6.5.12 Оператор побитового ИЛИ (стр. 88)
- Стандарт C89/C90 (ISO/IEC 9899:1990):
- 3.3.3.3 Унарные арифметические операторы
- 3.3.5 Множительные операторы
- 3.3.6 Арифметические операторы
- 3.3.7 Операторы битового сдвига
- 3.3.10 Оператор побитового И
- 3.3.11 Оператор побитового исключающего ИЛИ
- 3.3.12 Оператор побитового ИЛИ
См. также
| Общие операторы | ||||||
|---|---|---|---|---|---|---|
| присваивания | инкремента/декремента | арифметические | логические | сравнения | доступа к членам | другие |
|
|
|
|
|
|
|
|
| Документация C++ для Арифметических операторов |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/c/language/operator_arithmetic