Найти в Дзене
interrupt 21h

K&R: решение 2-1, 2-2, 2-3, 2-4 и 2-5

Привет! 😃🖐🏻 Сегодня решаем пять упражнений из второй главы книги "Язык программирования Си" Кернигана и Ритчи. Приступим. Упражнение 2.1. Напишите программу для определения диапазонов переменных типов char, short, int и long (как signed, так и unsigned) путем вывода соответствующих значений из заголовочных файлов, а также с помощью непосредственного вычисления. Для второго способа усложним задачу: определите еще и диапазоны вещественных типов. Сперва выведем диапазоны из заголовочных файлов. Открываем приложение Б11 и смотрим нужные нам константы. Их немного. Также выведем диапазоны вещественных чисел - пригодится: Диапазоны вещественных чисел выведем в экспоненциальном виде. Примечательно что в заголовочном файле минимальным значением вещественных чисел считается самое маленькое положительное число. Чтобы получить действительно минимальное число из диапазона нужно максимальное умножить на -1 😃 Теперь попробуем получить эти диапазоны расчетным методом. Для целых чисел всё просто:

Привет! 😃🖐🏻 Сегодня решаем пять упражнений из второй главы книги "Язык программирования Си" Кернигана и Ритчи. Приступим.

Упражнение 2.1. Напишите программу для определения диапазонов переменных типов char, short, int и long (как signed, так и unsigned) путем вывода соответствующих значений из заголовочных файлов, а также с помощью непосредственного вычисления. Для второго способа усложним задачу: определите еще и диапазоны вещественных типов.

Сперва выведем диапазоны из заголовочных файлов. Открываем приложение Б11 и смотрим нужные нам константы. Их немного. Также выведем диапазоны вещественных чисел - пригодится:

Диапазоны вещественных чисел выведем в экспоненциальном виде. Примечательно что в заголовочном файле минимальным значением вещественных чисел считается самое маленькое положительное число. Чтобы получить действительно минимальное число из диапазона нужно максимальное умножить на -1 😃

Теперь попробуем получить эти диапазоны расчетным методом. Для целых чисел всё просто: целые числа цикличны, т.е. если прибавлять к числу 1, то рано или поздно число обнулится в случае беззнакового, или станет отрицательным в случае знакового. Вспомним как хранятся целые знаковые числа в двоичной системе: у максимального положительного числа все биты равны 1, кроме старшего, а максимально отрицательного наоборот - все биты установлены в 0, кроме старшего. Значит если мы будем умножать число на 2, то рано или поздно оно станет отрицательным и это будет максимально отрицательное из диапазона, а если мы возьмет последнее положительное число, которое у нас было и прибавим к нему себя же уменьшенное на 1, то получим максимальное положительное из диапазона. Звучит сложно? 🤯 Давай на примере!

Возьмем диапазон однобайтовых чисел (Short)

Будем получать степени двойки до тех пор, пока число не станет отрицательным (в скобках буду записывать число в двоичной системе счисления):

2^0 = 1 (0000 0001) - положительное.

2^1 = 2 (0000 0010) - положительное.

2^2 = 4 (0000 0100) - положительное.

2^3 = 8 (0000 1000) - положительное.

2^4 = 16 (0001 0000) - положительное.

2^5 = 32 (0010 0000) - положительное.

2^6 = 64 (0100 0000) - положительное.

2^7 = -128 (1000 0000) - отрицательное.

Мы нашли минимальное число. Это -128. А чтобы получить максимальное нужно к последнему положительному (у нас это 2^6 = 64) прибавить себя же, уменьшенное на единицу: 64 + (64 - 1) = 127

Вот как выглядит это в двоичной системе:

(0100 0000 + (0100 0000 - 0000 0001)) =

(0100 0000 + 0011 1111) = 0111 1111

Аналогично будем искать максимальное для беззнаковых диапазонов, только с учетом того, что минимальное число это 0. В принципе, чтобы получить максимальное из беззнакового диапазона достаточно от минимального (это всегда 0) отнять 1. Но для чистоты эксперимента вычислим аналогично знаковым.

А вот над расчетом вещественных диапазонов мне пришлось поломать голову 😕 Дело в том, что в какой-то момент вещественные числа дают погрешность даже при целочисленном исчислении (возможно проблема компилятора, но странно видеть такие проблемы в gcc). Вот например вывод степеней десятки:

-2

В какой-то момент появляется необъяснимая погрешность 🤯. Но если считать степени двойки, то вроде бы без погрешностей. Еще я выяснил что вещественные числа не цикличны. Если уменьшать число, то в какой-то момент получу бесконечно малое число (NaN), которое уже не получится ни уменьшить, не увеличить. А если увеличивать, то получу бесконечно большое, т.е. бесконечность (infinyti). Я придумал алгоритм, за точность которого не могу 100% ручаться, но по крайней мере у меня он сработал. Чтобы получить максимальное вещественное число нужно получать степени двойки до тех пор, пока не достигнем бесконечности. Затем к последнему небесконечному числу нужно прибавлять числа равные степенями двойки меньшего порядка, постепенно их уменьшая до нулевого порядка, до тех пор пока накопленное число способно увеличиваться или до тех пор пока не достигнет бесконечности. А наименьшее положительное вещественное число получим путем уполовинивания до тех пор пока оно не станет NaN. Звучит сложно, я знаю 😟

-3

Как ни странно, но это сработало. Еще можно заметить, что расчетные минимальные положительные вещественные числа намного меньше значений из заголовочного файла float.h 😳 Это еще одна загадка для меня 🤔 Будем считать, что мы справились с этим упражнением - идем дальше.

Упражнение 2.2. Напишите цикл, эквивалентный приведенному выше циклу for, не используя операции && и | |.

"Вышеприведенный" цикл выглядит так:

Напишем простую программу с этим циклом. Программа будет считывать строку и выводить её:

-4

Теперь перепишем цикл. Т.к. мы еще не знаем оператор досрочного выхода из цикла break, то нужно завести переменную, которая будет содержать в себе значение условия продолжения цикла. В теле цикла при определенных условиях нужно эту переменную установить в 0, чтобы цикл завершился на следующей итерации. Цикл for нам не подойдет, т.к. при переходе на следующую итерацию увеличится счетчик i, что нам абсолютно не нужно. Значит используем более простой цикл while:

-5

Поведение программы не изменилось - значит мы справились с очередным упражнением! 😁 Решаем следующее.

Упражнение 2.З. Напишите функцию htoi(s), которая преобразует строку шестнадцатеричных цифр (учитывая необязательные элементы 0х или 0Х) в ее целочисленный эквивалент. В число допустимых цифр входят десятичные цифры от 0 до 9, а также буквы a-f и A-F.

Простое упражнение. В функции htoi будем посимвольно считывать переданную строку. Если строка не соответствует формату, то вернем 0. Также если первые два символа это "0x", то пропустим их.

-6

Работает без нареканий! 😋 А вот и следующее упражнение:

Упражнение2.4. Напишите альтернативную версию функции squeeze(s1, s2), которая бы удаляла из строки s1 все символы, встречающиеся в строке s2.

Первоначальная функция squeeze:

Напишем программу, которая будет удалять символы цифр из вводимых строк:

-7

Отлично! 💪🏻 Осталось последнее упражнение на сегодня!

Упражнение 2.5. Напишите функцию any(s1, s2), возвращающую номер первой позиции в строке s1, в которой находится какой-либо из символов строки s2, либо -1, если строка s1 не содержит ни одного символа из s2. (Стандартная библиотечная функция strpbrk делает то же самое, но возвращает указатель на найденную позицию.)

Напишем программу, которая будет показывать первый индекс символа цифры в вводимых строках:

-8

Успешно!

Итак, мы решили первые пять упражнений во второй главе 😛. Впереди еще пять и скоро мы их решим. Хорошего дня!

#k&r #c #programming