Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Прочее

Так ли нужны операции «&&», «||» и «^^»?

В Си-подобных языках принято различать «&&» и «&», «||» и «|», «^^» и «^» («и», «или» и «исключающее или»). Логические операции используют двойные «&&», «||» и «^^», побитовые операции — одинарные.
Причина — в отсутствии полноценного логического типа в родоначальнике этой ветви языков — в языке Си. Остальные языки просто копировали эту «особенность». В Си значение «ложь» — это «0», всё остальное — «истина».

        Возьмём, например, два целых числа, шестнадцатиричные значение которых равно «00000001» и «10000000». Применив к ним логичкую операцию «&&», получаем «истина» && «истина» = «истина». Но применив к ним побитовую «&», получаем: «00000001» & «10000000» = «00000000». Видим, что эти операции не эквивалентны.

        Похоже, что разное обозначение родственных операций всех устраивает. А зря. Если существует логический тип, у которого «ложь» — это «0», а «истина» — не «0» (~0 == 0xFFFFFF, т.е. «-1» в дополнительном коде), то побитовые «и», «или» и «исключающее или» являются и логическими одновременно. Экономия оперций налицо:

int      I, J, K;
         I = 0x00FF0077;
         J = I & 0x11223344;      // результат: 0x00220044
         K = J | 0x00110022;      // результат: 0x00330066
boolean  X = true;
boolean  Y = false;
boolean  Z = X & Y;               // результат: ложь
if ( Z ^ X)                       // результат: истина
        Если сделать так, что оператор «if» принимает только тип boolean, то запись вида «if X=Y» становится незаконной для X и Y любых типов, кроме boolean. Чтобы быть правильной, необходимо делать преобразование к boolean: «if boolean(X=Y)». Это удлинняет запись, но делает очевидными намерения программиста:
int  a, b;
// ...
if (a == b)              // нормально
if (a = b)               // ошибка, если а — не типа boolean
if ((boolean) a = b)     // требуется преобразование к типу boolean
        В логических операций «И» и «ИЛИ» существует один подвох. Дело в том, что они участвуют в ленивых вычислениях. В отличие от побитовых операций. Если первый операнд в операции «И» имеет значение «ложь», а первый операнд операции «ИЛИ» — «истина», то дальнейшие вычисления не производятся. Потому что они бесполезны и уже не могут изменить результата. Но побитовым операциям для вычисления результата второй операнд нужен всегда.

        Но и это не всё. В C++ вычисление второго операнда для логических операций «И» и «ИЛИ» не производится только для предопределённых типов. Для классов, в которых этих операции переопределены, «ленивые вычисления» невозможны. Почему? Да потому, что в языке не существует средств, которыми бы можно было описать, который операнд «ленивый», а который нет.

        От таких вопросов невозможно отмахнуться. Если считать этот момент несущественным и производить вычисление операндов всегда, то результатом может быть не только некоторое замедление вычислений. Дело в том, что если операнд не подставляется, а вычисляется, то это вычисление может иметь побочные эффекты. Ну например, если открыть файл, то система с таким вычислением и без него будет находиться в разных состояниях.

        Но может не всё так трагично, как на первый взгляд? Ведь логические «И» и «ИЛИ» должны, по идее, существовать только для логического типа. Ну может ещё для типов (классов), которые унаследованы от логического Для остальных типов они должны быть побитовыми. Поэтому возможность откладывания вычисления второго операнда нужно оставить только логичесому типу. И типам, для которых логический тип — базовый. Хотя целесообразность создания типов, производных от логического, не очевидна.

        Так что семь раз отмерь, и только один отрежь...

Последняя правка: 2018-11-26    17:37

ОценитеОценки посетителей
   ███████████████████ 7 (43.7%)
   ██████ 2 (12.5%)
   ███ 1 (6.25%)
   ████████████████ 6 (37.5%)

Отзывы

     2013/11/16 13:05, Noname

В Форт нет логических && !! ^^ а есть только обычные AND OR XOR
а также False = 0, а True = все остальные значения

     2014/01/16 10:56, Pensulo

