Побитовые операции
Побитовые операции (англ. bitwise operations) — операции, производимые над цепочками битов. Выделяют два типа побитовых операций: логические операции и побитовые сдвиги.
Принцип работы
Логические побитовые операции
Битовые операторы И [math](AND,\ \&)[/math] , ИЛИ [math](OR,\ \mid)[/math] , НЕ [math](NOT,\ \sim)[/math] и исключающее ИЛИ [math](XOR,\ $\textasciicircum$,\ \oplus)[/math] используют те же таблицы истинности, что и их логические эквиваленты.
Побитовое И
Побитовое И используется для выключения битов. Любой бит, установленный в [math]0[/math] , вызывает установку соответствующего бита результата также в [math]0[/math] .
& |
---|
11001010 11100010 |
11000010 |
Побитовое ИЛИ
Побитовое ИЛИ используется для включения битов. Любой бит, установленный в [math]1[/math] , вызывает установку соответствующего бита результата также в [math]1[/math] .
| |
---|
11001010 11100010 |
11101010 |
Побитовое НЕ
Побитовое НЕ инвертирует состояние каждого бита исходной переменной.
~ |
---|
11001010 |
00110101 |
Побитовое исключающее ИЛИ
Исключающее ИЛИ устанавливает значение бита результата в [math]1[/math] , если значения в соответствующих битах исходных переменных различны.
^ |
---|
11001010 11100010 |
00101000 |
Побитовые сдвиги
Операторы сдвига [math]\lt \lt [/math] и [math]<\gt \gt >[/math] сдвигают биты в переменной влево или вправо на указанное число. При этом на освободившиеся позиции устанавливаются нули (кроме сдвига вправо отрицательного числа, в этом случае на свободные позиции устанавливаются единицы, так как числа представляются в двоичном дополнительном коде и необходимо поддерживать знаковый бит).
Сдвиг влево может применяться для умножения числа на два, сдвиг вправо — для деления.
x = 7 // 00000111 (7) x = x >> 1 // 00000011 (3) x = x // 00000110 (6) x = x // 11000000 (-64) x = x >> 2 // 11110000 (-16)
В языке программирования Java существует также оператор беззнакового битового сдвига вправо [math]\gt \gt \gt [/math] . При использовании этого оператора на освободившиеся позиции всегда устанавливаются нули.
x = 7 // 00000111 (7) x = x // 11100000 (-32) x = x >>> 2 // 00111000 (56)
Применение
Сложные операции
Определение знака числа
Пусть дано число [math]x[/math] . Поскольку при сдвиге вправо на освобождающиеся позиции устанавливается бит знака, знак числа [math]x[/math] можно определить, выполнив сдвиг вправо на всю длину переменной:
int32 getSign(x: int32): if x != 0: mask = 1 else: mask = 0 return mask | (x >> 31) // результатом будет -1, 0, или +1 // для отрицательного, равного нулю и положительного числа x соответственно
Используя побитовые операции можно также узнать, различны ли знаки двух переменных [math]x[/math] и [math]y[/math] . Если числа имеют различный знак, то результат операции XOR, произведенной над их знаковыми битами, будет единицей. Поэтому неравенство [math](x \oplus y) \lt 0[/math] будет верно в том случае, если числа [math]x[/math] и [math]y[/math] разного знака.
Вычисление модуля числа без использования условного оператора
Пусть дано число [math]x[/math] . Если [math]x[/math] положительно, то [math]mask = 0[/math] , и [math](x + mask) \oplus mask = x[/math] . В случае, если [math]x[/math] отрицательно, [math]mask = -1[/math] . Тогда получается, что мы работаем с числом [math]x[/math] так, как будто оно представлено в коде со сдвигом с тем отличием, что у нас знаковый бит принимает значение [math]1[/math] для отрицательных чисел, а [math]0[/math] — для положительных.
int32 abs1(x: int32): mask = x >> 31 return (x + mask) XOR mask int32 abs2(x: int32): mask = x >> 31 return (x + mask) XOR mask
Нахождение минимума и максимума из двух чисел без использования условного оператора
Этот способ корректен только если можно утверждать, что величина [math](x — y)[/math] лежит между граничными значениями типа int.
Пусть даны числа [math]x[/math] и [math]y[/math] разрядности [math]n[/math] . Тогда если [math]x \lt y[/math] , то [math]((x — y) \gt \gt (n — 1)) = -1[/math] , а если [math]x \geqslant y[/math] , то [math]((x — y) \gt \gt (n — 1)) = 0[/math] . Выражение [math]((x — y) \& ((x — y) \gt \gt (n — 1))[/math] принимает значение [math]0[/math] , если [math]x \geqslant y[/math] , и [math](x — y)[/math] , если [math]x \lt y[/math] .
int32 min(x, y: int32): return y + ((x - y) & ((x - y) >> 31)) int32 max(x, y: int32): return x - ((x - y) & ((x - y) >> 31))
Проверка на то, является ли число степенью двойки
Пусть дано число [math]x[/math] . Тогда, если результатом выражения [math](x\ \&\&\ !(x\ \&\ (x — 1)))[/math] является единица, то число [math]x[/math] — степень двойки.
Правая часть выражения [math](!(x\ \&\ (x — 1)))[/math] будет равна единице, только если число [math]x[/math] равно [math]0[/math] или является степенью двойки. Если число [math]x[/math] является степенью двойки, то в двоичной системе счисления оно представляется следующим образом: [math]1\underbrace_[/math] , где [math]n[/math] — показатель степени. Соответственно, выражение [math](x — 1)[/math] будет иметь вид [math]\underbrace_[/math] , и [math]x\ \&\ (x — 1)[/math] равно [math]0[/math] .
Операция логического И в данном выражении отсекает тот случай, когда [math](x = 0)[/math] и не является степенью двойки, но при этом правая часть [math](!(x\ \&\ (x — 1)))[/math] равна единице.
Нахождение младшего единичного бита
Пусть дано число [math]x[/math] и необходимо узнать его младший единичный бит.
Применим к числу [math]x[/math] побитовое отрицание, чтобы инвертировать значения всех его бит, а затем прибавим к полученному числу единицу. У результата первая часть (до младшего единичного бита) не совпадает с исходным числом [math]x[/math] , а вторая часть совпадает. Применив побитовое И к этим двум числам, получим степень двойки, соответствующую младшему единичному биту исходного числа [math](x\ \&\ (\sim x + 1))[/math] .
К такому же результату можно прийти, если сначала отнять от числа [math]x[/math] единицу, чтобы обнулить его младший единичный бит, а все последующие разряды обратить в [math]1[/math] , затем инвертировать результат и применить побитовое И с исходным числом [math](x\ \&\ \sim (x — 1))[/math] .
Нахождение старшего единичного бита
Пусть дано число [math]x[/math] и необходимо узнать его старший единичный бит.
Рассмотрим некоторое число, представим его как [math]0\dots01b \dots b[/math] , где [math]b[/math] — любое значение бита. Тогда, если совершить битовый сдвиг этого числа вправо на [math]1[/math] и произвести побитовое ИЛИ результата сдвига и исходного числа, мы получим результат [math]0\dots011b \dots b[/math] . Если мы повторим эту последовательность действий над полученным числом, но устроим сдвиг на [math]2[/math] , то получим [math]0\dots01111b \dots b[/math] . При каждой следующей операции будем увеличивать модуль сдвига до следующей степени двойки. После некоторого количества таких операций (зависит от разрядности числа) мы получим число вида [math]0\dots01\dots1[/math] . Тогда результатом выполнения действий [math]x — (x \texttt< \gt \gt >1)[/math] будет число, состоящее только из старшего бита исходного числа.
int32 greatestBit(x: int32): power = 1 for i = 1
: x |= x >> power power return x - (x >> 1)Циклический сдвиг
Пусть дано число [math]x[/math] и надо совершить циклический сдвиг его битов на величину [math]d[/math] . Желаемый результат можно получить, если объединить числа, полученные при выполнении обычного битового сдвига в желаемую сторону на [math]d[/math] и в противоположном направлении на разность между разрядностью числа и величиной сдвига. Таким образом, мы сможем поменять местами начальную и конечную части числа.
int32 rotateLeft(x, d: int32): return (x >> (32 - d)) int32 rotateRight(x, d: int32): return (x >>> d) | (xПодсчет количества единичных битов
Для подсчета количества единичных битов в числе [math]x[/math] можно воспользоваться следующим алгоритмом:
// Для чисел других разрядностей необходимо использовать соответствующие константы. int16 setBitsNumber(x: int16): x = x - ((x >>> 1) & 0x5555) x = (x & 0x3333) + ((x >>> 2) & 0x3333) x = (x + (x >>> 4)) & 0x0F0F return (x * 0x0101) >>> 8Поскольку [math]5555_[/math] равно [math]01010101 01010101_[/math] , результатом операции [math]x\ \&\ 5555_[/math] является число, в котором все нечетные биты соответствуют нечетным битам числа [math]x[/math] . Аналогично, результатом операции [math](x\ \texttt<\gt \gt \gt >\ 1)\ \&\ 5555_[/math] является число, в котором все нечетные биты соответствуют четным битам [math]x[/math] . Четные биты результата в обоих случаях равны нулю.
Мысленно разобьем двоичную запись нашего числа [math]x[/math] на группы по [math]2[/math] бита. Результатом операции [math]x\ \&\ 5555_ + (x\ \texttt<\gt \gt \gt >\ 1)\ \&\ 5555_[/math] будет такое число, что если разбить его двоичную запись на группы по два бита, значение каждой группы соответствует количеству единичных битов в соответствующей паре битов числа [math]x[/math] .
Аналогично, число [math]3333_[/math] равно [math]00110011 00110011_[/math] и операция [math]x = (x\ \&\ 3333_) + (x\ \texttt<\gt \gt \gt >\ 2\ \&\ 3333_)[/math] , примененная к результату, полученному на первом этапе, выполняет подсчет количества единичных битов в блоках по [math]4[/math] . В свою очередь, число [math]\texttt_[/math] равно [math]00001111 00001111_[/math] и операция [math]x = (x\ \&\ \texttt_) + (x\ \texttt<\gt \gt \gt >\ 4\ \&\ \texttt_)[/math] позволяет подсчитать число единичных бит в блоках по [math]8[/math] .
Теперь необходимо просуммировать числа, записанные в блоках по [math]8[/math] битов, чтобы получить искомую величину. Это можно сделать, домножив результат на [math]0101_[/math] [math](1 00000001_)[/math] . Ответ на задачу будет находиться в первых восьми битах произведения. Выполнив сдвиг вправо на [math]8[/math] (для шестнадцатибитных чисел), мы получим долгожданный ответ.
int16 setBitsNumber(x: int16): x = (x & 0x5555) + ((x >>> 1) & 0x5555) x = (x & 0x3333) + ((x >>> 2) & 0x3333) x = (x & 0x0F0F) + ((x >>> 4) & 0x0F0F) return (x * 0x0101) >>> 8Заметим, что операция [math]x\ \&\ 55_ + (x\ \texttt<\gt \gt \gt >\ 1)\ \&\ 55_[/math] равносильна операции [math]x - (x\ \texttt<\gt \gt \gt >\ 1)\ \&\ 55_[/math] , в чем легко убедиться, рассмотрев все числа из двух бит.
В свою очередь, операцию [math](x\ \&\ \texttt_) + ((x\ \texttt<\gt \gt \gt >\ 4)\ \&\ \texttt_)[/math] можно заменить на [math](x + (x\ \texttt<\gt \gt \gt >\ 4))\ \&\ \texttt_[/math] . Эта замена не повлияет на результат, так как максимальное значение в любой группе из четырех битов данного числа равно четырем, то есть требует только трех битов для записи, и выполнение суммирования не повлечет за собой переполнения и выхода за пределы четверок.
Таким образом, мы получили код, приведенный в начале раздела.
Разворот битов
Чтобы получить биты числа [math]x[/math] , записанные в обратном порядке, применим следующий алгоритм.
// Для чисел других разрядностей нужны соответствующие константы. int16 reverseBits(x: int16): x = ((x & 0x5555) >> 1) & 0x5555) // Четные и нечетные биты поменялись местами. x = ((x & 0x3333) >> 2) & 0x3333) // Биты "перетасовываются" группами по два. x = ((x & 0x0F0F) >> 4) & 0x0F0F) // Биты "перетасовываются" группами по четыре. x = ((x & 0x00FF) >> 8) & 0x00FF) // Биты "перетасовываются" группами по восемь. return xБолее подробно про то, что за константы выбраны для данного алгоритма, можно прочитать в разделе подсчет количества единичных битов.
Применение для решения задач
Работа с битовыми масками
Для работы с подмножествами удобно использовать битовые маски. Применяя побитовые операции легко сделать следующее: найти дополнение [math](\sim mask)[/math] , пересечение [math](mask_1\ \&\ mask_2)[/math] , объединение [math](mask_1 \mid mask_2)[/math] множеств, установить бит по номеру [math](mask \mid (1\ \texttt<\lt \lt >\ x))[/math] , снять бит по номеру [math](mask\ \&\ \sim(1\ \texttt<\lt \lt >\ x))[/math] .
Битовые маски используются, например, при решении некоторых задач [1] динамического программирования.
Алгоритм Флойда
Основная статья: Алгоритм Флойда
Алгоритм Флойда–Уоршелла (англ. the Floyd–Warshall algorithm) — алгоритм для нахождения длин кратчайших путей между всеми парами вершин во взвешенном ориентированном графе. Работает корректно, если в графе нет циклов отрицательной величины, а если же такой цикл есть, позволяет найти хотя бы один такой цикл. Асимптотическая сложность алгоритма [math] \Theta(n^3) [/math] , также требует [math] \Theta(n^2) [/math] памяти.
Дерево Фенвика
Основная статья: Дерево Фенвика
Дерево Фенвика (англ. Binary indexed tree) — структура данных, которая может выполнять следующие операции:
- изменять значение любого элемента в массиве,
- выполнять некоторую ассоциативную, коммутативную, обратимую операцию [math] \circ [/math] на отрезке [math] [i, j] [/math] .
Данная структура требует [math] O(n) [/math] памяти, а выполнение каждой операции происходит за [math] O(\log n) [/math] .
Функция, позволяющая делать операции вставки и изменения элемента за [math] O(\log n) [/math] , задается следующей формулой [math] F(i) = (i \And (i + 1)) [/math] . Пусть дан массив [math] A = [a_0, a_1, \ldots, a_][/math] . Деревом Фенвика называется массив [math] T [/math] из [math] n [/math] элементов: [math] T_i = \sum\limits_^ a_k[/math] , где [math] i = 0\ldots n - 1 [/math] и [math] F(i) [/math] — функция, которую мы определили ранее.
См. также
- Определение булевой функции
- Сумматор
- Триггеры
Примечания
Источники информации
- Онлайн справочник программиста на С и С++
- Побитовые операторы
- Bit Twiddling Hacks by Sean Eron Anderson
- Habrahabr — Алгоритмы поиска старшего бита
- STP's blog — Counting the number of set bits in an integer
Арифметические операторы (справочник по C#)
Следующие операторы выполняют арифметические операции с операндами числовых типов:
- унарные — ++ (приращение), -- (уменьшение), + (плюс) и - (минус);
- бинарные — * (умножение), / (деление), % (остаток от деления), + (сложение) и - (вычитание).
Эти операторы поддерживаются всеми целочисленными типами и типами с плавающей запятой.
В случае целочисленных типов эти операторы (за исключением операторов ++ и -- ) определяются для типов int , uint , long и ulong . Если операнды принадлежат к другим целочисленным типам ( sbyte , byte , short , ushort или char ), их значения преобразуются в тип int , который также является типом результата операции. Если операнды принадлежат к разным целочисленным типам или типам с плавающей запятой, их значения преобразуются в ближайший содержащий тип, если такой тип существует. Дополнительные сведения см. в разделе Числовые повышения уровня в статье Спецификации языка C#. Операторы ++ и -- определяются для всех целочисленных числовых типов и числовых типов с плавающей запятой, а также типа char. Тип результата выражения сложного назначения является типом левого операнда.
Оператор инкремента ++
Оператор инкремента ++ увеличивает операнд на 1. Операндом должна быть переменная, свойство или индексатор.
Оператор инкремента поддерживается в двух формах: постфиксный оператор инкремента ( x++ ) и префиксный оператор инкремента ( ++x ).
Постфиксный оператор приращения
Результатом x++ является значение x перед выполнением операции, как показано в следующем примере:
int i = 3; Console.WriteLine(i); // output: 3 Console.WriteLine(i++); // output: 3 Console.WriteLine(i); // output: 4
Префиксный оператор инкремента
Результатом ++x является значение x после выполнения операции, как показано в следующем примере:
double a = 1.5; Console.WriteLine(a); // output: 1.5 Console.WriteLine(++a); // output: 2.5 Console.WriteLine(a); // output: 2.5
Оператор декремента --
Унарный оператор декремента -- уменьшает операнд на 1. Операндом должна быть переменная, свойство или индексатор.
Оператор декремента поддерживается в двух формах: постфиксный оператор декремента ( x-- ) и префиксный оператор декремента ( --x ).
Постфиксный оператор уменьшения
Результатом x-- является значение x перед выполнением операции, как показано в следующем примере:
int i = 3; Console.WriteLine(i); // output: 3 Console.WriteLine(i--); // output: 3 Console.WriteLine(i); // output: 2
Префиксный оператор декремента
Результатом --x является значение x после выполнения операции, как показано в следующем примере:
double a = 1.5; Console.WriteLine(a); // output: 1.5 Console.WriteLine(--a); // output: 0.5 Console.WriteLine(a); // output: 0.5
Операторы унарного плюса и минуса
Унарный оператор + возвращает значение полученного операнда. Унарный оператор - изменяет знак операнда на противоположный.
Console.WriteLine(+4); // output: 4 Console.WriteLine(-4); // output: -4 Console.WriteLine(-(-4)); // output: 4 uint a = 5; var b = -a; Console.WriteLine(b); // output: -5 Console.WriteLine(b.GetType()); // output: System.Int64 Console.WriteLine(-double.NaN); // output: NaN
Тип ulong не поддерживает унарный оператор - .
Оператор умножения *
Оператор умножения * вычисляет произведение операндов:
Console.WriteLine(5 * 2); // output: 10 Console.WriteLine(0.5 * 2.5); // output: 1.25 Console.WriteLine(0.1m * 23.4m); // output: 2.34
Оператор деления /
Оператор деления / делит левый операнд на правый.
Деление целых чисел
Для операндов цельночисленных типов результат оператора / является целочисленным типом, который равен частному двух операндов, округленному в сторону нуля:
Console.WriteLine(13 / 5); // output: 2 Console.WriteLine(-13 / 5); // output: -2 Console.WriteLine(13 / -5); // output: -2 Console.WriteLine(-13 / -5); // output: 2
Чтобы получить частное двух операндов в виде числа с плавающей запятой, используйте тип float , double или decimal :
Console.WriteLine(13 / 5.0); // output: 2.6 int a = 13; int b = 5; Console.WriteLine((double)a / b); // output: 2.6
Деление чисел с плавающей запятой
Для типов float , double и decimal результатом оператора / является частное двух операндов:
Console.WriteLine(16.8f / 4.1f); // output: 4.097561 Console.WriteLine(16.8d / 4.1d); // output: 4.09756097560976 Console.WriteLine(16.8m / 4.1m); // output: 4.0975609756097560975609756098
Если один из операндов — это decimal , второй операнд не может быть ни float , ни double , так как ни float , ни double не преобразуется неявно в тип decimal . Необходимо явным образом преобразовать операнд float или double в тип decimal . Дополнительные сведения о числовых преобразованиях см. в разделе Встроенные числовые преобразования.
Оператор остатка %
Оператор остатка % вычисляет остаток от деления левого операнда на правый.
Целочисленный остаток
Для целочисленных операндов результатом a % b является значение, произведенное a - (a / b) * b . Знак ненулевого остатка такой же, как и у левого операнда, как показано в следующем примере:
Console.WriteLine(5 % 4); // output: 1 Console.WriteLine(5 % -4); // output: 1 Console.WriteLine(-5 % 4); // output: -1 Console.WriteLine(-5 % -4); // output: -1
Используйте метод Math.DivRem для вычисления результатов как целочисленного деления, так и определения остатка.
Остаток с плавающей запятой
Для операндов типа float и double результатом x % y для конечных x и y будет значение z , так что:
- знак z , если отлично от нуля, совпадает со знаком x ;
- абсолютное значение z является значением, произведенным |x| - n * |y| , где n — это наибольшее возможное целое число, которое меньше или равно |x| / |y| , а |x| и |y| являются абсолютными значениями x и y , соответственно.
Этот метод вычисления остатка аналогичен тому, который использовался для целочисленных операндов, но отличается от спецификации IEEE 754. Если вам нужна операция вычисления остатка, которая соответствует спецификации IEEE 754, используйте метод Math.IEEERemainder.
Сведения о поведение оператора % в случае неконечных операндов см. в разделе Оператор остаткаспецификации языка C#.
Для операндов decimal оператор остатка % эквивалентен оператору остатка типа System.Decimal.
В следующем примере показано поведение оператора остатка для операндов с плавающей запятой:
Console.WriteLine(-5.2f % 2.0f); // output: -1.2 Console.WriteLine(5.9 % 3.1); // output: 2.8 Console.WriteLine(5.9m % 3.1m); // output: 2.8
Оператор сложения +
Оператор сложения + вычисляет сумму своих операндов:
Console.WriteLine(5 + 4); // output: 9 Console.WriteLine(5 + 4.3); // output: 9.3 Console.WriteLine(5.1m + 4.2m); // output: 9.3
Кроме того, оператор + можно использовать для объединения строк и делегатов. Дополнительные сведения см. в статье Операторы + и += .
Оператор вычитания -
Оператор вычитания - вычитает правый операнд из левого:
Console.WriteLine(47 - 3); // output: 44 Console.WriteLine(5 - 4.3); // output: 0.7 Console.WriteLine(7.5m - 2.3m); // output: 5.2
Кроме того, оператор - можно использовать для удаления делегатов. Дополнительные сведения см. в статье Операторы - и -= .
Составное присваивание
Для бинарного оператора op выражение составного присваивания в форме
x op= y
x = x op y
за исключением того, что x вычисляется только один раз.
Следующий пример иллюстрирует использование составного присваивания с арифметическими операторами:
int a = 5; a += 9; Console.WriteLine(a); // output: 14 a -= 4; Console.WriteLine(a); // output: 10 a *= 2; Console.WriteLine(a); // output: 20 a /= 4; Console.WriteLine(a); // output: 5 a %= 3; Console.WriteLine(a); // output: 2
Из-за восходящих приведений результат операции op может быть невозможно неявно преобразовать в тип T из x . В этом случае, если op является предопределенным оператором, и результат операции является явно преобразуемым в тип T x , выражение составного присваивания формы x op= y эквивалентно x = (T)(x op y) , за исключением того, что x вычисляется только один раз. В следующем примере продемонстрировано такое поведение.
byte a = 200; byte b = 100; var c = a + b; Console.WriteLine(c.GetType()); // output: System.Int32 Console.WriteLine(c); // output: 300 a += b; Console.WriteLine(a); // output: 44
В предыдущем примере значение 44 является результатом преобразования значения 300 в byte тип .
Вы также можете использовать операторы += и -= для подписки и отмены подписки на события соответственно. Дополнительные сведения см. в разделе Практическое руководство. Подписка и отмена подписки на события.
Приоритет и ассоциативность операторов
В следующем списке перечислены арифметические операторы в порядке убывания приоритета:
- Постфиксный инкремент x++ и декремент x--
- Префиксный инкремент ++x и декремент --x , унарные операторы + и -
- Мультипликативные операторы * , / , и %
- Аддитивные операторы + и -
Бинарные арифметические операторы имеют левую ассоциативность. То есть операторы с одинаковым приоритетом вычисляются в направлении слева направо.
Порядок вычисления, определяемый приоритетом и ассоциативностью операторов, можно изменить с помощью скобок ( () ).
Console.WriteLine(2 + 2 * 2); // output: 6 Console.WriteLine((2 + 2) * 2); // output: 8 Console.WriteLine(9 / 5 / 2); // output: 0 Console.WriteLine(9 / (5 / 2)); // output: 4
Полный список операторов C#, упорядоченный по уровню приоритета, можно найти в разделе Приоритет операторов статьи Операторы C#.
Арифметическое переполнение и деление на нуль
Если результат арифметической операции выходит за пределы диапазона возможных конечных значений соответствующего числового типа, поведение арифметического оператора зависит от типа его операндов.
Целочисленное арифметическое переполнение
Деление целого числа на ноль всегда вызывает исключение DivideByZeroException.
В случае целочисленного арифметического переполнения итоговое поведение определяется проверяемым или непроверяемым контекстом проверки переполнения:
- Если в проверяемом контексте переполнение возникает в константном выражении, происходит ошибка времени компиляции. В противном случае, если операция производится во время выполнения, возникает исключение OverflowException.
- В непроверяемом контексте результат усекается путем удаления старших разрядов, которые не помещаются в целевой тип данных.
Вместе с проверяемыми и непроверяемыми операторами можно использовать операторы checked и unchecked , чтобы управлять контекстом проверки переполнения, в котором вычисляется выражение:
int a = int.MaxValue; int b = 3; Console.WriteLine(unchecked(a + b)); // output: -2147483646 try < int d = checked(a + b); >catch(OverflowException) < Console.WriteLine($"Overflow occurred when adding to ."); >
По умолчанию арифметические операции выполняются в непроверяемом контексте.
Арифметическое переполнение с плавающей запятой
Арифметические операции с типами float и double никогда не вызывают исключение. Результатом арифметических операций с этими типами может быть одно из специальных значений, представляющих бесконечность и объект, не являющийся числовым:
double a = 1.0 / 0.0; Console.WriteLine(a); // output: Infinity Console.WriteLine(double.IsInfinity(a)); // output: True Console.WriteLine(double.MaxValue + double.MaxValue); // output: Infinity double b = 0.0 / 0.0; Console.WriteLine(b); // output: NaN Console.WriteLine(double.IsNaN(b)); // output: True
Для операндов типа decimal арифметическое переполнение всегда выдает исключение OverflowException, а деление на нуль — DivideByZeroException.
Ошибки округления
Из-за общих ограничений, касающихся представления вещественных чисел в форме с плавающей запятой и арифметических операций с плавающей запятой, при вычислениях с использованием типов с плавающей запятой могут возникать ошибки округления. То есть полученный результат выражения может отличаться от ожидаемого математического результата. В следующем примере показано несколько таких случаев:
Console.WriteLine(.41f % .2f); // output: 0.00999999 double a = 0.1; double b = 3 * a; Console.WriteLine(b == 0.3); // output: False Console.WriteLine(b - 0.3); // output: 5.55111512312578E-17 decimal c = 1 / 3.0m; decimal d = 3 * c; Console.WriteLine(d == 1.0m); // output: False Console.WriteLine(d); // output: 0.9999999999999999999999999999
См. заметки в справочной документации по System.Double, System.Single и System.Decimal.
Возможность перегрузки оператора
Определяемый пользователем тип может перегружать унарные ( ++ , -- , + и - ) и бинарные ( * , / , % , + и - ) арифметические операторы. При перегрузке бинарного оператора соответствующий оператор составного присваивания также неявно перегружается. Явная перегрузка составного оператора присваивания для пользовательского типа невозможна.
Проверенные операторы, определяемые пользователем
Начиная с C# 11 при перегрузке арифметического оператора можно использовать ключевое слово checked , чтобы определить проверенную версию этого оператора. Следующий пример показывает, как это сделать:
public record struct Point(int X, int Y) < public static Point operator checked +(Point left, Point right) < checked < return new Point(left.X + right.X, left.Y + right.Y); >> public static Point operator +(Point left, Point right) < return new Point(left.X + right.X, left.Y + right.Y); >>
При определении проверенного оператора следует также определить соответствующий оператор без модификатора checked . Проверенный оператор вызывается в проверенном контексте. Оператор без модификатора checked вызывается в непроверенном контексте. Если указать только оператора без модификатора checked , он будет вызываться как в контексте checked , так и в unchecked .
При определении обеих версий оператора их поведение будет различаться только в том случае, если результат операции слишком велик, чтобы представить его тип следующим образом:
- Проверенный оператор вызывает OverflowException.
- Оператор без модификатора checked возвращает экземпляр, который представляет усеченный результат.
Сведения о разнице в поведении встроенных арифметических операторов см. в разделе Арифметическое переполнение и деление на ноль.
Модификатор checked можно использовать только при перегрузке следующих операторов:
- Унарные операторы ++ , -- и -
- Бинарные операторы * , / , + и -
- Операторы явного преобразования
Контекст проверки переполнения в теле проверенного оператора не изменяется при наличии модификатора checked . Контекст по умолчанию определяется значением параметра компилятора CheckForOverflowUnderflow. С помощью операторов checked и unchecked можно явно указать контекст проверки переполнения, как показано в примере в начале этого раздела.
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:
- Постфиксные операторы инкремента и декремента
- Префиксные операторы инкремента и декремента
- Оператор унарного плюса
- Оператор унарного минуса
- Оператор умножения
- Оператор деления
- Оператор остатка
- Оператор сложения
- Оператор вычитания
- Составное присваивание
- Операторы checked и unchecked
- Восходящие приведения числовых типов
См. также раздел
- справочник по C#
- Операторы и выражения C#
- System.Math
- System.MathF
- Числовые значения в .NET
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
The .NET documentation is open source. Provide feedback here.
Обратная связь
Отправить и просмотреть отзыв по
Div, Mod, сложение, вычитание в Pascal.
Помимо операций умножения и сложения, над целочисленными переменными производятся операции Div (операция целочисленного деления), Mod (вычисление остатка от целочисленного деления), операция вычитания «-».
program number7; uses crt; var A,B,C: integer; begin clrscr; A:=17; B:=3; C:=A div B; writeln ('17 div 3 = ',C); C:=A mod B; writeln ('17 mod 3 = ',C); C:=A-B; writeln ('17-3 =',C); readln end.
В строке №8 используется операция Div. Это операция деления нацело, т. е. в результате получится целое число без остатка. Если 17 разделить на 3, то получится 5,66666… Здесь - целое число 5, оно и будет результатом операции div.
Строка №10. Операция Mod вычисляет остаток от целочисленного деления. Если мы разделим 17 на 3, то получится 5 – целое число и 2 – остаток. Таким образом, результатом операции Mod будет число 2
Теперь запишем нашу программу в Паскале и и запустим ее.
Дополнение к материалу.
Правила приоритета при выполнении операций в Паскале.
- Сначала выполняются действия над переменными, которые стоят в скобках.
- Затем вычисляются функции.
- Затем умножение и деление.
- Затем сложение и вычитание.
Операции, которые имеют одинаковый приоритет (сложение и вычитание, деление и умножение) выполняются слева направо, т.е. в порядке очереди.
Комментарии
+9 # Irzorg 04.04.2012 17:46
помогите решить задачу 1811 mod 32 = 19! Как получается этот ответ, все испробывал, не могу получить!
+2 # Евгений 09.01.2015 18:56
Достаточно в выше показанной программе переменную А определить как 1811,а переменную В как 32 то переменная С (C:=A mod B) и определит искомые 19.
+67 # Administrator 05.04.2012 08:29
Делишь 1811 на 32, получается 56,59375. Из этого числа берем целое число, т.е. 56 и умножаем его на 32. Получится 1792. 1811-1792=19 Это и есть остаток от целочисленного деления.
+9 # Евгений 09.01.2015 13:08
Этому можно придать и иной смысл. Достато чно вспомнить мультфильм "тридцать восемь попугаев. Числ о,которым делят,становить ся мерой числа которое делят. И остаток 0.59375 выражает часть числа 32. Так что достаточно умножить 32 на 0.59375 что бы получилось искомые 19.
-181 # яю 23.04.2012 12:14
Забудь про паскаль.
И задачу тебе не нужно решать.
А если хочешь научиться, то начинай с Ассемблера.
+39 # Нео 02.12.2012 13:08
-27 оценка.
вижу никому не нравиться Ассамблер
+30 # Антон 23.02.2013 14:00
Ну просто обсуждается именно паскаль, а не ассемблер, поэтому и минусуют, что не по теме
+6 # alex 30.06.2012 14:04
🙂
8 строка: C := trunc(A/B);
10 cстрока: C := A - B * trunc(A/B);
-8 # помогите 28.01.2013 19:21
помогите решить задачу:дано 2 значное число,переверни те его справа налево.Допучсти м 32 ответ должен быть 23. или дано 78 ответ 87 заранее спосибо. )
+4 # Антон 23.02.2013 14:35
Program pr1;
var
x, y1, y2, y: integer;
begin
write ('Введите двухзначное число: ');
readln (x);
y1:=(x mod 10)*10;
y2:=x div 10;
y:=y1+y2;
write ('Число-перевёр тыш: ', y);
readln;
end.
-5 # Вадим 22.11.2015 06:30
А не легче в 7,8 и 9 строках сделать так: y1:=x div 10; y2:=x mod 10; writeln (y2,y1);
+4 # VladiMir 08.04.2015 19:38
uses
SysUtils;
var a,b: integer;
n: word;
begin
readln(n);
a:=n mod 10;
n:= n div 10;
b:=n mod 10;
n:= n div 10;
n:=a*10+b;
writeln(n);
readln;
-14 # dim 16.02.2013 12:43
program prog1;
uses crt;
begin
write ('введите двухзначное число. ');
read(x);
z:=10;
y:=x div z;
c:=x mod z;
writeln (c,y);
readln
end.
0 # иришка* 18.02.2013 12:38
ребяята подскажите пожалуйста !очень надо как из числа 5264 получить число 2 типа третье от конца число помогите пожалуйста
-5 # Антон 23.02.2013 12:18
Program pr1;
var
x, y: integer;
begin
write ('Введите число: ');
readln (x); //вводим 5264
y:=(x div 100)-(((x div 100) div 10)*10);
write ('Третья цифра справа ', y);
end.
Программа всегда будет говорить, какая цифра стоит третьей
-4 # Антон 23.02.2013 13:58
Кстати, перед "end." надо добавить "readln;", а то после вывода результата программа тут же закроется
0 # Виталий 16.11.2017 13:31
a:=a div 10;
b:=a div 10;
a:=a div 10;
b:=a mod 10;
writeln(b);
0 # Alexandr 19.02.2013 11:27
# Irzorg
Вот тебе схема как это делать в дальнейшем:
Допустим,что нам даны два числа x и y;
1)X mod Y=F;-Нам нужно узнать,что же за число получится при вычислении остатка от целочисленного деления;
2)X div Y=Z;Z-это результат целочисленного деления;
3)Z*Y=G;G-число,которое получилось в результате умножения результата целочисленного деления на число Y(Y дан нам в исходном выражении);
4)X-G=F;Где F конечный результат.
Надеюсь,что помог тебе.
-4 # ivan 20.03.2013 16:32
подсчитал все значения,на экран нужно вывести каждое двадцатое число,подскажит е пожалуйста
+1 # katya 21.03.2013 17:25
помогите решить задачу.
дано два цэлых числа: A, B. проверить истинное ли выражение " Каждое ис чисел А,В - непарное"
+2 # Casil 16.03.2018 10:40
Boolean4. Даны два целых числа: A, B. Проверить истинность высказывания: «Справедливы неравенства A > 2 и B ≤ 3».
var
A, B: integer;
begin
write('Введите A и B: ');
read(A,B);
writeln('Справе дливы неравенства A > 2 и B ≤ 3 - ',(A > 2) and (B = 0) or (B дливо двойное неравенство A B) and (B > C)));
end.
Boolean8. Даны два целых числа: A, B. Проверить истинность высказывания: «Каждое из чисел A и B нечетное».
var
A, B: integer;
begin
write('Введите A, B: ');
read(A,B);
writeln('Каждое из чисел A и B нечетное - ', (A mod 2 0) and (B mod 2 0));
end.
Boolean9. Даны два целых числа: A, B. Проверить истинность высказывания: «Хотя бы одно из чисел A и B нечетное».
var
A, B: integer;
begin
write('Введите A, B: ');
read(A,B);
writeln('Хотя бы одно из чисел A и B нечетное - ', (A mod 2 0) or (B mod 2 0));
end.
Boolean10°. Даны два целых числа: A, B. Проверить истинность высказывания: «Ровно одно из чисел A и B нечетное».
var
A, B: integer;
begin
write('Введите A, B: ');
read(A,B);
writeln('Хотя бы одно из чисел A и B нечетное - ', (A mod 2 0) xor (B mod 2 0));
end.
Boolean11. Даны два целых числа: A, B. Проверить истинность высказывания: «Числа A и B имеют одинаковую четность».
var
A, B: integer;
C: boolean;
begin
write('Введите A, B: ');
read(A,B);
C := ((A mod 2 0) and (B mod 2 0)) or ((A mod 2 = 0) and (B mod 2 = 0));
writeln('Числа A и B имеют одинаковую четность - ', C);
end.
Boolean12. Даны три целых числа: A, B, C. Проверить истинность высказывания: «Каждое из чисел A, B, C положительное».
var
A, B, C: integer;
begin
write('Введите A, B и C: ');
read(A,B,C);
writeln('Каждое из чисел A, B, C положительное - ',(A > 0) and (B > 0) and (C > 0));
end.
Операции и выражения
Чтобы понять, какое значение имеют в языке MQL4 операции и выражения, не требуется никаких особых аналогий. Практически это - то же самое, что операции и выражения в простой арифметике. Каждому человеку понятно, что в записи f = n + m элементы f, n и m являются переменными, значки = и + являются знаками операций, а n + m - выражением.
В предыдущем параграфе мы познакомились с необходимостью представления разных типов данных. Теперь нам предстоит разобраться, в каких отношениях между собой эти данные могут находиться (квадратные метры по-прежнему нельзя складывать с яблоками). В MQL4 имеются естественные ограничения и правила использования операций в выражениях.
Понятия "операнд", "операция", "знак операции" и "выражение"
Операнд - это константа, переменная, элемент массива или значение, возвращаемое функцией (понятие "функция" рассматривается в разделе Функции, понятие "массив" - в разделе Массивы; на данном этапе обучения достаточно понимать под операндами уже известные нам константы и переменные).
Операция - это действие, производимое над операндами.
Знак операции - предопределённый символ или группа символов, предписывающие выполнить некоторую операцию.
Выражение - последовательность операндов и знаков операций - запись в программе, вычисленное значение которой характеризуется типом данных.
Виды операций
В языке MQL4 различают следующие виды операций:
- арифметические операции;
- операции присваивания;
- операции отношения;
- логические операции;
- побитовые операции;
- операция запятая;
- вызов функции.
Операции применяются в операторах (см. Операторы), только при этом их использование имеет смысл и реализуется в программе. Возможность использования операций определяется свойствами операторов (если свойства оператора позволяют применять конкретную операцию, то её можно использовать, в противном случае использовать данную операцию нельзя). Использование операций за пределами операторов запрещено.
Арифметические операции
К символам арифметических операций относятся следующие:
Символ | Операция | Пример | Аналог |
---|---|---|---|
+ | Сумма величин | x + 2 | |
- | Вычитание величин или изменение знака | x - 3, y = - y | |
* | Умножение величин | 3 * x | |
/ | Частное от деления | x / 5 | |
% | Остаток от деления | minutes = time % 60 | |
++ | Добавление 1 к значению переменной | y++ | y = y + 1 |
-- | Вычитание 1 из значения переменной | y-- | y = y - 1 |
Операции присваивания
К символам операций присваивания относятся следующие:
Символ | Операция | Пример | Аналог |
---|---|---|---|
= | Присваивание значения x переменной y | у = x | |
+= | Увеличение значения переменной у на x | у += x | y = y + x |
-= | Уменьшение значения переменной y на x | y -= x | y = y - x |
*= | Умножение значения переменной y на x | y *= x | y = y * x |
/= | Деление значения переменной y на x | y /= x | y = y / x |
%= | Остаток от деления значения переменной y на x | y %= x | y = y % x |
Операции отношения
К символам операций отношения относятся следующие:
Символ | Операция | Пример |
---|---|---|
== | Истина, если x равно y | x == y |
!= | Истина, если x не равно y | x != y |
Истина, если x меньше y | x < y | |
> | Истина, если x больше y | x > y |
Истина, если x меньше или равно y | x | |
>= | Истина, если x больше или равно y | x >= y |
Логические операции
К символам логических операций относятся следующие:
Символ | Операция | Пример | Пояснения |
---|---|---|---|
! | НЕ (логическое отрицание) | ! х | ИСТИНА(1), если значение операнда ЛОЖЬ(0), и ЛОЖЬ(0), если значение операнда не ЛОЖЬ(0). |
|| | ИЛИ (логическое ИЛИ) | x < 5 || x >7 | ИСТИНА(1), если истинно любое из значений |
&& | И (логическое И) | x == 3 && y < 5 | ИСТИНА(1), если истинны все значения |
Побитовые операции
Побитовые операции выполняются только с целыми числами. К побитовым операциям относятся следующие:
Дополнение значения переменной до единицы. Значение выражения содержит 1 во всех разрядах, в которых значения переменной содержат 0, и 0 во всех разрядах, в которых значения переменной содержат 1.
Двоичное представление x сдвигается вправо на y разрядов. Сдвиг вправо логический, то есть освобождающиеся слева разряды будут заполняться нулями.
Двоичное представление x сдвигается влево на y разрядов; освобождающиеся справа разряды заполняются нулями.
Побитовая операция И двоичных представлений x и y. Значение выражения содержит 1 (ИСТИНА) во всех разрядах, в которых и x, и y содержат не ноль; и 0 (ЛОЖЬ) во всех остальных разрядах.
Побитовая операция ИЛИ двоичных представлений x и y. Значение выражения содержит 1 во всех разрядах, в которых x или y не содержат 0, и 0 - во всех остальных разрядах.
Побитовая операция "исключающее ИЛИ" (eXclusive OR) двоичных представлений x и y. Значение выражения содержит 1 в тех разрядах, в которых x и y имеют разные двоичные значения, и 0 - во всех остальных разрядах.
Операция запятая
Выражения, разделённые запятыми, вычисляются слева направо. Все побочные эффекты вычисления левого выражения могут возникать до вычисления правого выражения. Тип и значение результата совпадают с типом и значением правого выражения.
for(i=0,j=99; i |
В качестве примера можно рассматривать список передаваемых параметров (будет рассмотрено далее).
My_function (Alf, Bet, Gam, Del) // Вызов функции с аргументами |
Операторы и функции рассматриваются в разделах Операторы, Функции и в главе Операторы.
Вызов функции
Вызов функции подробно рассматривается в разделе Вызов функции.
Вызов функции подробно рассматривается в соответствующем разделе.
Операции над однотипными операндами
Если бы ученику начальной школы сообщили, что, решая задачу о количестве карандашей, он должен строить свои рассуждения на таких понятиях, как операнды, операции и выражения, он, наверное, счёл бы, что от него хотят невозможного. Глядя на символы операций, кто-то может решить, что программирование - некий таинственный, сложный процесс, доступный только избранным. На самом деле в программировании нет ничего сложного, достаточно лишь разобраться в сущности нескольких понятий. Чтобы в этом убедиться, рассмотрим несколько примеров.
Задача 1. У Васи 2 карандаша, у Пети 3 карандаша. Сколько всего карандашей у мальчиков?:) |
Решение. Обозначим количество карандашей у Васи как переменную А, количество карандашей у Пети – как переменную В, а результат – через переменную С.
Ответ будет таким: С = А + В
В разделе Типы данных мы рассматривали варианты объявления переменных. Карандаши - это предметы, т.е. нечто такое, что в принципе может существовать в виде части (половина карандаша, например). Поэтому карандаши будем учитывать как действительные переменные, т.е. переменные типа double.
Программный код можно записать, например, так:
double A = 2.0; // Количество карандашей у Васи
double B = 3.0; // Количество карандашей у Пети
double C = A + B; // Общее количество
В данном случае показательным моментом является применение операции "+" для нахождения суммы значений однотипных переменных.
Типом значения выражения
A + B
будет тип тех переменных, которые составляют выражение, в данном случае - тип double.
Аналогичным будет ответ для разности величин (на сколько больше карандашей у Пети, чем у Васи?):
double A = 2.0; // Количество карандашей у Васи
double B = 3.0; // Количество карандашей у Пети
double C = B - A; // Разность действительных чисел
Подобным образом используются и другие арифметические операции:
double C = B * A; // Умножение действ. чисел
double C = B / A; // Деление действительных чисел
Аналогичные вычисления могут производиться и с целыми числами.
Задача 2. Ученики выходят к доске для ответа. Вася выходил 2 раза, Петя выходил 3 раза. Сколько всего раз мальчики выходили к доске? |
Решение. Обозначим количество ответов Васи как переменную Х, количество ответов Пети как переменную Y, а результат - через Z.
В этом примере необходимо использовать тип целых переменных int, т.к. мы учитываем события, являющиеся целыми по своей сути (невозможно выйти для ответа полтора или полраза; сам ответ может быть плохим или хорошим, но в данном случае нас интересует только количество ответов).
Решение задачи можно записать так:
int X = 2; // Количество ответов Васи
int Y = 3; // Количество ответов Пети
int Z = X + Y; // Общее количество
В случае вычисления разности, произведения и частного от деления целых чисел знак операции "-" используется так же просто:
int X = 2; // Целое число
int Y = 3; // Целое число
int Z = Y - X; // Разность целых чисел
int Z = Y * X; // Произведение целых чисел
int Z = Y / X; // Частное от деления целых чисел
С переменными типа string возникает несколько иная ситуация:
Задача 3. На одном углу дома расположен гастроном с названием "Северный". На другом углу дома расположено заведение под названием "Парикмахерская". Требуется определить, что написано на доме. |
Решение. В MQL4 разрешено складывать значения строковых констант и переменных. В случае сложения переменных типа string строки просто приплюсовываются друг к другу в той последовательности, в которой они упоминаются в выражении.
Нетрудно составить код программы, которая вычислила бы интересующий нас результат:
string W1 = "Северный"; // Строка 1
string W2 = "Парикмахерская"; // Строка 2
string Ans = W1 + W2; // Сумма строк
Значением переменной Ans будет строка следующего вида:
СеверныйПарикмахерская |
Получилось несколько неказистое с виду, но в полной мере правильно образованное значение строкового типа. Разумеется, для практического использования в подобных случаях необходимо предусматривать пробелы и др. знаки пунктуации.
Любые другие арифметические операции с переменными строкового типа запрещены:
string Ans= W1 - W2; // Не допускается
string Ans= W1 * W2; // Не допускается
string Ans= W1 / W2; // Не допускается
Приведение типов
Приведение типов - это изменение (преобразование) типа значения операнда или выражения. Перед выполнением операций (кроме операций присваивания) происходит преобразование в тип, имеющий наибольший приоритет, а перед операциями присваивания - в целевой тип.
Рассмотрим несколько задач, касающихся приведения типов.
Задача 4. У Васи 2 карандаша, Петя выходил для ответа 3 раза. Сколько всего? |
Некорректность этого вопроса с точки зрения формальной логики очевидна. Здравый смысл нам подсказывает, что суммировать события с предметами нельзя, что это неправильно.
Задача 5. На одном углу дома расположен гастроном с названием "Северный", а у Васи 2 карандаша.:) |
С одинаковой степенью безысходности (с точки зрения обычных рассуждений) мы можем задать любой из вопросов:
1. Сколько всего? 2. Что написано на доме?
Чтобы правильно решить две последние задачи в рамках языка MQL4, необходимо обратиться к правилам приведения типов значений. Для этого необходимо указать, как переменные разных типов представлены в памяти компьютера.
Типы данных int, bool, color, datetime и double относятся к числовому типу. Внутренним (машинным) представлением констант и переменных типов int, double, bool, color и datetime является число. При этом переменные типов int, bool, color и datetime представлены в памяти машины как целые числа, а переменные типа double - как числа двойной точности с плавающей точкой, т.е. действительные числа. Значением констант и переменных типа string является набор символов (рис. 16).
Значения типов int, bool, color и datetime представлены в памяти машины как целые числа. Значения типа double представлены в памяти машины как действительные числа. Значения типа string представлены в памяти машины как последовательность символов. Значения типов int, bool, color, datetime и double являются значениями числового типа. Значения типа string являются значениями символьного типа. |
Рис. 16. Представление разных типов данных в памяти машины.
Выше указывалось, что значения переменных типов int, bool, color и datetime представлены в памяти машины как целые числа, а double - как действительные числа. Выясняя вопрос о типе выражения, в котором участвуют переменные разных типов, допустимо вести разговор только относительно трёх типов данных: int, double и string. Значения типов bool, color и datetime будут проявлять себя в выражении так же, как значения типа int.
Итак, каким будет значение выражения, если его составляют операнды разных типов? В языке MQL4 принято правило неявного приведения типов:
Вернёмся к Задаче 4. Возможны два варианта решения.
Вариант 4.1. Вычисляется результат целого типа:
double A = 2.0; // Количество карандашей у Васи
int Y = 3; // Количество ответов Пети
int F = A + Y; // Общее количество
Прежде всего нам необходимо выяснить, каким будет значение выражения, если операнды имеют разный тип. В выражении:
A + Y
участвуют операнды двух типов данных: А - действительного типа double и Y - целого типа int.
В соответствии с правилом неявного преобразования типов значением этого выражения будет число действительного типа double. Обратите внимание: мы говорим только о типе выражения A+Y, но не о типе переменной F, стоящей слева от знака операции присвоения. Значением этого выражения будет действительное число 5.0. Для определения типа выражения A+Y применена первая часть правила неявного приведения типов.
После вычисления выражения A+Y исполняется операция присвоения. В данном случае также имеется несовпадение типов: тип выражения A+Y - double, а тип переменной F - int. В процессе выполнения операции присвоения: сначала тип выражения A+Y будет приведен к типу int (в соответствии с правилом вычисления целых чисел) и равен целому числу 5, а затем этот результат станет значением целой переменной F. Вычисления выполнены в соответствии со второй частью правила неявного приведения типов - приведения к целевому типу. Конечный результат вычислений и преобразований таков: значением целой переменной F является целое число 5.
Вариант 4.2. Аналогичная ситуация возникает и в случае, если искать результат в виде значения действительного типа:
double A = 2.0; // Количество карандашей у Васи
int Y = 3; // Количество ответов Пети
double F = A + Y; // Общее количество
Представленный случай отличается от предыдущего тем, что целевой тип переменной F (слева от знака операции присвоения), в данном случае - тип double, совпадает с типом double выражения A+Y, поэтому целевого преобразования типов не происходит. Результатом вычислений (значением действительной переменной F) будет действительное число 5.0.
Теперь посмотрим, каким будет решение Задачи 5. При инициализации переменных вопросов не возникает:
string W1 = "Северный"; // Строка 1
double A = 2; // Количество карандашей у Васи
Вариант 5.1. Допустимое решение задачи:
string W1 = "Северный"; // Строка 1
double A = 2; // Количество карандашей у Васи
string Sum = W1 + A; // Неявное преобразование справа
Здесь в правой части складываются значения двух переменных, одна из которых имеет тип string, а другая - тип double. В соответствии с правилом неявного приведения типов значение переменной А сначала будет приведено к типу string (т.к. у этого типа более высокий приоритет), а после этого произойдёт сложение (конкатенация) однотипных значений. Типом вычисленного значения в правой части от знака операции присвоения будет тип string. На следующем этапе это значение будет присвоено строковой переменной Sum. В результате значением переменной Sum будет следующая строка:
Северный2.00000000 |
Вариант 5.2. Такое решение является ошибочным:
string W1 = "Северный"; // Строка 1
double A = 2; // Количество карандашей у Васи
double Sum = W1 + A; // Это недопустимо
В данном случае нарушен запрет приведения к целевому типу значения типа string. Типом значения выражения W1+A, как и в предыдущем примере, является тип string. При исполнении операции присвоения должно быть произведено целевое приведение типа. Однако, в соответствии с правилом, целевое понижение типа string запрещено. Это - ошибка, которая будет обнаружена редактором MetaEditor на этапе создания программы (при компиляции).
В целом, указанные правила понятны и просты: при вычислении любых значений происходит приведение к типу с более высоким приоритетом. Понижение приоритета при целевом приведении типа допустимо только для численных значений, а строки в числа не преобразовываются.
Особенности вычисления значений целых чисел
Как известно, целые числа - это числа, у которых нет дробной части. При их сложении и вычитании получается интуитивно понятный результат. Например, если:
int X = 2; // Первая целая переменная
int Y = 3; // Вторая целая переменная
int Z = X + Y; // Операция сложения
то не возникает никаких проблем с вычислением значения переменной Z: 2 + 3 = 5
Аналогично, если выполняется операция умножения:
int Z = X * Y; // Операция умножения
то результат также легко предсказуем: 2 * 3 = 6
Но каким будет результат в том случае, если в программе необходимо выполнить операцию деления?
int Z = X / Y; // Операция деления
Нетрудно написать 2 / 3. Но это - не целое число. Каким же будет значение выражения X/Y и переменной Z?
Правило вычисления значений целых чисел состоит в том, что дробная часть отбрасывается. |
В этом примере справа от знака равенства находится выражение, содержащее только целые числа, т.е. в данном случае приведение типов не происходит. Это значит, что типом выражения X/Y является тип int. Поэтому результатом вычисления целого значения выражения X/Y (= 2/3) будет 0 (ноль). В результате это значение (ноль) будет присвоено переменной Z.
Соответственно, при других значениях переменных X и Y результат будет другим. Например, если:
int X = 7; // Значение переменной целого типа
int Y = 3; // Значение переменной целого типа
int Z = X / Y; // Операция деления
то значение 7 / 3 выражения X / Y и переменной Z будет равно 2 (двум).
Порядок вычисления выражений
Правило вычисления выражений состоит в следующем:
Вычисление значения выражения производится в соответствии с приоритетом арифметических операций и слева направо, причём каждый промежуточный результат вычисляется по правилам приведения типов. |
Рассмотрим порядок вычисления выражения в следующем примере:
Y = 2.0*( 3*X/Z - N) + D; // Пример выражения
Выражение в правой части от знака равенства состоит из двух слагаемых: 2.0*( 3*X/Z - N) и D. Слагаемое 2.0*( 3*X/Z - N) состоит из двух множителей, а именно, 2 и (3*X/Z - N). Выражение в скобках 3*X/Z - N, в свою очередь, состоит из двух слагаемых, причём слагаемое 3*X/Z состоит из трёх множителей, а именно, 3, X и Z.
Для вычисления значения выражения в правой части от знака равенства сначала будет вычислено значение выражения 3*X/Z. Это выражение содержит две операции (умножение и деление), имеющих одинаковый приоритет, поэтому вычисление выражения будет выполняться слева направо. Сначала будет вычислено значение выражения 3*X, причём тип этого значения будет таким, какой тип имеет переменная X. Затем будет вычислено значение выражения 3*X/Z, его тип также будет вычислен на основании правил приведения типов. После этого программа вычислит значение и тип выражения 3*X/Z - N, потом выражения 2.0*( 3*X/Z - N) и в последнюю очередь - значение и тип всего выражения 2.0*( 3*X/Z - N) + D.
Легко заметить, что порядок вычисления выражений в программе аналогичен порядку подобных вычислений в математике, но отличается вычислением типов значений промежуточных выражений, что существенно влияет на конечный результат вычислений. В частности (в отличие от правил, принятых в математике), немаловажное значение имеет порядок операндов в выражении. Чтобы продемонстрировать это, рассмотрим небольшой пример.
Задача 6. Вычислить значения выражений А/В*С и А*С/В для целых чисел А, В и С. |
Интуитивно ожидается, что результат вычислений в обоих случаях будет одинаковым. Однако это утверждение справедливо только в отношении действительных чисел. При вычислении значений выражений, составленных из операндов целого типа, очень важен промежуточный результат. В таком случае последовательность операндов имеет принципиальное значение:
int A = 3; // Значение целого типа
int B = 5; // Значение целого типа
int C = 6; // Значение целого типа
int Res_1 = A /B*C; // Результат 0 (ноль)
int Res_2 = A*C/ B; // Результат 3 (три)
Проследим процесс вычисления выражения A/B*C:
1. Сначала (слева направо) будет вычислено значение выражения A/B. В соответствии с указанными выше правилами значением выражения (3/5) будет целое значение 0 (ноль).
2. Вычисление выражения 0*С (ноль умножить на С). Результат - целое значение 0 (ноль).
3. Общий результат (значение переменной Res_1) - целое значение 0 (ноль).
Теперь посмотрим, как будут развиваться события при вычислении выражения A*C/B.
1. Вычисление A*C. Значением этого выражения будет целое число 18 (3*6=18).
2. Вычисление выражения 18/B. Ответ очевиден: (18/5) после отбрасывания дробной части получится целое число 3(три).
3. Общий результат (значение переменной Res_2) - целое значение 3 (три).
В данном примере рассмотрен небольшой фрагмент программы, в котором вычисляются значения переменных целого типа. Если эти переменные заменить константами, но использовать при этом те же их значения, то конечный результат от этого не изменится. При вычислении выражений, в которых используются целые числа, необходимо проявлять повышенное внимание к содержанию программных строк. В противном случае в программе может возникнуть ошибка, которую впоследствии (особенно в больших программах) очень трудно обнаружить. При вычислениях, в которых участвуют только действительные числа, подобная проблема не возникает. Но если в сложном выражении употребляются операнды разных типов, то конечный результат может полностью зависеть от случайно составленного фрагмента, где происходит деление целых чисел.
В разделе Операторы рассматривается понятие и общие свойства операторов, в главе Операторы раскрываются собственные свойства каждого оператора.