Базовые операторы, математика
Многие операторы знакомы нам ещё со школы: сложение + , умножение * , вычитание — и так далее.
В этой главе мы начнём с простых операторов, а потом сконцентрируемся на специфических для JavaScript аспектах, которые не проходят в школьном курсе арифметики.
Термины: «унарный», «бинарный», «операнд»
Прежде, чем мы двинемся дальше, давайте разберёмся с терминологией.
Операнд – то, к чему применяется оператор. Например, в умножении 5 * 2 есть два операнда: левый операнд равен 5 , а правый операнд равен 2 . Иногда их называют «аргументами» вместо «операндов».
Унарным называется оператор, который применяется к одному операнду. Например, оператор унарный минус "-" меняет знак числа на противоположный:
Бинарным называется оператор, который применяется к двум операндам. Тот же минус существует и в бинарной форме:
Формально, в последних примерах мы говорим о двух разных операторах, использующих один символ: оператор отрицания (унарный оператор, который обращает знак) и оператор вычитания (бинарный оператор, который вычитает одно число из другого).
Математика
Поддерживаются следующие математические операторы:
- Сложение + ,
- Вычитание — ,
- Умножение * ,
- Деление / ,
- Взятие остатка от деления % ,
- Возведение в степень ** .
Первые четыре оператора очевидны, а про % и ** стоит сказать несколько слов.
Взятие остатка %
Оператор взятия остатка % , несмотря на обозначение, никакого отношения к процентам не имеет.
Результат a % b – это остаток от целочисленного деления a на b .
Возведение в степень **
Оператор возведения в степень a ** b возводит a в степень b .
В школьной математике мы записываем это как a b .
Математически, оператор работает и для нецелых чисел. Например, квадратный корень является возведением в степень 1/2 :
Сложение строк при помощи бинарного +
Давайте рассмотрим специальные возможности операторов JavaScript, которые выходят за рамки школьной арифметики.
Обычно при помощи плюса ‘+’ складывают числа.
Но если бинарный оператор ‘+’ применить к строкам, то он их объединяет в одну:
Обратите внимание, если хотя бы один операнд является строкой, то второй будет также преобразован в строку.
Как видите, не важно, первый или второй операнд является строкой.
Вот пример посложнее:
Здесь операторы работают один за другим. Первый + складывает два числа и возвращает 4 , затем следующий + объединяет результат со строкой, производя действие 4 + ‘1’ = ’41’ .
Сложение и преобразование строк — это особенность бинарного плюса + . Другие арифметические операторы работают только с числами и всегда преобразуют операнды в числа.
Например, вычитание и деление:
Приведение к числу, унарный +
Плюс + существует в двух формах: бинарной, которую мы использовали выше, и унарной.
Унарный, то есть применённый к одному значению, плюс + ничего не делает с числами. Но если операнд не число, унарный плюс преобразует его в число.
На самом деле это то же самое, что и Number(. ) , только короче.
Необходимость преобразовывать строки в числа возникает очень часто. Например, обычно значения полей HTML-формы — это строки. А что, если их нужно, к примеру, сложить?
Бинарный плюс сложит их как строки:
Поэтому используем унарный плюс, чтобы преобразовать к числу:
С точки зрения математика, такое изобилие плюсов выглядит странным. Но с точки зрения программиста тут нет ничего особенного: сначала выполнятся унарные плюсы, которые приведут строки к числам, а затем бинарный ‘+’ их сложит.
Почему унарные плюсы выполнились до бинарного сложения? Как мы сейчас увидим, дело в их приоритете.
Приоритет операторов
В том случае, если в выражении есть несколько операторов – порядок их выполнения определяется приоритетом, или, другими словами, существует определённый порядок выполнения операторов.
Из школы мы знаем, что умножение в выражении 1 + 2 * 2 выполнится раньше сложения. Это как раз и есть «приоритет». Говорят, что умножение имеет более высокий приоритет, чем сложение.
Скобки важнее, чем приоритет, так что, если мы не удовлетворены порядком по умолчанию, мы можем использовать их, чтобы изменить приоритет. Например, написать (1 + 2) * 2 .
В JavaScript много операторов. Каждый оператор имеет соответствующий номер приоритета. Тот, у кого это число больше, – выполнится раньше. Если приоритет одинаковый, то порядок выполнения – слева направо.
Отрывок из таблицы приоритетов (нет необходимости всё запоминать, обратите внимание, что приоритет унарных операторов выше, чем соответствующих бинарных):
| Приоритет | Название | Обозначение |
|---|---|---|
| … | … | … |
| 15 | унарный плюс | + |
| 15 | унарный минус | — |
| 14 | возведение в степень | ** |
| 13 | умножение | * |
| 13 | деление | / |
| 12 | сложение | + |
| 12 | вычитание | — |
| … | … | … |
| 2 | присваивание | = |
| … | … | … |
Так как «унарный плюс» имеет приоритет 15 , который выше, чем 12 у «сложения» (бинарный плюс), то в выражении "+apples + +oranges" сначала выполнятся унарные плюсы, а затем сложение.
Присваивание
Давайте отметим, что в таблице приоритетов также есть оператор присваивания = . У него один из самых низких приоритетов: 2 .
Именно поэтому, когда переменной что-либо присваивают, например, x = 2 * 2 + 1 , то сначала выполнится арифметика, а уже затем произойдёт присваивание = с сохранением результата в x .
Присваивание = возвращает значение
Тот факт, что = является оператором, а не «магической» конструкцией языка, имеет интересные последствия.
Большинство операторов в JavaScript возвращают значение. Для некоторых это очевидно, например сложение + или умножение * . Но и оператор присваивания не является исключением.
Вызов x = value записывает value в x и возвращает его.
Благодаря этому присваивание можно использовать как часть более сложного выражения:
В примере выше результатом (a = b + 1) будет значение, которое присваивается переменной a (то есть 3 ). Потом оно используется для дальнейших вычислений.
Забавное применение присваивания, не так ли? Нам нужно понимать, как это работает, потому что иногда это можно увидеть в JavaScript-библиотеках.
Однако писать самим в таком стиле не рекомендуется. Такие трюки не сделают ваш код более понятным или читабельным.
Присваивание по цепочке
Рассмотрим ещё одну интересную возможность: цепочку присваиваний.
Такое присваивание работает справа налево. Сначала вычисляется самое правое выражение 2 + 2 , и затем результат присваивается переменным слева: c , b и a . В конце у всех переменных будет одно значение.
Опять-таки, чтобы код читался легче, лучше разделять подобные конструкции на несколько строчек:
Польза от такого стиля особенно ощущается при быстром просмотре кода.
Сокращённая арифметика с присваиванием
Часто нужно применить оператор к переменной и сохранить результат в ней же.
Name already in use
javascript-tutorial-ru / 1-js / 2-first-steps / 8-operators / article.md
- Go to file T
- Go to line L
- Copy path
- Copy permalink
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
Для работы с переменными, со значениями, JavaScript поддерживает все стандартные операторы, большинство которых есть и в других языках программирования.
Несколько операторов мы знаем со школы — это обычные сложение + , умножение * , вычитание и так далее.
В этой главе мы сконцентрируемся на операторах, которые в курсе математики не проходят, и на их особенностях в JavaScript.
Термины: «унарный», «бинарный», «операнд»
У операторов есть своя терминология, которая используется во всех языках программирования.
Прежде, чем мы двинемся дальше — несколько терминов, чтобы понимать, о чём речь.
Операнд — то, к чему применяется оператор. Например: 5 * 2 — оператор умножения с левым и правым операндами. Другое название: «аргумент оператора».
Унарным называется оператор, который применяется к одному операнду. Например, оператор унарный минус «-» меняет знак числа на противоположный:
Бинарным называется оператор, который применяется к двум операндам. Тот же минус существует и в бинарной форме:
Сложение строк, бинарный +
Обычно при помощи плюса ‘+’ складывают числа.
Но если бинарный оператор ‘+’ применить к строкам, то он их объединяет в одну:
Иначе говорят, что «плюс производит конкатенацию (сложение) строк».
Если хотя бы один аргумент является строкой, то второй будет также преобразован к строке!
Причем не важно, справа или слева находится операнд-строка, в любом случае нестроковый аргумент будет преобразован. Например:
Это приведение к строке — особенность исключительно бинарного оператора «+» .
Остальные арифметические операторы работают только с числами и всегда приводят аргументы к числу.
Преобразование к числу, унарный плюс +
Унарный, то есть применённый к одному значению, плюс ничего не делает с числами:
Как видно, плюс ничего не изменил в выражениях. Результат — такой же, как и без него.
Тем не менее, он широко применяется, так как его «побочный эффект» — преобразование значения в число.
Например, когда мы получаем значения из HTML-полей или от пользователя, то они обычно в форме строк.
А что, если их нужно, к примеру, сложить? Бинарный плюс сложит их как строки:
Поэтому используем унарный плюс, чтобы преобразовать к числу:
С точки зрения математики такое изобилие плюсов может показаться странным. С точки зрения программирования — никаких разночтений: сначала выполнятся унарные плюсы, приведут строки к числам, а затем — бинарный ‘+’ их сложит.
Почему унарные плюсы выполнились до бинарного сложения? Как мы сейчас увидим, дело в их приоритете.
В том случае, если в выражении есть несколько операторов — порядок их выполнения определяется приоритетом.
Из школы мы знаем, что умножение в выражении 2 * 2 + 1 выполнится раньше сложения, т.к. его приоритет выше, а скобки явно задают порядок выполнения. Но в JavaScript — гораздо больше операторов, поэтому существует целая таблица приоритетов.
Она содержит как уже пройденные операторы, так и те, которые мы еще не проходили. В ней каждому оператору задан числовой приоритет. Тот, у кого число больше — выполнится раньше. Если приоритет одинаковый, то порядок выполнения — слева направо.
Отрывок из таблицы:
| Приоритет | Название | Обозначение |
|---|---|---|
| . | . | . |
| 15 | унарный плюс | + |
| 15 | унарный минус | — |
| 14 | умножение | * |
| 14 | деление | / |
| 13 | сложение | + |
| 13 | вычитание | — |
| . | . | . |
| 3 | присваивание | = |
| . | . | . |
Так как «унарный плюс» имеет приоритет 15 , выше, чем 13 у обычного «сложения», то в выражении +apples + +oranges сначала сработали плюсы у apples и oranges , а затем уже обычное сложение.
Обратим внимание, в таблице приоритетов также есть оператор присваивания = .
У него — один из самых низких приоритетов: 3 .
Именно поэтому, когда переменную чему-либо присваивают, например, x = 2 * 2 + 1 сначала выполнится арифметика, а уже затем — произойдёт присваивание = .
Возможно присваивание по цепочке:
Такое присваивание работает справа-налево, то есть сначала вычислятся самое правое выражение 2+2 , присвоится в c , затем выполнится b = c и, наконец, a = b .
««smart header =\» возвращает значение» Все операторы возвращают значение. Вызов `x = выражение` не является исключением.
Он записывает выражение в x , а затем возвращает его. Благодаря этому присваивание можно использовать как часть более сложного выражения:
В примере выше результатом (a = b + 1) является значение, которое записывается в a (т.е. 3 ). Оно используется для вычисления c .
Забавное применение присваивания, не так ли?
Знать, как это работает — стоит обязательно, а вот писать самому — только если вы уверены, что это сделает код более читаемым и понятным.
Побитовые операторы рассматривают аргументы как 32-разрядные целые числа и работают на уровне их внутреннего двоичного представления.
Эти операторы не являются чем-то специфичным для JavaScript, они поддерживаются в большинстве языков программирования.
Поддерживаются следующие побитовые операторы:
- AND(и) ( & )
- OR(или) ( | )
- XOR(побитовое исключающее или) ( ^ )
- NOT(не) (
Они используются редко, поэтому вынесены в отдельную главу info:bitwise-operators.
Сокращённая арифметика с присваиванием
Часто нужно применить оператор к переменной и сохранить результат в ней же, например:
Эту запись можно укоротить при помощи совмещённых операторов, вот так:
Вызов с присваиванием имеет в точности такой же приоритет, как обычное присваивание, то есть выполнится после большинства других операций:
Один из самых необычных операторов — запятая ‘,’ .
Его можно вызвать явным образом, например:
Запятая позволяет перечислять выражения, разделяя их запятой ‘,’ . Каждое из них — вычисляется и отбрасывается, за исключением последнего, которое возвращается.
Запятая — единственный оператор, приоритет которого ниже присваивания. В выражении a = (5,6) для явного задания приоритета использованы скобки, иначе оператор ‘=’ выполнился бы до запятой ‘,’ , получилось бы (a=5), 6 .
Зачем же нужен такой странный оператор, который отбрасывает значения всех перечисленных выражений, кроме последнего?
Обычно он используется в составе более сложных конструкций, чтобы сделать несколько действий в одной строке. Например:
Такие трюки используются во многих JavaScript-фреймворках для укорачивания кода.
C++. Унарный минус и беззнаковый тип
Привет, Хабр! Меня зовут Владимир, я работаю в VK Карты. Хочу рассказать про случай, который недавно произошёл у нас в подразделении. Он кажется достаточно типичным и может быть интересен другим программистам.
Нам, программистам на C++, не привыкать, что даже самый безобидный код может таить в себе сюрпризы. Рассмотрим пример:
Он полон сюрпризов! Каких? Короткий ответ: значение signed_offset не определено стандартом и зависит от реализации. Но это далеко не все неожиданности в этом коде. Статья как раз о них.
Результат, возвращаемый унарным минусом
Давайте сначала разберемся с тем, что возвращает -width . Это может прозвучать неожиданно, но тип, возвращаемый -width , это uint32_t . И это не опечатка. То есть если унарный минус применить к неотрицательному числу, то в результате получим опять же неотрицательное число. Давайте разберёмся, как такое могло получиться и чему будет равен результат -width .
Если выполнить такой код:
Как мы выяснили выше, -width вернёт значение 4 294 967 289 типа uint32_t . А оно не поместится в int32_t . Соответственно, значение signed_offset будет не определено. Отмечу, что согласно стандарту это допустимая запись, не приводящая к неопределенному поведению. То есть код допустим, но значение в signed_offset после его исполнения может быть любым.
Если скомпилировать и запустить этот пример, то с большой долей вероятности в signed_offset окажется -7. Так реагирует Clang 13.1.6 на MacOS. Но это реакция на переполнение знакового целого числа. А с точки зрения стандарта в signed_offset может быть любое значение.
Переполнение знаковых и беззнаковых типов
Отмечу, что стандарт по-разному относится к переполнению знаковых и беззнаковых типов. При присвоении слишком большого значения знаковому типу мы получаем поведение, зависящее от реализации. Этот случай мы подробно рассмотрели выше. При присвоении слишком большого значения беззнаковому типу мы получим вполне определённое и гарантированное стандартом поведение, согласно разделу 7.8.2. Правило такое: если мы хотим присвоить число big_number переменной unsigned_offset беззнакового типа some_unsigned_type, то результатом этой операции будет число big_number по модулю 2 n , где n — количество бит, необходимое для хранения типа some_unsigned_type . Звучит запутано, но на самом деле тут всё предельно просто. Достаточно взглянуть на пример:
unsigned_offset будет равно 7, потому что размер uint32_t — 32 бита. 2 32 равно 4 294 967 296, а 4 294 967 296 + 4 294 967 296 по модулю 4 294 967 296 будет равно 0. Выходит результат 7.
Выводы и рекомендации
Рекомендую обращать внимание на неявные переполнения беззнаковых типов. В частности, стоит избегать применения унарного минуса к беззнаковым типам. Если же нужно, чтобы код выше был выполнен, то можно написать, например, так:
Во всех рассуждениях я преимущественно писал про uint32_t и int32_t . Однако эти рассуждения верны соответственно для всех знаковых и беззнаковых типов.
Ссылки
Хочу кое-что уточнить. То что я писал выше относится к стандарту C++17. В C++20 приняли «two’s complement». Это раздел 6.8.1, параграф 3. То есть код из начала статьи:
должен всегда работать одинаково в C++20, и signed_offset должно быть -7. В C++17 значение signed_offset не определено.
Урок №39. Арифметические операторы
На этом уроке мы рассмотрим арифметические операторы и их использование в языке С++.
Унарные арифметические операторы
Унарные операторы — это операторы, которые применяются только к одному операнду. Существуют два унарных арифметических оператора: плюс ( + ) и минус ( − ).
| Оператор | Символ | Пример | Операция |
| Унарный плюс | + | +x | Значение x |
| Унарный минус | − | −x | Отрицательное значение x |
Унарный оператор + возвращает значение операнда. Другими словами, +5 = 5 или +х = х . Унарный плюс вам, скорее всего, не придется использовать. Его по большей части добавили в качестве симметрии с унарным оператором минус. Унарный оператор минус возвращает операнд, умноженный на −1 . Например, если х = 5 , то −х = −5 .
Оба этих оператора пишутся непосредственно перед самим операндом, без пробела ( −x , а не − x ).
Не следует путать унарный оператор минус с бинарным оператором вычитания, хоть они и используют один и тот же символ. Например, в выражении х = 5 − −3; , первый минус — это оператор вычитания, а второй — унарный минус.
Бинарные арифметические операторы
Бинарные операторы — это операторы, которые применяются к двум операндам (слева и справа). Существует 5 бинарных операторов.
| Оператор | Символ | Пример | Операция |
| Сложение | + | x + y | x плюс y |
| Вычитание | − | x − y | x минус y |
| Умножение | * | x * y | x умножить на y |
| Деление | / | x / y | x разделить на y |
| Деление с остатком | % | x % y | Остаток от деления x на y |
Операторы сложения, вычитания и умножения работают так же, как и в обычной математике. А вот деление и деление с остатком рассмотрим детально.
Деление целых чисел и чисел типа с плавающей точкой
Оператор деления имеет два режима. Если оба операнда являются целыми числами, то оператор выполняет целочисленное деление. Т.е. любая дробь (больше/меньше) отбрасывается и возвращается целое значение без остатка, например, 7 / 4 = 1 .
Если один или оба операнда типа с плавающей точкой, то тогда будет выполняться деление типа с плавающей точкой. Здесь уже дробь присутствует. Например, выражения 7.0 / 3 = 2.333 , 7 / 3.0 = 2.333 или 7.0 / 3.0 = 2.333 имеют один и тот же результат.
Попытки деления на 0 (или на 0.0 ) станут причиной сбоя в вашей программе, и это правило не следует забывать!
Использование оператора static_cast в операциях деления
На уроке о символьном типе данных char мы уже использовали оператор static_cast для вывода ASCII-символов в виде целых чисел.
Аналогичным образом мы можем использовать static_cast для конвертации целого числа в число типа с плавающей точкой. Таким образом, вместо целочисленного деления выполнится деление типа с плавающей точкой. Например:
Результат выполнения программы:
int / int = 1
double / int = 1.75
int / double = 1.75
double / double = 1.75
Деление с остатком
Оператор деления с остатком ( % ) работает только с целочисленными операндами и возвращает остаток от целочисленного деления. Например:
Пример №1: 7 / 4 = 1 с остатком 3 , таким образом, 7 % 4 = 3 .
Пример №2: 25 / 7 = 3 с остатком 4 , таким образом, 25 % 7 = 4 . Остаток составляет не дробь, а целое число.
Пример №3: 36 % 5 = 7 с остатком 1 . В числе 36 только 35 делится на 5 без остатка, поэтому 36 − 35 = 1 , 1 — это остаток и результат.
Данный оператор чаще всего используют для проверки деления без остатка одних чисел на другие. Если х % у == 0 , то х делится на у без остатка.
Например, мы хотим написать программу, которая выводит числа от 1 до 100 по 20 значений в каждой строке. Мы можем использовать оператор деления с остатком для создания разрыва строк. Несмотря на то, что мы еще не рассматривали цикл while, в следующей программе всё максимально просто и понятно:
Результат выполнения программы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
О while мы еще поговорим на соответствующем уроке.
Отрицательные числа в операциях деления до C++11
До C++11, если любой из операндов целочисленного деления является отрицательным, то компилятор округляет результат самостоятельно! Например, результатом −5 / 2 может быть либо −3 , либо −2 . Однако большинство современных компиляторов округляют числа в сторону нуля (например, в −5 / 2 результатом будет −2 ). В спецификации C++11 определили, что компилятор должен всегда округлять к нулю (или, проще говоря, просто отбрасывать дробь).
Также до C++11, если один из операндов оператора деления с остатком является отрицательным, то результат может быть как положительным, так и отрицательным! Например, результатом −5 % 2 может быть как 1 , так и −1 . В спецификации C++11 решили сделать так, чтобы результат a % b был того же знака, что и значение а .
Арифметические операторы присваивания
| Оператор | Символ | Пример | Операция |
| Присваивание | = | x = y | Присваиваем значение y переменной x |
| Сложение с присваиванием | += | x += y | Добавляем y к x |
| Вычитание с присваиванием | −= | x −= y | Вычитаем y из x |
| Умножение с присваиванием | *= | x *= y | Умножаем x на y |
| Деление с присваиванием | /= | x /= y | Делим x на y |
| Деление с остатком и с присваиванием | %= | x %= y | Присваиваем остаток от деления x на y переменной x |
До этого момента, когда нам нужно было добавить число 5 к определенной переменной, мы делали следующее: