Нужны ли беззнаковые целые?
Плюрализм в одной голове — это шизофрения.
Наум Коржавин
Без беззнаковых целых чисел, в принципе, можно обойтись.
В самом деле если нам вдруг понадобилось использовать число, большее 2 147 483 647
(это максимум для 32-битных целых; максимум для 32-битных беззнаковых целых — это 4 294 967 295),
то надо просто перейти на 64-битные целые числа.
По этому пути пошёл язык Java.
В нём нет беззнаковых целых.
Живи мы в идеальном мире, который был бы создан точно по нашим вкусам, то беззнаковые целые следовало бы исключить из языка нашей мечты.
Но приходится мириться с существующим окружением, которое часто представлено ОС Windows.
А WinAPI вовсю использует беззнаковые целые.
Если мы хотим полноценного взаимодействия наших программ с Windows, то беззнаковые целые отбрасывать нельзя.
Каков максимальный размер файла, с которым может работать ваша программа?
Попробуем разобраться.
32-разрядные версии Windows поддерживают работу с файлами длиной 4Gb.
Убеждаемся в этом, читая описание функции WinAPI
ReadFile:
BOOL ReadFile
{ HANDLE hFile, // дескриптор файла
LPCVOID lpBufer, // буфер данных
DWORD nNumberOfBytesToRead, // размер буфера: unsigned int!
LPDWORD lpNumberOfBytesRead, // прочитанных байтов: unsigned int!
LPOVERLAPPED lpOverlapped
};
Но рассмотрим функции позиционирования в файле (Borland C++ 5.5):
int _RTLENTRY _EXPFUNC fseek(FILE * __stream, long __offset, int __whence);
int _RTLENTRY _EXPFUNC fgetpos(FILE * __stream, fpos_t*__pos);
Как видим, беззнаковые целые в определении этих функций не используются.
Только целые со знаком.
Следовательно, Borland C++ 5.5 просто не позволит нам работать с файлами длиннее 2GB!
Или придётся придётся ужаться в своих желаниях?
Ограничение размера файла 2Gb — не самая острая проблема. Тем более в эпоху перехода к 64-разрядным вычислениям.
Однако необходимость взаимодействия с WinAPI вынуждает иметь в языке беззнаковые целые.
Коды символов — целые без знака или со знаком?
Нет, наверное, такого программиста, который бы ни разу в жизни не обращался к таблице символов.
Допустим, понадобилось нам узнать код, допустим, буквы «я» в кодовой странице 1251.
Это буква имеет код 255.
Пишем в программе:
int code = 'я';
cout << "print code: " << code << "\n";
Будет выведено:
print code: -1;
Что за чертовщина?
Как это символ может иметь отрицательный код?
В таблице символов нет ни одного отрицательного кода!
Увы, отрицательные коды символов — горькая правда.
Се ля ви.
Казалось бы, ничего страшного, надо об этом просто помнить.
Но допустим, что у вас есть массив, где каждому символу соответствует некое целое число.
int ar [256]; // массив неких характеристик для всех 8-битных символов
ar ['z'] = func('z'); // заполняем некую характеристику символу 'z'
ar ['я'] = func('я'); // заполняем некую характеристику символу 'я'
Увы, так нельзя.
Поскольку 'я' имеет код, равный -1, то это вызовет обращение к элементу массива с индексом -1.
Это не проблема для языков типа PHP.
Но что делать в языках типа C/C++?
Нет, нам всё-таки не нравится работать с отрицательными индексами масива.
И как это преоделеть?
Может, взять на вооружение такой трюк:
int ar0 [128]; // массив для отрицательных индексов [-128;-1]
int ar1 [128]; // массив для индексов [0;127]
ar1 ['z'] = func('z'); // обращаемся к массиву с положительными индексами
ar1 ['я'] = func('я'); // написано ar1 ['я'] (или ar1 [-1]), но на самом
// деле обращаемся к ar0 [127]
Но вообще-то компилятор не гарантирует, что массивы «ar0» и «ar1»
будут размещены в смежных участках памяти.
Стандарт C/C++ (впрочем, и любого другого языка!) никак не гарантирует
размещение соседних переменных так же по соседству и в памяти.
Да и зачем делать такое, неужели нет нормальных решений?
Попробуем пойти в обход.
unsigned int code2 = 'я';
cout << "print code2: " << code2 << "\n";
Получается:
print code3: 4294967295;
Идиотизм! Что за гениальный человек сделал коды отрицательными?
Может, это всё из-за Страуструпа?
Ведь в чистом C символьные константы типа 'я' имеют тип int, а в C++ — тип char.
Переменные и константы типа char не могут иметь значения больше 127.
Но если они имеют тип int, то int может позволить значения больше 127!
Проверяем с помощью компилятора чистого C — TinyCC.
int n = 'я';
printf("code: %d\n", n);
printf("size: %d\n", sizeof('я'));
Получаем:
code: -1
size: 4
Так, у Страуструпа есть отмазка: в C такой же бардак.
Во всём виноват Ритчи.
Вот его явка с повинной и чистосердечное признание
(Керниган, Ритчи, «Язык программирования C», второе издание):
Являются ли переменные типа char знаковыми (signed) или беззнаковыми,
зависит от конкретной системы, но выводимые на экран и печать символы
всегда имеют положительные коды.
Вот так.
Иными словами, хотите — придерживайтесь правил правостроннего движения.
А можете — левостороннего, всё зависит от конкретной страны.
Ну а нам-то что делать, когда нет стандартов де-юре, но есть стандарты де-факто?
Пробуем побороться ещё:
int code3 = (unsigned char)'я';
cout << "print code3: " << code3 << "\n";
Уф, наконец-то! Но как тщательно были расставлены грабли!
print code3: 255;
В конце концов, если уж нам не нравится, что коды символов отрицательные,
а в справочных таблицах написано совсем другое, то можем эти таблицы просто переписать.
Теперь можно вздохнуть спокойно?
Нет, погодите.
Как бы не так!
Первый попавшийся под руку учебник по C говорит нам, что...
коды символов меняются от 0 до 255!
Как же так?
Мы же только что убедились, что символы хранятся в переменных типа char,
которые имеют значения от -128 до 127!
Давайте проверим, выведем на консоль символы в кодировке 866:
cout << "\x8f\xe0\xa8\xa2\xa5\xe2\n";
На консоли появится:
Привет
Что же получается?
Строковые константы состоят из элементов типа char (дипазон значений — от -128 до 127),
но в эти строки мы должны записывать коды от 0 до 255?
Именно так!
Язык C просто не предусматривает ввод отрицательных кодов внутри строковых констант!
И это ещё не всё.
При сортировке строк необходимо применять либо собственные функции сравнения
(которые учитывают, что 'я' всё-таки больше 'z'),
либо рассматривать сортируемые строки как unsigned char.
Каков же вывод можно сделать из наших расследований?
Можно ли сделать так, чтобы коды символов были бы положительными?
Ведь есть же языки, где нет таких таких проблем.
В Бейсике ведь всё нормально!
Выходы видятся такими (в зависимости от типизации).
-
Статическая типизация, беззнаковых целых в языке не существуют.
В этом случае придётся чем-то платить.
Для хранения 8-битных символов в таком случае понадобится в теории 9 битов, а на практике 16.
16-битные символы UNICODE придётся хранить в 32 битах.
Велика ли эта цена — решать создателям будущих языков.
-
Статическая типизация, беззнаковые целые в языке есть.
Тогда символьные константы вполне могут быть unsigned char!
Это один из аргументов «за» в спорах о праве на существование беззнаковых целых.
-
Динамическая типизация.
В таких языках не имеет особого значения, какого типа та или иная величина.
Если в программе будет написано «a = 'я';»,
то переменная «a» будет иметь строковый тип, а значением будет эта самая 'я'.
А если понадобится узнать код этого символа?
Так для этого в таких языках существует функция val(), которая возвращает код символа.
А при написании функции всё в наших руках: хочу — и все коды будут положительные, хочу — всё наоборот.
В этом случае лишние «технические детали» будут просто спрятаны и не видны программисту.
Сравнение знаковых и беззнаковых целых
Если дать право на жизнь беззнаковым целым, то мы должны быть готовы и к другим фокусам.
Например, к таким:
unsigned int ui = 4294967290;
int i = -1;
cout << "i < ui : ";
if (i < ui)
cout << "true";
else
cout << "false";
cout << "\n";
Несмотря на то, что -1 < 4294967290, выводится:
i < ui : false
Переменная «i» трактуется, как целое без знака!
Да, и тут засада...
Но избежать её — вполне посильная задача.
Беззнаковых целые в циклах
Применение беззнаковых целых в качестве счётчика в циклах иногда чревато непредсказуемыми последствиями.
char ar [N];
for (unsigned int i=N-1; i >= 0; --i)
ar [i] = ...;
В том случае, когда i = 0, то в конце цикла выполнится «--i».
И тогда беззнаковый ноль превратится в ...
Во что?
В 4294967295?
Ох!
Уж лучше б в элегантные шорты...
Ведь это означает, что условие «i >= 0» всегда истинно и цикл не прервётся, как ожидается, а продолжится дальше.
И, следовательно, здесь имеет место бесконечный цикл, в котором «i» меняет свои значения от 4294967295 до 0.
Причина — некорректное сравнение беззнаковых и знаковых целых при проверке условия продолжения цикла.
Это мы рассматривали в предыдущем пункте.
Поведение при переполнениях
Описанные выше проблемы связаны с некорректным поведением программ в случае переполнения.
Переполнение на аппаратном уровне отследить вполне возможно.
Архитектурой IBM/360/370 и ЕС ЭВМ преусмативалось программное прерывание при переполнениях.
В архитектуре x86 есть специальный регистр флагов, в котором флаги CF и OF сигнализируют об
имевших место переполнениях при арифметических операциях.
Казалось бы, ничто не мешает как-то реагировать на них.
Но в C/C++ этого нет, виной тому, скорее всего, необхомость межплатформенной переносимости.
Ведь этот язык должен одинаково себя вести на сотнях платформ, включая компьютеры из 1970-х годов:
PDP, DEC, ICL.
Язык C возник в начале 1970-х и должен вести себя так же, как и 40 лет назад.
Поэтому и современные компиляторы игнорирует аппаратные возможности реакции на переполнения.
А вот язык Ruby, не имея за спиной ветхозаветного унаследованного кода, может себе позволить работать правильно.
В Ruby при возникновении переполнения ячейка для хранения
значения переменной «расширяется» таким образом, чтобы вместить без потерь
новое значение. Т.е. «на ходу» меняется разрядность вычислений.
Именно поэтому Ruby не проваливает тест на вычисление факториала.
Большинство языков «ломается» на 12!
или 13!
В их числе Pascal, который хвалят за его «математичность».
Выводы по использованию беззнаковых целых.
Они более применимы для языков со статической типизацией.
Вот они:
-
В будущих языках всё-таки лучше иметь беззнаковые целые.
-
Переполнение должно рассматриваться как ошибка. Ошибки должны в обязательном порядке рассматриваться.
Программа не должна молча проглатывать такие ошибки.
Ошибки не должны замалчиваться!
-
При присвоении целым числам некорректных значений тоже должна генерироваться исключительная ситуация.
-
Необходимо сделать корректным сравнение целых со знаком и без. Если сравниваются unsigned int A и int B, то
- Если у А старший бит равен 1 (т.е. A > 2 147 483 647), то A > B
- Если у B старший бит равен 1 (т.е. B отрицательно), то A > B
- Иначе старшие биты у обоих чисел равны 0 и результат сравнения A > B будет корректен.
Что ещё почитать на эту тему
Опубликовано: 2012.09.25, последняя правка: 2023.11.04 21:09
Отзывы
✅ 2012/10/05 08:32, utkin #0
В языке программирования Scheme реализована такая модель чисел, которая наиболее соответствует современным математическим представлениям. Каждое число там является комплексным. Однако оно имеет и другие статусы. Например — 3 это и целое число и вещественное число и +3 и комплексное число. Скажем так все целые числа языка являются вещественными. Все вещественные числа являются комплексными. И хотя для внутреннего представления используются разные форматы, эта модель полностью прозрачна для программиста — разные числа могут участвовать в одном выражении без явного преобразования. Исключения могут составлять только целые числа — в ряде реализации они являются длинными и могут содержать произвольное число разрядов. Это при операциях, результатом которого указано не целое число может привести к переполнению Обязанности по отслеживанию такой ситуации лежат на программисте.✅ 2014/09/28 15:51, 1231231 #1
"Переменная «ui» трактуется, как целое со знаком! Да, и тут засада... Но избежать её — вполне посильная задача."
Наоборот это "i" трактуется, как целое без знака.✅ 2014/09/29 14:16, Автор сайта #2
Да, Вы правы, сейчас исправлю✅ 2016/08/13 23:30, rst256 #3
Увы, так нельзя. Поскольку 'я' имеет код, равный -1, то это вызовет обращение к элементу массива с индексом -1. Это не проблема для языков типа PHP. Но что делать в языках типа C/C++? char s[256]; char *s1=&(s[127]); //и индексируйте в обе стороны на здоровье Идиотизм! Что за гениальный человек сделал коды отрицательными? Бред, не понимаю в чем проблема, signed char — будут отрицательные, unsigned char — не будут. И не все ли равно в какую сторону сортировать?
И по вопросу переполнения. Gcc: 6.53 Built-in Functions to Perform Arithmetic with Overflow Checking
The following built-in functions allow performing simple arithmetic operations together with checking whether the operations overflowed. — Built-in Function: bool __builtin_add_overflow (type1 a, type2 b, type3 *res) — Built-in Function: bool __builtin_sadd_overflow (int a, int b, int *res) — Built-in Function: bool __builtin_saddl_overflow (long int a, long int b, long int *res) ... И это: Clang provides the following checked arithmetic builtins: bool __builtin_add_overflow (type1 x, type2 y, type3 *sum); bool __builtin_sub_overflow (type1 x, type2 y, type3 *diff); bool __builtin_mul_overflow (type1 x, type2 y, type3 *prod); ...✅ 2016/08/19 11:36, Автор сайта #4
индексируйте в обе стороны на здоровье... не все ли равно в какую сторону сортировать?.. не понимаю в чем проблема Это в Паскале индексы в массиве имеют произвольные величины:M [m..n] В Си индексы начинаются с нуля. Адресом массива является адрес его нулевого элементаM [n] & M [0] == M Проблема заключается в нелогичности и непоследовательности. Читаем в десяти справочниках о том, что код символа «я» равен 255, а потом в десяти компиляторах убеждаемся, что это не так. Читаем наставления гуру от программирования, что выход за границы массива является одной из самых распространённых ошибок в Си, но сами стимулируем эти ошибки отрицательными диапазонами.✅ 2018/11/29 05:19, Freeman #5
Справедливости ради, fseek перемещает позицию в обе стороны, так что смещение может быть отрицательным по задаче. Про fpos_t сходу не скажу, но возможно, файловый API стандартной библиотеки Си предполагает возврат отрицательных позиций в качестве признака ошибки или выхода за границы файла.✅ 2018/12/02 18:00, БудДен #6
На самом деле, число и компьютерное число — это просто разные типы. Целые числа правильно сделаны в Лиспе. Но нельзя сказать, сколько ресурсов (памяти и времени) потребует сложение чисел. Компьютерные числа не являются числами, а являются, в лучшем случае, кольцами по модулю, да и то не факт. При защите от переполнения операция сложения двух чисел определена не на полном наборе чисел, а на треугольнике, вырезанном из квадрата.
Соответственно, вопрос ортогонален тому, статическая ли типизация.
Можно принять то или иное решение. Я в Яре планировал завести и компьютерные, и математические целые. Причём даже единственного решения о том, что делать при переполнении, тоже принять нельзя, и этот вопрос представляет собой ещё одну отдельную ось в пространстве решений. Отсюда возникает идея модульной арифметики.
С действительными числами всё ещё хуже. Теоретически, можно пойти по определению и описать действительное число функцией(n), выдающий n-й член последовательности, сходящейся к этому числу. Если будут заданы ещё и свойства сходимости этой последовательности, то можно будет даже проводить операции над ними. Но мне никогда не попадался такой подход, разве только в Mathematica есть что-то подобное. Числа с плавающей точкой — это особый объект. Слава Богу, хотя бы есть стандарт. Хотя не факт, что этот стандарт хорошо подходит для данной конкретной задачи.✅ 2018/12/03 08:44, Freeman #7
Про математическую библиотеку для ЯП с критикой IEEE 754 было когда-то обсуждение на OSDev.ru: http://osdev.ru/viewtopic.php?f=18&t=1057✅ 2018/12/07 04:38, rst256 #8
В Си индексы начинаются с нуля. Адресом массива является адрес его нулевого элемента В Си индексы могут быть отрицательными, это просто смешение указателя. Делайте указатель на середину массива (т.е. на символ с кодом равным 0) и индексируйте на здоровье.✅ 2018/12/07 08:36, Автор сайта #9
В том то и дело, что я этого не хочу! Я хочу соблюдать правила, а по правилам индексы имеют нумерацию от 0 до n-1. И хочу, чтобы компилятор следил за выполнением правил. Но он (компилятор Си), не следит. Это плохо, но позволяет выходить за границы массива — прямо сейчас, без дополнительных усилий можно просто написать M[-1] — и это будет работать.✅ 2018/12/28 11:12, kt #10
Немного не по теме, но хотелось бы обратить внимание, что пример с символом «я», где всплыл недостаток «беззнаковости», получился из-за того, что потребовалось обращение к одному и тому же объекту и как к символу, и как к числу. И это не такой уж и редкий случай. В языке PL/1 для таких вещей имеется механизм «Defined». По образному выражению Л.Ф Штенберга: «если представить переменную в виде коробки (память) с наклеенным ярлыком-идентификатором (тип), то наложенная переменная — это коробка, на которую наклеены два ярлыка». Я бы уточнил: «несколько ярлыков». Данный механизм позволяет парировать отсутствие беззнаковых чисел (как и описано в исходной заметке) за счет «лишних» байт. Например, рассылка по всем типам символов:Declare i fixed(63), s char defined(i), рассылка (0:255) label static init((32) m1,(32) m2, (64) m3,(64) m4,(64) m5); . . . s=’я’; goto рассылка(i); // рассылка по всем типам символов . . . m5: //обработка всех русских букв, кроме ё … Есть два мелких нюанса: лишние байты используются лишь для одного текущего символа, а не для всех, но главное — defined позволяет НА САМОМ ДЕЛЕ обращаться к одному и тому же объекту и как к символу и как к числу. Никаких преобразований принципиально не требуется. В примере я наложил символ на 8-байтное число, хотя достаточно было 2-байтового, но тогда потребовались бы преобразования индекса, который по умолчанию 8 байт для 64-х разрядных программ.
Резюме: к беззнаковым числам я отношусь отрицательно, поскольку в случае их смешения со знаковыми могут быть трудно понимаемые ошибки. Но беззнаковые значения могут появиться объективно (как в примере с «я»). Один из способов парирования их отсутствия в типах — наложение переменных на числа с гарантированно нулевыми старшими байтами. При этом накладных расходов не появляется, ну, может быть, в крайнем случае, одно дополнительное присваивание.✅ 2018/12/28 22:26, Автор сайта #11
беззнаковым числам я отношусь отрицательно, поскольку в случае их смешения со знаковыми могут быть трудно понимаемые ошибки. Статическая типизация препятствует смешению. Но в том же Си всегда есть лазейки всё это обойти. А ещё если я прочитал файл, то что я прочитал? Данные какого типа? Тут статическая типизация ответа не даст.✅ 2019/01/10 07:29, utkin #12
При защите от переполнения операция сложения двух чисел определена не на полном наборе чисел, а на треугольнике, вырезанном из квадрата. При защите от переполнения все зависит от Вашего желания иметь скорость. Вы всегда можете узнать, будет ли переполнение, ДО САМОЙ ОПЕРАЦИИ. Например, анализируя старшие разряды. То же самое касается операции умножения. Единственно более сложный алгоритм предсказания может потребоваться для деления. Но там, жертвуя точностью, можно жестко обрезать число для того, чтобы уложить в нужное количество битов/байтов. Это если, разумеется, не полагаться на процессор, а делить самостоятельно. Вариант 2 — преобразовывать числа (округлять) перед делением. Тоже требует предсказания результата до операции.✅ 2019/01/10 12:23, Автор сайта #13
Ещё полсотни лет назад флаги переполнения появились для того, чтобы не проверять операнды до операции. Это был шаг вперёд. Это хорошо вписалось в язык ассемблера. Но плохо вписалось в дизайн языков высокого уровня. Как проверить наличие переполнения в Rust, Oberon-2 или Haskell? И чтоб это было удобно? Про Си уже молчу.✅ 2019/01/10 12:53, utkin #14
Ну, как как. Берете старшие разряды при сложении. Если они в сумме дают перенос, значит однозначное переполнение. Ну вот простой пример, у нас два бита всего: 10 +11 Это однозначное переполнение, потому что старшие разряды требуют переноса в более старший разряд которого точно нет в нашей системе. То есть такой вот примитив. Единственно нужно отслеживать пограничное состояние, когда прочие разряды переходящим итогом могут дать не очевидную единицу переноса. Но это тоже решается, нужно внимательно посмотреть на вопрос.
С умножением аналогично. Если у вас 25 * 5, это однозначно три разряда. То есть при умножении разряды складываются. Для дробных чисел ещё нужно учитывать арифметику вычисления позиции запятой в разрядах. То есть предсказать все это можно. Проблема в том, что стоимость предсказания намного выше стоимости самого умножения.И чтоб это было удобно? Да смотря как это нужно. Самый простой вариант, например, функция какая-нибудь. Типа IsCorrectAdd(x, y) — true, операция без переполнения, false — переполнение при сложения. Или наоборот. Сейчас в современных языках полно доппримочек. Можно навалять какой-нибудь хелпер к классу числа. Все зависит от инструментов в языке.Статическая типизация, беззнаковых целых в языке не существуют. В этом случае придётся чем-то платить. Для хранения 8-битных символов в таком случае понадобится в теории 9 битов, а на практике 16. 16-битные символы UNICODE придётся хранить в 32 битах. Велика ли эта цена — решать создателям будущих языков. Я тут ничего не понял. Почему 8 битные символы перестанут помещать? Со знаком минус они будут представляться ИМЕННО в ПРОГРАММЕ. Везде в остальном НИЧЕГО не изменится. Вот вообще никак. Это неудобно только программисту — ему нужно будет смотреть какую-нибудь отдельную версию ASCII, например. Где будут минуса, чтобы их правильно представлять в ПРОГРАММЕ. Программа будет корректно и читать такие строки из других программ. Потому что они по-другому ПРЕДСТАВЛЯЮТСЯ, ВЫГЛЯДЯТ. Но их действительные значения НЕ МЕНЯЮТСЯ. То есть если перевести в двоичный вид, то они будут везде иметь один и тот же вид.✅ 2019/01/11 13:58, Автор сайта #15
Берете старшие разряды при сложении. Зачем брать? Я лишь хочу, чтобы выражение «а+б» давало правильный результат. А если не давало, то был бы сигнал об этом. Во что превратится код, если перед каждым арифметическим действием проверять операнды на предмет потенциального переполнения?Самый простой вариант, например, функция какая-нибудь. Типа IsCorrectAdd(x, y) Это как раз неудобно, есть способы получше. Скоро будет статья на эту тему.✅ 2019/01/11 18:24, MihalNik #16
чтобы выражение «а+б» давало правильный результат. Нужно выбирать подходящие операнды — заведомо большего размера. Если их процессор не предоставляет — разбивать вручную, некоторые ЯП/библиотеки так умеют. Это масштабируемое решение. Потому что если складывать, например, 1000 чисел, то проверять каждый раз флаг недостаточно, нужно где-то хранить старшую часть разрядов.✅ 2019/01/12 07:35, utkin #17
Ну тогда да, брать под результат тип, который заведомо может хранить нужный результат. Складываете байты, значит результат нужно помещать в слово.Во что превратится код, если перед каждым арифметическим действием проверять операнды на предмет потенциального переполнения? Ну, это нужно далеко не всегда. Такие ошибки случаются, но как часто? Значит это не острая проблема и такие проверки нужны только в критических местах.✅ 2021/03/27 12:51, Виталий Монастырский #18
У меня все одновременно и так и наоборот. В языке не используются знаковые числа вообще. Все виды чисел автоматически представляются только беззнаковыми целыми, а для реализации знака используется отдельный булевый тип. НО! При этом, числовой тип работает с числами только в представлении их, как знаковых. То есть нет проблемы в том, что 3-5=0. Однако, совсем отказаться от беззнаковых чисел нельзя из-за итераторов. Поэтому у меня есть отдельный тип "итератор", который использует только натуральные числа от 1 до 64-бит. Естественно — беззнаковые. И этот тип данных не используется для представления чисел, а только как итератор во всех принятых для этого случаях. Так что в целом смыслью о том, что числа должны везде и всегда рассматриваться как знаковые — согласен, но с мыслью о том, что беззнаковый тип вообще не нужен — нет. Да и представление чисел и их обработка в процессоре в виде беззнаковых намного проще и быстрее, и обрабатывать такие числа в мат-операциях намного удобнее, так что пользоваться знаковым представлением на низком уровне я категорически отказался. Добавить свой отзыв
Написать автору можно на электронную почту mail(аt)compiler.su
|