Позвольте уточнить и дополнить вас, уважаемый Noname.

В современном стандарте Форт аргументы типа flag (содержащие логическое значение) возвращаемые стандартными словами языка в качестве FALSE должны возвращать одноячеечное число со всеми битами сброшенными в 0, а в качестве TRUE должны возвращать одноячеечное число со всеми битами взведёнными в 1. Так что в знаковом трактовании числа выходит что FALSE = 0, а TRUE = -1.
На практике же, повальное большинство слов принимающих аргумент, расцениваемый как логический (не обязательно типа flag, кстати говоря), проводят анализ на равенство именно FALSE и принимают решения исходя из результата этого сравнения, а потому все прочие числовые значения автоматически воспринимаются равными TRUE.

Но суть вашего сообщения в корне верна, — в Форт нет логических операций AND, OR, XOR, а также операция сравнения "=" всегда расценивает свои аргументы как числовые, а не логические.

     2014/03/18 17:37, Noname

Слова ветвления IF WHILE UNTIL сравнивают аргумент на неравенство нулю и "ответвляются" по данному положению вещей. Пэтому де факто True все ненулевые значения. Хотя слова сравнения = <> и др. возвращают True = -1

     2014/05/23 08:00, Pensulo

Слова ветвления IF WHILE UNTIL сравнивают аргумент на неравенство нулю и "ответвляются" по данному положению вещей.

На деле, при дизассемблировании Форт-кода использующего IF, WHILE и UNTIL, можно видеть что сравнение при ветвлении производится как-раз таки на равенство НУЛЮ.

Поэтому де факто True все ненулевые значения. Хотя слова сравнения = <> и др. возвращают True = -1.

Для операторов ветвления — согласен, так и есть. Но, например, выражение "1 2 AND" даст в результате 0, хотя и 1 и 2 по сути являют собою ненулевые значения (т.е. могут расцениваться как логическое True). То же самое относится и к операторам сравнения = и <>, ибо, как я уже заметил ранее, в Форт нет логических (булевых) операций. Все (ну или почти все) операции рассматривают свои аргументы как сугубо числовые значения. И потому надо проявлять особую осторожность при составлении выражений смешанного типа.

     2014/11/04 05:50, Smart

Оператора ^^ в C\C++ нет, как ни странно. Есть только ^.

Как по мне, запись стоˊит унифицировать, но для логических операций предусмотреть "ленивую" и "неленивую" версии. В C это не предусмотрено, в Паскале поведение логических операторов задаётся опциями компилятора, что неудобно. Самый лучший вариант был в VB.NET: в довесок к "ленивым" And и Or имелись "неленивые" AndAlso и OrElse. То же самое можно предусмотреть и здесь: например &, ! -- неленивые операторы (как для целочисленных, так и для логических величин), а &&, !! -- ленивые (только для логических), или наоборот.

     2014/11/04 10:33, Автор сайта

Оператора ^^ в C\C++ нет, как ни странно. Есть только ^.

Я тоже этому удивлялся.

Мне хотелось бы, чтобы все логические операции были «ленивыми». Разница между «ленивыми» и «неленивыми» вычислениями проявляется тогда, когда функция, возвращающая результат, не является чистой, т.е. производит побочные эффекты. Допустим, f(x) возвращает логическое значение и производит побочные эффекты. Тогда
bool a = false; if (a & f(x)) // . . .
Если «&» — ленива, то f(x) не будет вызвана и не произведёт побочный эффект. Поэтому правильно было бы
bool a = false; bool b = f(x); if (a & b) // . . .
Для упрощения языка я бы всё-таки оставил бы только одинарные «&», «|» и «^», сделав их ленивыми.

     2014/11/07 14:08, Сергей

Оператор ^^ не может иметь ленивых аргументов.

     2014/11/10 19:33, Автор сайта

Ну да, конечно.

     2016/06/22 04:19, rst256

Для упрощения языка я бы всё-таки оставил бы только одинарные «&», «!» и «^», сделав их ленивыми. Программиста, не знающего какие функции у него производят побочные эффекты, все равно уже ничто не спасет. А вот насчет замены операций на побитовые я бы не советовал, будет очень весело, если вдруг туда будет записано неверное значение. Да и как насчет битовых полей и типов меньшего размера? Может

