Arithmetic operators
Арифметические операторы применяют стандартные математические операции к своим операндам.
|
Этот раздел не завершён
Причина: рассмотрите более универсальное оглавление для этой и других таблиц, охватывающих несколько тем |
| Оператор | Название оператора | Пример | Результат |
|---|---|---|---|
| + | унарный плюс | + 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 |
Содержание |
Переполнения
Арифметика беззнаковых целых чисел всегда выполняется
по модулю 2
n
, где 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
с помощью одной инструкции FMA процессора или оптимизировать
a
=
x
*
x
*
x
*
x
;
в
tmp
=
x
*
x
;
a
=
tmp
*
tmp
.
Не связанные с контрактным программированием промежуточные результаты операций с плавающей запятой могут иметь диапазон и точность, отличные от указанных их типом, см. FLT_EVAL_METHOD
Унарная арифметика
Унарные арифметические операторы имеют следующий вид
+
выражение
|
(1) | ||||||||
-
выражение
|
(2) | ||||||||
| expression | - | выражение любого arithmetic type |
Унарный плюс и унарный минус сначала применяют integral promotions к своему операнду, а затем
- унарный плюс возвращает значение после продвижения
- унарный минус возвращает отрицание значения после продвижения (за исключением того, что отрицание NaN является другим NaN)
Тип выражения - это тип после продвижения, а категория значения является не-lvalue.
Примечания
Унарный минус вызывает неопределённое поведение из-за переполнения знакового целого числа при применении к INT_MIN , LONG_MIN или LLONG_MIN на типичных (дополнение до 2) платформах.
В 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); // Определенное поведение, поскольку арифметика выполняется для беззнакового целого. // Следовательно, вычисление (-1) по модулю (2 в степени n) = UINT_MAX, где n - // количество бит unsigned int. Если unsigned int имеет длину 32 бита, то это // дает (-1) по модулю (2 в степени 32) = 4294967295 printf("-1, where 1 is unsigned: %u\n", -1u); // Неопределенное поведение, потому что математическое значение -INT_MIN = INT_MAX + 1 // (т.е. на 1 больше максимального возможного значения для знакового int) // // printf("%d\n", -INT_MIN); // Неопределенное поведение, потому что математическое значение -LONG_MIN = LONG_MAX + 1 // (т.е. на 1 больше максимального возможного значения для знакового long) // // printf("%ld\n", -LONG_MIN); // Неопределенное поведение, потому что математическое значение -LLONG_MIN = LLONG_MAX + 1 // (т.е. на 1 больше максимального возможного значения для знакового 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
Аддитивные операторы
Выражения бинарного арифметического оператора сложения имеют вид
lhs
+
rhs
|
(1) | ||||||||
lhs
-
rhs
|
(2) | ||||||||
-
- оба имеют арифметические типы , включая комплексные и мнимые
- один является указателем на полный объектный тип, другой имеет целочисленный тип
-
- оба имеют арифметические типы , включая комплексные и мнимые
- lhs имеет указатель на полный объектный тип, rhs имеет целочисленный тип
- оба являются указателями на полные объекты совместимых типов, без учёта квалификаторов
Арифметическое сложение и вычитание
Если оба операнда имеют арифметические типы , тогда
- сначала usual arithmetic conversions выполняются
- затем значения операндов после преобразований складываются или вычитаются согласно обычным математическим правилам (для вычитания rhs вычитается из lhs ), за исключением того, что
-
- если один операнд — 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
+
N
и
N
+
P
являются указателями, которые указывают на элемент того же массива с индексом
Поведение определено только в том случае, если и исходный указатель, и результирующий указатель указывают на элементы одного массива или на позицию за последним элементом этого массива. Обратите внимание, что выполнение 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) | ||||||||
- сначала usual arithmetic conversions выполняются. Затем...
Умножение
Бинарный оператор * выполняет умножение своих операндов (после обычных арифметических преобразований) в соответствии с обычными арифметическими определениями, за исключением того, что
- если один операнд является 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 более простые случаи, взять некоторые из C++ double complex z = (1 + 0*I) * (INFINITY + I*INFINITY); // по учебной формуле получилось бы // (1+i0)(∞+i∞) ⇒ (1×∞ – 0×∞) + i(0×∞+1×∞) ⇒ NaN + I*NaN // но C дает комплексную бесконечность printf("%f + i*%f\n", creal(z), cimag(z)); // по учебной формуле получилось бы // cexp(∞+iNaN) ⇒ exp(∞)×(cis(NaN)) ⇒ NaN + I*NaN // но C дает ±∞+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)]/(u
2
+v
2
)
, так как программист берёт на себя ответственность за ограничение диапазона операндов и обработку бесконечностей.
Несмотря на запрет недопустимого переполнения, деление комплексных чисел может вызывать ложные исключения с плавающей точкой (в противном случае реализация версий без переполнения чрезвычайно сложна)
Если второй операнд равен нулю, поведение не определено, за исключением случая, когда поддерживается арифметика 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
не определено в системах с дополнительным кодом)
Примечание: оператор остатка не работает с типами с плавающей точкой, библиотечная функция fmod предоставляет эту функциональность.
Побитовая логика
Выражения с побитовыми арифметическими операторами имеют вид
~
rhs
|
(1) | ||||||||
lhs
&
rhs
|
(2) | ||||||||
lhs
|
rhs
|
(3) | ||||||||
lhs
^
rhs
|
(4) | ||||||||
где
| lhs , rhs | - | выражения целочисленного типа |
Сначала операторы & , ^ и | выполняют обычные арифметические преобразования для обоих операндов, а оператор ~ выполняет целочисленные повышения для своего единственного операнда.
Затем соответствующие бинарные логические операторы применяются побитово; то есть каждый бит результата устанавливается или сбрасывается в соответствии с логической операцией (NOT, AND, OR или XOR), применённой к соответствующим битам операндов.
Примечание: побитовые операторы обычно используются для работы с битовыми наборами и битовыми масками.
Примечание: для беззнаковых типов (после продвижения) выражение ~E эквивалентно максимальному значению, представимому результирующим типом, минус исходное значение E .
Возможный вывод:
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 * 2
RHS
, приведённому по модулю максимального значения возвращаемого типа плюс 1 (то есть выполняется побитовый сдвиг влево, и биты, выходящие за пределы целевого типа, отбрасываются). Для знакового
lhs
с неотрицательными значениями значение
LHS << RHS
равно
LHS * 2
RHS
, если оно представимо в продвинутом типе
lhs
, в противном случае поведение не определено.
Для беззнакового
lhs
и для знакового
lhs
с неотрицательными значениями, значение
LHS >> RHS
является целой частью от
LHS / 2
RHS
. Для отрицательного
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" // переполнение обрезает старшие биты для беззнаковых чисел "0x10 << 10 = %#x\n", // char продвигается до int ulong_num << 1, ulong_num << 63, c << 10); long long long_num = -1000; printf("-1000 >> 1 = %lld\n", long_num >> ONE); // определяется реализацией }
Возможный вывод:
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 Оператор побитового включающего ИЛИ
Смотрите также
| Общие операторы | ||||||
|---|---|---|---|---|---|---|
| присваивание |
инкремент
декремент |
арифметические | логические | сравнения |
доступа к членам
доступа |
другие |
|
a
=
b
|
++
a
|
+
a
|
!
a
|
a
==
b
|
a
[
b
]
|
a
(
...
)
|
|
Документация C++
для
Арифметические операторы
|