В прошлый раз у меня на занятии был пятиклассник, а на этот раз дошёл до второклассницы (не нравится мне эта игра на понижение, ох не нравится!). Рассказывал, помимо прочего, про порядок арифметических операций, но по-честному.

Я уже написал было пост по мотивам занятия про то, что нет такого математического закона о том, что умножение выполняется раньше, чем сложение. Но обнаружил, что у меня про это уже был текст. Так что я просто допишу небольшой кусочек

Как я там рассказывал, у любого выражения должен быть один единственный порядок вычисления операций, который на письме задаётся скобками. И есть только договорённость о том, что если некоторые скобки можно пропускать. Например, в выражении (a + (b * c)) можно опустить скобки и записать это как (a + b * c), потому что мы всегда можем эти скобки вернуть ровно как были: вокруг умножения.

Но. Есть ещё один тип скобок, которые мы пропускаем, не всегда отдавая себе в этом отчёт: скобки между одинаковыми операциями. Если у нас написано a * b * c, какой порядок вычислений имеется в виду? Почему мы вообще можем писать это без скобок?

На это можно посмотреть с двух сторон: математической и «программистской». Давайте сначала с математической.

Умножения и сложение обладают свойством ассоциативности: ((a * b) * c) = (a * (b * c)). Это довольно хитрое свойство, которое не так-то просто объяснить. Но с прикладной точки зрения это означает, что от порядка скобок результат не зависит, то есть мы можем выполнить эти операции в любом порядке. А раз от порядка этих скобок результат не зависит, мы можем для простоты писать (a * b * c), не конкретизируя как именно мы это вычисляем.

Теперь я перейду на сторону счетоводов программистов и скажу, что всё равно ((a * b) * c) и (a * (b * c)) — два разных выражения, ведущие к двум разным вычислениям, хоть и с одинаковым результатом.
Причём бывают ситуации, когда этот порядок очень сильно влияет на сложность вычисления. С числами это не так просто заметить. Но если a — матрица 1 × 1000, b — матрица 1000 × 1, c — матрица 1 × 1000, то это играет уже очень большую роль.
Вычисление ((a * b) * c) требует провести 2000 умножений и 999 сложений элементов
Тогда как (a * (b * c)) тратит аж 2000000 умножений и 999000 сложений. В тысячу раз больше! А результат тот же, да. Только у вас терпения не хватит столько считать.
Про матрицы я второкласснице, к сожалению, рассказать за первое занятие не успел.

Большинство популярных языков программирования существуют в императивной парадигме: компьютер делает ровно те действия, которые ему сказали, причём сказали максимально чётко.
Но программисты почти настолько же ленивы, насколько математики, поэтому тоже часто пишут формулы без скобок: result = a * b * c.
Здесь компьютер должен был бы столкнуться с неоднозначностью: неясно какое из действий делать первым. Но в языках программирования есть чётко прописанные правила о том, как следует расставлять пропущенные скобки. Для этого есть
— (а) табличка с приоритетом операций, которая говорит, что в нашей записи умножение/деление имеют приоритет над сложением/вычитанием.
— (б) каждой группе операций с одинаковым приоритетом назначается ассоциативность нотации: левая или правая. Это немножко другая ассоциативность, чем в математике, и её-то я как раз объясню.

Левая ассоциативность означает, что операнды группируются сначала слева-направо, правая — наоборот.
Большая часть арифметических операций лево-ассоциативна: a * b * c трактуется как ((a * b) * c).
Возведение в степень иногда делают право-ассоциативной: a ^ b ^ c ≡ (a ^ (b ^ c)). Но я в этом и всех всех других случаях, вызывающих сомнения, предпочитаю явно написать все скобочки.

На самом деле левая ассоциативность предполагается не только в программистской, но и в математической нотации, хотя это и сложнее заметить из-за замыленности глаза. При первом подходе к снаряду мы немного сжульничали, написав только про сложение и умножение. А ведь есть ещё вычитание и деление, и они НЕ ассоциативны с математической точки зрения: выражение ((a - b) - c) даёт совсем не тот же результат, что (a - (b - c)).

Но мы договорились, что все арифметические операции лево-ассоциативны с точки зрения нотации, поэтому выражения (a - b - c) и ((a - b) - c) мы считаем взаимозаменяемыми.

Поскольку сложение и умножение имеют одинаковый приоритет, то всякие их комбинации работают аналогично.
Для иллюстрации расставим скобки в выражении 1 - 2 + 3 + 4 - 5 - 6.
Левая ассоциативность группирует их вот так: (((((1 - 2) + 3) + 4) - 5) - 6)
Можете убедиться, что это соответствует вашему представлению о том, что должно получиться.

Но если левая ассоциативность — лишь договорённость, значит ли это, что мы могли бы вместо этого договориться использовать правую ассоциативность? Ну… на письме да, а в речи нет.
Левая ассоциативность позволяет начать вычисления не дожидаясь конца выражения. Вы услышали «один минус два… [посчитали]… плюс три… [посчитали]… плюс четыре… [посчитали]» итд.
А теперь представьте, что мы трактовали бы это так: (1 - (2 + (3 + (4 - (5 - 6))))). Вы слышите «один минус два…», но на этом месте вы ещё не знаете, а двойку ли надо вычитать или результат какого-то здоровенного вычисления, которое вы ещё недослушали. Это как в немецком, где последнее слово часто меняет смысл всего предложения.

Так что с точки зрения эргономики у нас сложилась очень классная математическая нотация. Но сколько же всего заметают под ковёр, когда в школе преподают арифметику.

Мем в тему

![[photos/photo_275@18-06-2026_18-32-38.jpg]]