если(а ! б) — логическое
если(а ! б > 0х558) — побитовое
.

     2016/06/22 04:40, rst256

Если сделать так, что оператор «if» принимает только тип boolean, то запись вида «if X=Y» — это что — присваивание или сравнение? Для присвоения вполне допустимо применить неявное приведение к логическому типу (по стандарту Си), ведь неоднозначных операций тут нет. Хотя с таким "нестандартным" значением для true это может быть не лишним. А проверка на равенство по определению должна возвращать значение логического типа, а ее аргументами могут быть все типы для которых она определена.

     2016/06/22 14:05, Автор сайта

Возможно, идею с одинаковыми обозначениями для логических и побитовых операций придётся оставить по причине разности в приоритетах. Программисты на Паскале признают, в Си их приоритеты более логичны, меньше путаницы при программировании.

Осутствие автоматического приведения целых типов к логическому убережёт от неправильного понимания «if X=Y». Если Y — целое, то правильным будет «if bool(X=Y)». Значение «true» — это всегда -1, т.е. инверсия нуля («false») устанавливает все биты в «1». А инверсия -1 («true») будет равна 0. Явное приведение целых типов с ненулевым значением к логическому типу даст -1, т.е. «true». Если «false» — это 0, а «true» — это -1, то логические и побитовые операции имеют одинаковую аппаратную реализацию. Несовпадение разрядности приводит к неявному приведению к «наибольшему» типу.

     2016/07/14 10:03, alextretyak

Оператора ^^ в C\C++ нет, как ни странно. Есть только ^.

Я тоже этому удивлялся.

Вообще-то такой оператор есть. :)
Называется: !=
И для булевых типов работает также как ^.
Предполагаю, что авторы языков (Си и его производных) предусмотрительно не стали вводить оператор ^^ с абсолютно таким же действием, как у уже имеющегося оператора.

     2016/07/15 17:25, Автор сайта

Видите ли, в Си есть привычка использовать переменные числового типа как логического: 0 — это «ложь», всё остальное — это «истина». Для числовых типов операция «!=» не эквивалентна гипотетической «^^»:
int a = 1; int b = 2;
a != b // «истина»
a ^^ b // «ложь», т.к. a — «истина» (a=1) и b — «истина» (b=2)
a && b // проверяем: здесь «истина», т.к. a и b тоже «истина»

     2016/07/16 09:50, alextretyak

Ну да, верно, про такой "вариант использования" оператора ^^ я что-то не подумал. ☺
Но, вообще-то говоря, несмотря на кажущееся удобство (не нужно писать "лишнюю" проверку != 0), я считаю такое неявное приведение [int к bool] всё же недопустимым (как и сделано в Rust — там оператор if требует значение строго типа bool).

     2016/07/16 14:48, Автор сайта

Я тоже так считаю. Должно быть явное приведение.

     2016/10/31 03:51, rst256

Отсутствие автоматического приведения целых типов к логическому убережёт от неправильного понимания «if X=Y». Если Y — целое, то правильным будет «if bool(X=Y)».

У меня только один вопрос остается: явное приведение логического типа к логическому же типу (ну так сказать тоже для большей очевидности намерений) будет возможно, т.е. "bool b; ... if bool(b)"?

     2017/01/06 23:48, Автор сайта

На этот счёт такие мысли. В языках со статической типизацией (мы ведь о таком языке размышляем, исходя из философии?) типы известны во время компиляции. Следовательно, компилятор должен обнаружить приведение логического типа к логическому. Это бессмысленная с точки зрения компилятора операция. Она должна быть ликвидирована при оптимизации. В принципе, такие операции можно даже запретить. Тогда программист, увидев ошибку при компиляции А=bool(А), понимает, что масло и так масляное. По идее, стремление программиста перестраховаться операциями типа А=bool(А) говорит о том, что он не может почерпнуть информацию о типе у компилятора, он слишком молчалив. Хорошо бы при компиляции иметь не только исполняемый модуль, но и обзор всех внутренностей программы с подробностями, степень которых можно регулировать.

     2018/11/18 15:26, Freeman

