Spec-Zone.ru › C

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

Арифметические операторы применяют стандартные математические операции к своим операндам.

Оператор Название оператора Пример Результат
+ унарный плюс +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)
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)
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)
1) умножение. lhs и rhs должны иметь арифметические типы
2) деление. lhs и rhs должны иметь арифметические типы
3) остаток от деления. lhs и rhs должны иметь целые типы
  • сначала выполняется обычное преобразование арифметических типов. Затем…

Умножение

Бинарный оператор * выполняет умножение своих операндов (после обычного преобразования арифметических типов) в соответствии с обычными арифметическими определениями, за исключением

  • если один операнд — 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)
1) битовое НЕ
2) битовое И
3) битовое ИЛИ
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)
1) сдвиг влево lhs на rhs бит
2) сдвиг вправо lhs на rhs бит

где

lhs, rhs - выражения целочисленного типа

Сначала выполняются преобразования целочисленных типов индивидуально для каждого операнда (примечание: это отличается от других бинарных арифметических операторов, которые все выполняют обычные арифметические преобразования). Тип результата — тип lhs после преобразования.

Поведение неопределено, если rhs отрицательное или больше или равно количеству битов в преобразованном lhs.

Для беззнакового lhs значение LHS << RHS — это значение LHS * 2RHS, уменьшенное по модулю максимального значения типа возврата плюс 1 (то есть выполняется побитовый сдвиг влево, и биты, которые выходят за пределы типа назначения, отбрасываются). Для знакового lhs с неотрицательными значениями значение LHS << RHS — это LHS * 2RHS, если оно представимо в преобразованном типе lhs, в противном случае поведение неопределённо.

END_OF_DOCUMENT_MARKER

Для беззнакового 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 Оператор побитового ИЛИ

См. также

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

Общие операторы
присваивания инкремента/декремента арифметические логические сравнения доступа к членам другие

a = b a += b a -= b a *= b a /= b a %= b a &= b a |= b a ^= b a <<= b a >>= b

++a --a a++ a--

+a -a a + b a - b a * b a / b a % b ~a a & b a | b a ^ b a << b a >> b

!a 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(...) a, b (type) a a ? b : c sizeof

_Alignof
(с C11)

Документация C++ для Арифметических операторов

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/c/language/operator_arithmetic

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API