Zealint писал(а):
Yoda писал(а):
Код:
int72 operator + (int72 a, int72 b) {
int128 t = a.l + b.l;
Result.l = int64(t);
Result.h = a.h + b.h + t>>64;
}
Сергей, я в недоумении. Куда опять дели байт?
Который байт? По-моему, сейчас всё в порядке.
Zealint писал(а):
Я вот тут не согласен. Можно взять и запретить, и наплевать на отзывы. Конечно, тут можно возразить, дескать, я не авторитет, чтобы решать данный вопрос. Но есть вещи, которые нужно отрубать жёстко. Ещё вариант: отступы в языке делать ТОЛЬКО символом tab и никаким больше. Пробел в начале строки будет синтаксической ошибкой. Но блин, это не сработает, так как многие редакторы настраиваются их пользователями так, чтобы tab сразу заменялся на пробелы (у меня это 2 пробела и чхать на всё : ) ) Мы здесь приходим к неоднозначности, в моей концепции (которую я пытаюсь продумать) сказано, что не должно быть никаких неоднозначностей. И здесь вопрос ребром: либо запрещаем tab, либо создаём какие-то совершенно однозначные правила его использования.
Так я и говорю, - вопрос решается отказом от отступов в пользу ограничителей ({}, begin/end или любые другие формы). Это убирает и неоднозначность, и необходимость накладывать какие-либо формальные запреты на табуляции и их трактовку.
Zealint писал(а):
Yoda писал(а):
16-битные целые числа к 32-битным тоже явно приводить?
Нет, можно, как было предложено, создать чёткую иерархию и правила преобразования.
То есть, приходим к выводу, что определённые преобразования типов, которые можно считать условно-безопасными, можно (а с моей точки зрения даже желательно) производить молча, без явного приведения и без замечаний компилятора. Важно только определить класс таких преобразований. В этом смысле C/C++ не так уж и плохи, - если не считать некоторые странности (типа использования целых типов вместо булевого), то современный компилятор без (отключаемых) возражений не даст даже привести беззнаковый тип к знаковому того же размера.
Zealint писал(а):
Yoda писал(а):
А если есть типы, определённые пользователем, с тем же видом отношений между ними?
Пользователь должен прописать правила преобразования (по сути, эту задачу решает конструктор, который нужно указывать явно)
Не существует конструкторов встроенных типов, тем более сложно себе представить конструктор производного от встроенного типа, например, указателя. К тому же, существование конструктора не обязательно означает, что такое преобразование типов можно осуществлять молча (хотя мне пока что не приходит в голову, при каких условиях наличие конструктора не может быть использовано в качестве молчаливого преобразования типа).
Zealint писал(а):
Yoda писал(а):
Код:
cout << static_cast <const char *>(mystr);
Оператор «<<» для ostream придётся прописать для нашей собственной строки, раз мы её определяем.
Да ну, бросьте. Это лишь частный пример, - функций могут быть десятки, даже сотни, вы же не будете их все переопределять только ради того, чтобы избежать постоянного использования вполне безопасного преобразования типа. И данный случай - очень хороший пример такого применения.
Zealint писал(а):
Yoda писал(а):
А если учесть, что результатом умножения всегда является удвоенный размер, нужно ли явно приводить результат к тому же типу после умножения?
Нет, оператор умножения должен возвращать тот размер, который определён в его коде. Если прописано, что возвращается удвоенный размер, значит возвращается удвоенный, если нет, то нет.
Ага, значит мы должны согласиться с тем, что иногда должны производиться молча даже небезопасные приведения типов.
Zealint писал(а):
Например, программист может от выражения a*(b*c) хотеть, чтобы в скобках тип на время или навсегда стал удвоенным, чтобы случилось переполнение и отрезание старшей половины, чтобы тип стал не удвоенным, а утроенным. Я не думаю, что эту проблему можно оставить на откуп компилятору. Лучше прописать явно, чего мы хотим. Например,
Код:
int64 a, b, c;
a*int128(b*c) // явно хотим, чтобы b*c имело удвоенный тип.
a*(b*c) // явно хотим, чтобы b*c имело тот же тип.
int128(a*(b*c)) // явно хотим, чтобы b*c имело тот же тип, а умножение на a уже удвоенный.
У Вас имеется более удачный вариант решения проблемы? Я считаю, что приводить тип к int128, а затем выполнять умножение – это пустая трата ресурсов.
Да, я полагаю, что у меня есть удачное решение данной проблемы. Компилятор всегда должен производить результат удвоенной разрядности по всем операциям кроме деления и молча отбрасывать лишние разряды при условии, что целевая разрядность не меньше, чем максимальная разрядность исходных операндов. Поясню на примере.
d = a*(b*c);
Если d - 64-битный, a, b, c - 32-битные, то результат умножения (b*c) будет 64 битным.
Если d - 32-битный, то результат (b*c) - 32-битный.
Если d - 16-битный, то результат (b*c) будет 16-битным (причём, для эффективности лучше умножать только младшие части b и c), но с выводом замечания.
Если d - 32-битный, b и c - 32-битные, a - 64-битное, то результат (b*c) - 32-битный, но с выводом замечания по второй операции т.к. размер операнда a больше размера d.
Собственно, при таком подходе скобки и явные расширяющие приведения типов становятся просто не нужны, т.к. такая логика одновременно гарантирует и корректность результата и эффективность выполнения. Приведение типа к int128
перед умножением - действительно пустая трата ресурсов, но в моём подходе этого и не требуется.
Zealint писал(а):
Yoda писал(а):
А если вспомнить, что даже операции с плавающей точкой как правило приводят к потере точности (а то и к переполнению), нужно ли после каждой операции над числами типа float приводить результат к float?
Если это не следует из контекста, то да. Если написана команда float=float*float, то умножаем как есть с одинарной точностью. Если написано double = float*float, значит нужно обеспечить умножение с удвоенной точностью. Это непривычно для программиста на C++, как и предыдущие примеры, но я лучше не придумал.
Правильно, и это - в точности частный случай предложенной мной выше схемы.
Zealint писал(а):
Хорошо, Ваша точка зрения ясна, но она лежит в области программирования транслятора. Возможно, мало кого из пользователей будет волновать, что транслятор будет чуть сложнее. Моё неприятие подобной записи скорее эмоционально-эстетическое.
Так ведь это тоже аргумент. Моё неприятие префикса 0x - тоже эмоционально-эстетическое.
Zealint писал(а):
Yoda писал(а):
Всё верно. А префиксную запись типа 0xABCD я бы запретил. Да и восьмеричную систему счисления тоже запретил бы. Только два суффикса - b и h.
У Вас есть исследования, которые позволяли бы столь смело выбрасывать восьмеричную систему? Я ни разу за 15 лет ей не воспользовался в реальной программе (если не считать тупых университетских задач, придуманных, видимо, для детей младше 3-х лет).
Нет, исследований у меня нет. Но есть некоторые обоснования.
Начнём с того, что системы счисления непосредственно в коде программы, отличные от десятичной, требуются в основном для системных целей. Математикам они не требуются или требуются только при вводе/выводе.
Далее, восьмеричная система счисления на самом деле имеет чисто исторические корни, т.к. большое количество ранних машин имели 9-битный базовый машинный тип или производный от него (18-битные PDP-1, PDP-4, PDP-7, PDP-9, PDP-15, UNIVAC, IBM 7700, 36-битные - ряд машин IBM, UNIVAC, PDP-6, PDP-10 и ряд других). Для них машинно-зависимые константы действительно было удобней записывать триадами в восьмеричной системе. Но поскольку 9-битность сама по себе выпадает из машинной логики (как не кратная степени двойки, - например, эффективно работать с полем бит в такой системе практически нереально) и полностью отмерла, необходимость в такой записи также отмерла. По тем же причинам нелогичным кажется деление на группы из
трёх бит, - тройка не является степенью двойки и также выпадает из машинной логики.
Далее, префиксная запись нулём, принятая в С, - вообще катастрофа. Постфиксная буквой 'o' или 'O' визуально может сливается с 0, что тоже нехорошо.
Наконец, если у нас есть механизм отбивки групп разрядов, то нет никаких проблем вместо восьмеричной системы использовать двоичную - достаточно разбить число на триады. Я бы сказал, что отсутствие в С/С++ двоичных констант - такая же ошибка, как и введение восьмеричных.
Более того, двоичная запись гораздо более полезна, чем восьмеричная и шестнадцатеричная вместе взятые, т.к. в большинстве случаев необходимость в них возникает именно тогда, когда нам нужно манипулировать с группами бит или отдельными битами.
Zealint писал(а):
Yoda писал(а):
Никаких апострофов перед суффиксами. Неоднозначность решается просто, - любое число должно начинаться с цифры. 0ABCDh - число, ABCDh - переменная.
Если честно, то это некрасиво : ) хотя и проще для анализа. И с апострофом тоже не очень. Видимо, придётся пожертвовать здесь красотой.
Красота здесь, вероятно, не главное, более важна понятность. Возьмём три записи: 0badh, bad'h и badh. Только первая запись, очевидно, является числом. Второй вариант уже вызывает серьёзный когнитивный диссонанс, а третий вообще неотличим от идентификатора никакими разумными правилами.
Zealint писал(а):
Во-первых, оператор типа break (как бы его не называли), должен иметь параметр, указывающий, из какого именно цикла мы выходим.
Я так понял из ваших примеров, что break вы хотите распространить и на оператор if, верно? Ваш первый пример не имеет циклов. Если так, то я категорически против, это только запутает логику, уж лучше пользоваться оператором goto. Оператор break должен выходить только из циклов.
Далее. Я согласен, что должен быть механизм задания уровня, однако мне не очень понятно, как его сопрячь с моим механизмом. Дело вот в чём. Оператор break практически всегда используется внутри оператора if. Получается запись следующего типа:
Код:
loop {
...
if (условие) break;
...
}
Я считаю, что раз break условный, то и следует его сделать
условным оператором. Предыдущая запись в моей логике будет такой:
Код:
loop {
...
break (условие);
...
}
Более того, я уже много лет именно так и работаю в С/С++ (включая оператор loop, - в точности как написано), - подобная запись настолько удобна и хорошо воспринимается, что я уже не готов от неё отказаться. Необходимо продумать, как в синтаксисе совместить опциональное задание уровня вложенности с опциональным же условием.
Zealint писал(а):
Разумно (не только циклы, но и условия). Но тогда возникает вопрос: можно ли использовать для именования циклов метки?
Тогда вопрос: а чем это отличается от оператора goto?
Zealint писал(а):
Косяк в такой логике в другом: непонятно как перемножать переменный с разной точностью. Например целое число с бесконечной точностью помножить на один байт (тут преобразование типа не требуется). Или плавающее число с одинарной точностью на плавающее с бесконечной (тут преобразование типа потребуется). Я подозреваю, что тут нужны особые правила.
А нет никаких проблем с логикой. Хорошо, сейчас формализую правила.
- Размер результата умножения равен сумме размеров операндов.
- Размер результата сложения и вычитания равен большему из двух размеров операндов плюс единица.
- Размер результата деления равен размеру делимого.
- Размер результата взятия остатка от деления равен размеру делимого.
- Размер результата возведения в степень равен 128 бит независимо от размеров операндов.
- Если размер целевой размер меньше максимального из двух размеров операндов для сложения/вычитания/умножения, или меньше размера делимого для деления, или меньше размера основания для возведения в степень, то выдаётся предупреждение.
- Если целевой размер меньше результата операции, то старшие значащие разряды отбрасываются.
На машинном уровне можно выполнять любую оптимизацию, не нарушающую эти формальные законы. Например, если результат 32-битный, то можно не использовать 64-битное умножение и не сохранять старшую половину.