Этюд о битовых и логических операциях недавно восстановлен, став теперь частью документации: «Логические и битовые операции ». Раздельные операции нужны для избавления от скобок.

Ленивость вычислений в Канторе не проблема, в языке с объектами первого класса все операции обобщенные.

     2018/11/26 14:23, Автор сайта

Сперва хотел просто ответить, потом хотел доработать статью. В итоге родилась новая статья: «Изменение приоритетов операций».

Написать отзыв

Написать автору можно на электронную почту mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Каким должен быть язык программирования?

Анализ и критика

Устарел ли текст как форма представления программы

Русский язык и программирование

Многоязыковое программирование

Синтаксис языков программирования

Синтаксический сахар

Некоторые «вкусности» Алгол-68

«Двухмерный» синтаксис Python

Почему языки с синтаксисом Си популярнее языков с синтаксисом Паскаля?

Должна ли программа быть удобочитаемой?

Стиль языка программирования

Тексто-графическое представление программы

●  Разделители

●  Строки программы

●  Слева направо или справа налево?

Комментарии

●  Длинные комментарии

●  Короткие комментарии

●  Комментарии автоматической генерации документации

●  Нерабочий код

●  Помеченные комментарии

Нужны ли беззнаковые целые?

Шестнадцатиричные и двоичные константы

Условные операторы

Переключатель

Циклы

●  Продолжение цикла и выход из него

Некошерный «goto»

Изменение приоритетов операций

Операции присвоения и проверки на равенство. Возможно ли однаковое обозначение?

Так ли нужны операции «&&», «||» и «^^»?

Постфиксные инкремент и декремент

Почему в PHP для конкатенации строк используется «.»?

Указатели и ссылки в C++

Использование памяти

Почему динамическое распределение памяти — это плохо

Как обеспечить возврат функциями объектов переменной длины?

●  Типы переменного размера (dynamically sized types, DST) в языке Rust

●  Массивы переменной длины в C/C++

●  Размещение объектов в стеке, традиционный подход

●  Размещение объектов переменной длины с использованием множества стеков

●  Размещение объектов переменной длины с использованием двух стеков

●  Реализация двухстековой модели размещения данных

●  Двухстековая модель: тесты на скорость

●  Изменение длины объекта в стеке во время исполнения

●  Размещение объектов переменной длины с использованием одного стека

Можно ли забыть о «куче», если объекты переменной длины хранить в стеке

Безопасность и размещение объектов переменной длины в стеке

Массивы, структуры, типы, классы переменной длины

О хранении данных в стеке, вместо заключения

Описание языка

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

Компьютерный юмор

Прочее

Последние комментарии

2018/12/08 23:03 ••• Попов Михаил
✎ Программирование без программистов — это медицина без врачей

2018/12/07 08:57 ••• Автор сайта
✎ Почему обречён язык Форт

2018/12/07 08:36 ••• Автор сайта
✎ Нужны ли беззнаковые целые?

2018/12/03 13:51 ••• kt
✎ Экстракоды при синтезе программ

2018/11/30 17:56 ••• Freeman
✎ Изменение приоритетов операций

2018/11/30 17:20 ••• Автор сайта
✎ Почему языки с синтаксисом Си популярнее языков с синтаксисом Паскаля?

2018/11/26 14:23 ••• Автор сайта
✎ Так ли нужны операции «&&», «||» и «^^»?

2018/11/18 15:21 ••• Freeman
✎ Устарел ли текст как форма представления программы

2018/11/17 03:28 ••• Comdiv
✎ Изменение длины объекта в стеке во время исполнения

2018/11/16 12:53 ••• Автор сайта
✎ Помеченные комментарии

2018/11/11 14:01 ••• Александр Коновалов aka Маздайщик
✎ Нерабочий код

2018/11/11 13:39 ••• Александр Коновалов aka Маздайщик
✎ О русском языке в программировании