Здравствуйте, Roman Odaisky, Вы писали:
RO>Хорошо, а как «правильно» решить следующую простейшую задачу?
RO>Круг разбит на n > 0 секторов, пронумерованых подряд числами 0, 1, ..., n – 1. Некто стоит в секторе номер 0, затем идет по кругу, делая s шагов. Куда он попадет?
Так а что означают этти загадочные числа "с потолка" в конце таблицы? Это при том-то, что реализации функции не приведено
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Здравствуйте, Roman Odaisky, Вы писали:
RO>>Хорошо, а как «правильно» решить следующую простейшую задачу?
RO>>Круг разбит на n > 0 секторов, пронумерованых подряд числами 0, 1, ..., n – 1. Некто стоит в секторе номер 0, затем идет по кругу, делая s шагов. Куда он попадет?
АТ>Так а что означают эти загадочные числа "с потолка" в конце таблицы? Это при том-то, что реализации функции не приведено
А, понял. Это правильные результаты, а не "с потолка"...
И все таки интересно, какую связь этой задачи с данной дискуссией видит автор?
Здравствуйте, Centaur, Вы писали:
АТ>>Отдельно, кстати, стоит заметить, что, несмотря на то, что большая часть машинных арифметических операций на популярных арихитектурах не требуют различения знаковых и беззнаковых типов, такие требования языков С/С++, как округление у нулю при целочисленном делении, зачастую таки приводят к генерации менее эффективного кода для знаковых типов.
C>В C++ такого требования в общем случае нет. Оно есть для положительных делимого и делителя; в противном случае поведение implementation-defined (C++03 5.6/4). В C — да, округление к нулю (C99 6.5.5/6).
Да, это так (С++ тем не менее неформально рекомендует придерживаться округления к нулю), но опять же это означает что в общем случае этот эффект имеет место.
Код изящен и эффективен, но думаю может привести к неприятным трудноуловимым ошибкам. В стандарте C99 результат целочисленного переполнения UB, как в C++2003 стандарте менее ясно, там как-то закручено по-моему.
По-моему, очевидно и то, что результат может зависеть как от архитектуры компьютера, так и даже от компилятора и остального кода. Даже в случае x86 процессора после переполнения выставляется флаг C (carry flag) и нет никакой уверенности в том, как он используется остальным сгенерированным кодом. Признаться я не знаю примера, когда подобный код привёл бы к ошибке, но чисто теоретически полагаю его небезопасным.
Ш>3) В C/C++ размерности массивов выражаются беззнаковым типом size_t. По этой причине ипользовать для индексирования знаковые типы -- создавать потенциальные дыры.
В этом случае может быть. Если очень большие массивы.
Ш>4) LUT удобнее делать с беззнаковыми типами. Классический пример.
Ш>
Ну так и я не противлюсь везде и всюду беззнаковым типам. Иногда они к месту, как здесь. Но не для случаев, когда просто некая вычисляемая сущность не может быть отрицательной.
Ш>>1) Беззнаковая арифметика имеет точно определённую семантику, в отличии от знаковой.
M>Вы не могли бы пояснить, потому что мне представляется, что это архитектурно зависимо.
Нет. Какая-то свобода представления значений целых чисел в С/С++ имеется только для знаковых типов. Для беззнаковых все жестко. (Кроме диапазонов, разумеется).
Ш>>2) В беззнаковой арифметике лего ловить переполнение.
Ш>>
M>Код изящен и эффективен, но думаю может привести к неприятным трудноуловимым ошибкам. В стандарте C99 результат целочисленного переполнения UB, как в C++2003 стандарте менее ясно, там как-то закручено по-моему.
Результат знакового целочисленного переполнения — UB. Беззнаковые типы реализуют арифметику по модулю, т.е. у них не бывает "переполнения" или, другими словами, традиционное переполнение у них приводит к однозначно оговоренному эффекту без какого-либо UB.
M>По-моему, очевидно и то, что результат может зависеть как от архитектуры компьютера, так и даже от компилятора и остального кода. M>Даже в случае x86 процессора после переполнения выставляется флаг C (carry flag) и нет никакой уверенности в том, как он используется остальным сгенерированным кодом.
Нет. Уверенность есть. На уровне языка. Если "остальной код" начинает как-то неадекватно реагировать на CF в этом случае, или это начинает зависеть от архитектуры, или от компилятора, то все это — глюки компилятора. Но речь не об этом.
Здравствуйте, Андрей Тарасевич, Вы писали:
M>>Чем? На мой взгляд существует очень ограниченный перечень случаев, когда unsigned оправдан, это флаги, коды значений, работа с памятью, может ещё пара случаев и всё. В принципе, ещё можно даже для арифметических операций, если не хватает диапазона значений знакового типа, но это уже грязный стиль и нежелательно.
АТ>Чем — это очень странный вопрос. Беззнаковые по своей природе сущности естественно представлять беззнаковыми типами. Тут, собственно, и вопросов никаких нет.
Пожалуй я с вами соглашусь где-то процентов так на 85
Но вопрос есть — что считать "беззнаковыми по своей природе сущностями"? На мой взгляд только те сущности, которым физически бессмыслено приписывать знак. Как-то: флаги, коды символов, безтиповое содержимое ячеек памяти (например при копировании откуда-то куда-то), указатели на память (их значение, а не тип на который они указывают). Наверное, ещё идентификаторы и хэши, хотя уже не уверен.
И, разумеется, это не догма. Если их применением достигается меньше ошибок и душевный комфорт программиста или например, вычисления получаются быстрее или компактнее код с беззнаковыми типами и это важно для программы, то применять надо беззнаковый тип.
Просто мой подход, что пока они не требуются их и не использовать.
АТ> И в большинестве случаев оказывается, что знаковые типы просто-напросто чаще "прощают" беспечное отношение к коду.
Что вы считаете в данном контексте безпечностью?
АТ> Во-первых, я считаю, что правильнее внимательнее относиться к коду, а не пытаться компенсировать лень использованием знаковых типов. Во-вторых, "безопасность" знаковых типов — не более чем иллюзия.
Понятно, что по-большому счёту безопасность кода не зависит от знаковости типа. Есть куча более серьёзных проблем. Полагаю Кернигана и Ритчи ещё не раз вспомнят "тихим незлым словом" за строки с нулём на конце от которых даже в C++ далеко не всегда удаётся уйти.
Просто мне кажется чуточку удобнее программировать со знаковыми типами, не переходя без нужды к беззнаковым. А раз удобнее, значит и чуточку более безопасно.
АТ>Делать из этого вывод о каких-то недостатках беззнаковых типов — это примерно то же самое, что пытаться писать Паскаль-код на языке С++, и, потерпев неудачу, заявлять, что это свидетельствует о недостатках языка С++.
Да нет недостатков у беззнаковых типов, как и у знаковых. А есть не всегда оправданное их применение, причём я думаю, что применять беззнаковые типы надо реже, чем это многими принято.
M>>Например, казалось бы логично сделать индекс/счётчик беззнаковым, но в дальнейшем может оказаться, что проще всего присваивать ему отрицательные значения для обработки особых ситуаций.
АТ>А вот это — идеологически неправильно.
Как сказать. Писал я как-то код для почти (были там некоторые извращения с перестановками и вставками) FIFO очереди , вполне ничего пошло принять отрицательный индекс начала для пустой очереди, а индекс хвоста в ноль с принудительным выставлением в (-1,0) после обработки последнего элемента. А отдельный признак заводить не хотелось.
AT> Так что говорить, что беззнаковые типы создают какие-то присущие именно им проблемы -совершенно неверно. Это не проблемы знаковых типов. Это соврешенно нормальная реальность программирования на С/С++ к которой рано или поздно все равно придется привыкать. А вот упоминающаяся тут манера безусловного использования знаковых типов — это не более чем попытка убежать от этой реальности.
Ещё раз повторю, что я не говорю о безусловном использовании, а скорее о приоритетном. То есть я воспринимаю беззнаковый тип как более сложный. Не в смысле структуры или реализации, где он как раз проще, а в смысле близости к человеческому восприятию.
P.S. Перечитал сообщение и стало где-то даже смешно, как бы не чересчур серьёзно мы обсуждаем довольно незначительные в общем-то аспекты программирования.
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Беззнаковые по своей природе сущности естественно представлять беззнаковыми типами. Тут, собственно, и вопросов никаких нет.
Я всеми руками за типизацию. Если у переменной только два значения, то надо использовать bool, а не int. Если значение не меняется, то надо использовать const. Это замечательно, когда компилятор отлавливает ошибки. Это надо использовать всегда. Всегда, за исключением одного случая. Не надо использовать unsigned. Почему? Из-за реализации в языке. Проблема в том, что компилятор не отловит ошибки. Ну не отловит он и всё. И это получается ещё хуже, т.к. появляется чувство ложной уверенности. Думаешь, я завёл unsigned — всё, все мои проблемы решены. А на самом деле ничего не решено и компилятор практически ничего не контролирует.
Я могу, конечно, поприводить примеры типа таких:
unsigned u = 0;
int i = 0;
u = i;
i = u;
// msvc80sp1 уровень предупреждений 4 — ни одного варнинга. Хотя оба из присваиваний могут вызвать проблемы...
std::vector<int> v (3);
for (size_t i = v.size() - 1; i >= 0; --i)
std::cout << v[i];
Но достаточно просто оценить какой шум вокруг этой темы вот уже на протяжении десятилетий. Десятилетиями программисты наступают на одни и те же грабли. Причём не самые плохие программисты (и вы предлагаете понаступать на них ещё немного всем нам). Почему? Они тоже надеются, что компилятор им чем-то поможет. Они тоже думают, что они повышают типизацию.
Фразы типа:
я считаю, что правильнее внимательнее относиться к коду, а не пытаться компенсировать лень использованием знаковых типов
Это из идеального мира. Действительно, давайте просто внимательно писать программы без ошибок.
В реальном мире, к сожалению, не так. Программисты работают по вечерам, по 12 часов, в спешке и т.д. Все мы писали и одинарное равно в if'е вместо двойного, и код типа примера выше и т.д. И очень бы хотелось, что бы при написании программ не надо было проявлять чудеса внимательности и памяти.
знаковые типы просто-напросто чаще "прощают" беспечное отношение к коду
Знаковые типы не просто прощают беспечное отношение к коду, они действительно заставляют больше кода работать корректно. Почувствуйте разницу. Посмотрите на цикл вверху. Что бы заставить его работать достаточно просто заменить size_t на int. Всё. Код будет корректным и рабочим.
Посмотрите пример в статья. Всё что достаточно сделать, что бы там код заработал. Правильно. Заменить unsigned на int.
Работу этого кода сломало единственное — замена signed на unsigned. Причём тоже из благих намерений. В цикле мы по каким индексам хотим пробежать? От size()-1 до 0. Может быть отрицательным? Нет. Заменяем signed на unsigned — код перестаёт работать!
В примере в статье процент в каком диапазоне может быть? От 0 до 100. Может быть отрицательным? Нет. Заменяем signed на unsigned — код (опять) перестаёт работать!
Я думаю таких примеров можно найти ещё.
Обратных же примеров, когда замена unsigned на signed ломает код, нет!
Если не считать, что у unsigned чуть больше диапазон. Но стоит ли оно того? Если уж надо хранить действительно большие числа, то имхо лучше воспользоваться int64_t. Тут и диапазон действительно больше, чем у int и проблем не будет.
Я недавно думал над этим вопросом. И изначально тоже отталкивался от точки, что всё должно быть максимально типизировано. И что надо использовать unsigned. Но потом после анализа таких примеров и кучи блогов, статей, постов по поводу ошибок связанных с unsigned, пришёл к указанному выводу — типизация везде, кроме unsigned. Ну не работает оно так как хочется. Не работает.
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Результат знакового целочисленного переполнения — UB. Беззнаковые типы реализуют арифметику по модулю, т.е. у них не бывает "переполнения" или, другими словами, традиционное переполнение у них приводит к однозначно оговоренному эффекту без какого-либо UB.
Да? Я загляну в стандарт и ещё раз перечитаю.
АТ>Нет. Уверенность есть. На уровне языка.
Хм. Буду перечитывать. Если так, то зря я раскритиковал пример.
Здравствуйте, Erop, Вы писали:
NB>>у беззнаковых чисел есть одно хорошее свойство -- все арифметические операции делаются по модулю. NB>>но постоянная борьба с варнингами при сравнении знаковых и беззнаковых просто удручает.
E>Поясни что ты имеешь в виду под этим "хорошим" свойством.
Здравствуйте, Nuzhny, Вы писали:
N>Добавлю ещё чуть-чуть: нет функций преобразования С-строк к unsigned типам. Есть atoi, atol, atoi64. И ни одного unsigned.
Неверно. 'atoi' и иже с ними — функции "мертвые" и практической ценности не представляющие. Немножко "живее" — 'sscanf', который поддерживает unsigned, но и тот, к сожалению, неполноценен. Настоящие функции преобразования строк к целым из библиотеки С — 'strtol' и 'strtoul' (плюс 'll' версии в С99), из которых последняя как раз unsigned.
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Здравствуйте, Nuzhny, Вы писали:
N>>Добавлю ещё чуть-чуть: нет функций преобразования С-строк к unsigned типам. Есть atoi, atol, atoi64. И ни одного unsigned.
АТ>Неверно. 'atoi' и иже с ними — функции "мертвые" и практической ценности не представляющие. Немножко "живее" — 'sscanf', который поддерживает unsigned, но и тот, к сожалению, неполноценен. Настоящие функции преобразования строк к целым из библиотеки С — 'strtol' и 'strtoul' (плюс 'll' версии в С99), из которых последняя как раз unsigned.
Совсем мёртвые? А я для С-строк ими пользуюсь...
Надо переходить на std::num_get<>::get().
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Во-первых, в STL — это необходимость реализации именно абстракций. Поэтому стали пользоваться новыми добрыми простыми и надёжными способами. И они, похоже, даже добрее, проще и недежней. Что делает осмысленным из использование и за пределами STL.
АТ>Во-вторых, к STL все это отностся на самом деле мало. Эти "абстакции" зарыты намного глубже.
АТ>(Причины приведения примеров не совсем понятны).
Плохо, что не понятны.
Я предалагаю STL подробно не обсуждать. просто, АФАИК идеи о unsigned индексах и размерах массивов появились вместе с изобретением STL.
Ну а с тем, что "эти абстракции" зарыты намного глубже я тоже совершенно согласен.
про то и вопрос. От чего в языке фортран или там паскаль массивы было удобно итерировать знаковым индексом, а в C++ + STL стало так трудно и опасно?
Да, от чего вообще в этом обсуждении возник STL.
Собственно ты сам начал проводить аналогии между unsigned индексом и STL-ными итераторами.
Действительно и там и там нельзя сделать "лишний шаг"
А в знаковом индексе -- можно.
После этого ты стал утверждать, что раз уж в STL-way (через указатели и итераторы) итерации лишний шаг делать нельзя, то не надо приучаться к такой возможности при итерации через индекс.
А я во делаю совершенно противоположенный вывод. Раз уж в STL-way делать "лишний шаг" опасно, а часто хочется, то это косяк STL-way.
Возможно у этого пути естьи плюсы, но конкретно это -- минус. Косяк, провоцирующий ошибки. Для того, чтобы избежать их, предлагается писать все итерации массивов через unsigned индекс. Мне кажется, что лучше избегать STL-way итерации.
Забавно, кстати, что сами авторы STL пожоже это тоже чувствовали. Потому что понаписали всяких for_each
В принципе я их понимаю. Так как простой for() при использовании итераторов и unsigned индексов действительно становится неудобным и опасным
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[4]: Нужно искать внимательнее, товарищ эксперт!
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Конечно, лучше! Вот только где бы найти такой оптимизатор... Т.е. ясно, что нет такого оптимизатора и быть не может, но все равно — лучше бы он...
А какие проблемы с принципиальным существованием такого оптимизатора?
Ведь в коде
int i;
int size;
assert( 0 <= size );
if( 0 <= i && i < size ) {
Do1();
}
if( unsigned( i ) < unsigned( size ) ) {
Do2();
}
Do1() и Do2() будут всегда вызываться одновременно.
Так что условия в if'ах совершенно эквивалентны. только перове условие понятнее.
при этом оптимизатор занет переносима такая оптимизация или нет
О том, что в большинстве случаев это будет ещё и преждевременная оптимизация я скромно стараюсь не вспоминать
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Андрей Тарасевич, Вы писали:
E>>1) реально size таким большим не бывает, так как в память плохо помещается АТ>Ни предел типа 'size_t', ни предел типа 'int' никак не связаны с "помещаемостью в память".
Ну в программировании вообще всё задачами определяется. Область деятельности-то прагматическая
Вот в обсуждаемой задаче -- итерации массива, мало есть возможностей завести такой массив, чтобы знаково/беззнаковые дыры сказались
А в каком-то абстрактном вычислительном алгоритме использовать для итерации unsigned, КМК, себе дороже.
Скажем для итерации слоёв в вычислительной схеме. Таки из номера слоя что-нибудь вычитать иногда хочется
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Неверно. 'atoi' и иже с ними — функции "мертвые" и практической ценности не представляющие.
А почему?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Практически согласен со всем, кроме вот этого
R>Обратных же примеров, когда замена unsigned на signed ломает код, нет! R>Если не считать, что у unsigned чуть больше диапазон. Но стоит ли оно того? Если уж надо хранить действительно большие числа, то имхо лучше воспользоваться int64_t. Тут и диапазон действительно больше, чем у int и проблем не будет.
Как только мы начинаем упражняться в работе с битами, проблемы сразу же появляются.
— приведение типов (всем хорошо известно, как переводить char в int: (int)(unsigned char)ch)
— сдвиг (SHR для беззнаковых и SAR для знаковых)
— битовые поля
struct bitz
{
unsigned u : 1; // принимает значения 0 и 1int i : 1; // принимает значения... сюрприз! 0 и -1, потому что старший (единственный) бит - знаковый
};