- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 1. Введение
- •1.1. Новые инструменты
- •1.2. Новые приемы
- •1.3. Новый подход
- •Глава 2. Добро пожаловать в Лисп
- •2.1. Форма
- •2.2. Вычисление
- •2.3. Данные
- •2.4. Операции со списками
- •2.5. Истинность
- •2.6. Функции
- •2.7. Рекурсия
- •2.8. Чтение Лиспа
- •2.9. Ввод и вывод
- •2.10. Переменные
- •2.11. Присваивание
- •2.12. Функциональное программирование
- •2.13. Итерация
- •2.14. Функции как объекты
- •2.15. Типы
- •2.16. Заглядывая вперед
- •Итоги главы
- •Упражнения
- •Глава 3. Списки
- •3.1. Ячейки
- •3.2. Равенство
- •3.3. Почему в Лиспе нет указателей
- •3.4. Построение списков
- •3.5. Пример: сжатие
- •3.6. Доступ
- •3.7. Отображающие функции
- •3.8. Деревья
- •3.9. Чтобы понять рекурсию, нужно понять рекурсию
- •3.10. Множества
- •3.11. Последовательности
- •3.12. Стопка
- •3.13. Точечные пары
- •3.14. Ассоциативные списки
- •3.15. Пример: поиск кратчайшего пути
- •3.16. Мусор
- •Итоги главы
- •Упражнения
- •Глава 4. Специализированные структуры данных
- •4.1. Массивы
- •4.2. Пример: бинарный поиск
- •4.3. Строки и знаки
- •4.4. Последовательности
- •4.5. Пример: разбор дат
- •4.6. Структуры
- •4.7. Пример: двоичные деревья поиска
- •4.8. Хеш-таблицы
- •Итоги главы
- •Упражнения
- •Глава 5. Управление
- •5.1. Блоки
- •5.2. Контекст
- •5.3. Условные выражения
- •5.4. Итерации
- •5.5. Множественные значения
- •5.6. Прерывание выполнения
- •5.7. Пример: арифметика над датами
- •Итоги главы
- •Упражнения
- •Глава 6. Функции
- •6.1. Глобальные функции
- •6.2. Локальные функции
- •6.3. Списки параметров
- •6.4. Пример: утилиты
- •6.5. Замыкания
- •6.6. Пример: строители функций
- •6.7. Динамический диапазон
- •6.8. Компиляция
- •6.9. Использование рекурсии
- •Итоги главы
- •Упражнения
- •Глава 7. Ввод и вывод
- •7.1. Потоки
- •7.2. Ввод
- •7.3. Вывод
- •7.4. Пример: замена строк
- •7.5. Макрознаки
- •Итоги главы
- •Упражнения
- •Глава 8. Символы
- •8.1. Имена символов
- •8.2. Списки свойств
- •8.3. А символы-то не маленькие
- •8.4. Создание символов
- •8.5. Использование нескольких пакетов
- •8.6. Ключевые слова
- •8.7. Символы и переменные
- •8.8. Пример: генерация случайного текста
- •Итоги главы
- •Упражнения
- •Глава 9. Числа
- •9.1. Типы
- •9.2. Преобразование и извлечение
- •9.3. Сравнение
- •9.4. Арифметика
- •9.5. Возведение в степень
- •9.6. Тригонометрические функции
- •9.7. Представление
- •9.8. Пример: трассировка лучей
- •Итоги главы
- •Упражнения
- •Глава 10. Макросы
- •10.1. Eval
- •10.2. Макросы
- •10.3. Обратная кавычка
- •10.4. Пример: быстрая сортировка
- •10.5. Проектирование макросов
- •10.6. Обобщенные ссылки
- •10.7. Пример: макросы-утилиты
- •10.8. На Лиспе
- •Итоги главы
- •Упражнения
- •Глава 11. CLOS
- •11.1. Объектно-ориентированное программирование
- •11.2. Классы и экземпляры
- •11.3. Свойства слотов
- •11.4. Суперклассы
- •11.5. Предшествование
- •11.6. Обобщенные функции
- •11.7. Вспомогательные методы
- •11.8. Комбинация методов
- •11.9. Инкапсуляция
- •11.10. Две модели
- •Итоги главы
- •Упражнения
- •Глава 12. Структура
- •12.1. Разделяемая структура
- •12.2. Модификация
- •12.3. Пример: очереди
- •12.4. Деструктивные функции
- •12.5. Пример: двоичные деревья поиска
- •12.6. Пример: двусвязные списки
- •12.7. Циклическая структура
- •12.8. Неизменяемая структура
- •Итоги главы
- •Упражнения
- •Глава 13. Скорость
- •13.1. Правило бутылочного горлышка
- •13.2. Компиляция
- •13.3. Декларации типов
- •13.4. Обходимся без мусора
- •13.5. Пример: заранее выделенные наборы
- •13.6. Быстрые операторы
- •13.7. Две фазы разработки
- •Итоги главы
- •Упражнения
- •Глава 14. Более сложные вопросы
- •14.1. Спецификаторы типов
- •14.2. Бинарные потоки
- •14.3. Макросы чтения
- •14.4. Пакеты
- •14.5. Loop
- •14.6. Особые условия
- •Глава 15. Пример: логический вывод
- •15.1. Цель
- •15.2. Сопоставление
- •15.3. Отвечая на запросы
- •15.4. Анализ
- •Глава 16. Пример: генерация HTML
- •16.1. HTML
- •16.2. Утилиты HTML
- •16.3. Утилита для итерации
- •16.4. Генерация страниц
- •Глава 17. Пример: объекты
- •17.1. Наследование
- •17.2. Множественное наследование
- •17.3. Определение объектов
- •17.4. Функциональный синтаксис
- •17.5. Определение методов
- •17.6. Экземпляры
- •17.7. Новая реализация
- •17.8. Анализ
- •Комментарии
- •Алфавитный указатель
9
Числа
«Перемалывание чисел» (решение числовых задач большого объема) – одна из сильных сторон Лиспа. В нем имеется богатый набор числовых типов, а по их поддержке он даст фору многим другим языкам.
9.1. Типы
Common Lisp предоставляет множество различных числовых типов: це лые числа, числа с плавающей запятой, рациональные и комплексные числа. Большинство функций, рассмотренных в этой главе, работают одинаково со всеми типами чисел, за исключением, может быть, ком плексных чисел, к которым некоторые из этих функций не применимы.
Целое число записывается строкой цифр: 2001. Число с плавающей за пятой, помимо цифр, содержит десятичный разделитель – точку, на пример 253.72, или 2.5372e2 в экспоненциальном представлении. Рацио нальная дробь представляется в виде отношения двух целых чисел: 2/3. Комплексное число вида a+bi можно записать как #c(a b), где a и b – два действительных числа одного типа.
Проверку на принадлежность к соответствующему типу осуществляют предикаты integerp, floatp и complexp. Иерархия численных типов пред ставлена на рис. 9.1.
Ниже приведены основные правила, согласно которым можно опреде лить, число какого типа будет получено в результате вычисления:
1.Если функция принимает хотя бы одно число с плавающей запя той, она также вернет десятичную дробь (или комплексное число, компоненты которого – десятичные дроби) . Так (+ 1.0 2) вернет 3.0, а (+ #c(0 1.0) 2) вернет #c(2.0 1.0).
2.Рациональная дробь при возможности будет сокращена до целого числа. Вызов (/ 10 2) вернет 5.
9.2. Преобразование и извлечение |
155 |
3.Комплексные числа, мнимая часть которых равна нулю, будут преоб разованы в действительные. Таким образом, вызов (+ #c(1 -1) #c(2 1)) вернет 3.
Правила 2 и 3 вступают в силу в момент считывания выражения, по этому:
> (list (ratiop 2/2) (complexp #c(1 0))) (NIL NIL)
ratio
rational bignum integer
fixnum bit
real |
|
short float |
|
|
|
||
number |
float |
single float |
|
double float |
|||
|
|
||
|
|
long float |
|
complex |
|
|
|
|
|
|
|
Рис. 9.1. Численные типы |
|
|
9.2.Преобразование и извлечение
ВЛиспе имеются средства для преобразования, а также извлечения компонентов чисел, принадлежащих любым типам. Функция float пре образует любое действительное число в эквивалентную ему десятичную дробь:
> (mapcar #’float ’(1 2/3 .5)) (1.0 0.66666667 0.5)
Также можно конвертировать любое число в целое, но к этому преобразо ванию не всегда следует прибегать, поскольку это может повлечь за со бой некоторую потерю информации. Целочисленную компоненту любо го действительного числа можно получить с помощью функции truncate:
> (truncate 1.3) 1 0.2999995
Второе значение является результатом вычитания первого значения из аргумента. (Разница в .00000005 возникает вследствие неизбежной неод нозначности операций с плавающей запятой.)
Функции floor, ceiling и round также приводят свои аргументы к цело численным значениям. С помощью floor, возвращающей наибольшее целое число, меньшее или равное заданному аргументу, а также ceiling, возвращающей наименьшее целое, большее или равное аргументу, мы
156 |
Глава 9. Числа |
можем обобщить функцию mirror? (стр. 62) для распознавания палинд ромов:
(defun palindrome? (x)
(let ((mid (/ (length x) 2))) (equal (subseq x 0 (floor mid))
(reverse (subseq x (ceiling mid))))))
Как и truncate, функции floor и ceiling вторым значением возвращают разницу между аргументом и своим первым значением:
> (floor 1.5) 1 0.5
В действительности, мы могли бы определить truncate таким образом:
(defun our-truncate (n) (if (> n 0)
(floor n) (ceiling n)))
Для получения ближайшего к аргументу целого числа существует функ ция round. В случае когда ее аргумент равноудален от обоих целых чи сел, Common Lisp, как и многие другие языки, не округляет его, а воз вращает ближайшее четное число:
> (mapcar #’round ’(-2.5 -1.5 1.5 2.5)) (-2 -2 2 2)
Такой подход позволяет сгладить накопление ошибок округления в ря де приложений. Однако если необходимо округление вверх, можно на писать такую функцию самостоятельно.1 Как и остальные функции та кого рода, round в качестве второго значения возвращает разницу меж ду исходным и округленным числами.
Функция mod возвращает второе значение аналогичного вызова floor, а функция rem – второе значение вызова truncate. Мы уже использовали функцию mod (стр. 107) для определения делимости двух чисел и для на хождения позиции элемента в кольцевом буфере (стр. 137).
Для действительных чисел существует функция signum, которая воз вратит 1, 0 или -1 в зависимости от того, каков знак аргумента: плюс, ноль или минус. Модуль числа можно получить с помощью функции abs. Таким образом, (* (abs x) (signum x)) = x.
> (mapcar #’signum ’(-2 -0.0 0.0 0 .5 3)) (-1 -0.0 0.0 0 1.0 1)
В некоторых реализациях -0.0 может существовать сам по себе, как в примере, приведенном выше. В любом случае это не играет роли, и в вычислениях -0.0 ведет себя в точности как 0.0.
1Функция format при округлении не гарантирует даже того, будет ли получе но четное или нечетное число. См. стр. 136.
9.3. Сравнение |
157 |
Рациональные дроби и комплексные числа являются структурами, со стоящими из двух частей. Получить соответствующие целочисленные компоненты рациональной дроби можно с помощью функций numerator и denominator. (Если аргумент уже является целым числом, то первая функция вернет сам аргумент, а последняя – единицу.) Аналогично функции realpart и imagpart извлекают действительную и мнимую час ти комплексного числа. (Если аргумент не комплексный, то первая вер нет это число, а вторая – ноль.)
Функция random принимает целые числа или десятичные дроби. Выра жение вида (random n) вернет число, большее или равное нулю и мень шее n, и это значение будет того же типа, что и аргумент.
9.3. Сравнение
Для сравнения двух чисел можно применять предикат =. Он возвраща ет истину, если аргументы численно эквивалентны, то есть их разность равна нулю:
>(= 1 1.0)
T
>(eql 1 1.0) NIL
Он менее строг, чем eql, так как последний также требует, чтобы его ар гументы были одного типа.
Предикаты для сравнения: < (меньше), <= (меньше или равно), = (равно), >= (больше или равно), > (больше) и /= (не равно) . Все они принимают один или более аргументов. Вызванные с одним аргументом, они всегда возвращают истину. Для всех функций, кроме /=, вызов с тремя и более аргументами:
(<= w x y z)
равноценен объединению попарных сравнений:
(and (<= w x) (<= x y) (<= y z))
Так как /= возвращает истину, лишь когда ни один из аргументов не ра вен другому, выражение:
(/ w x y z)
эквивалентно
(and (/= w x) (/= w y) (/= w z) (/= x y) (/= x z) (/= y z))
Существуют также специализированные предикаты zerop, plusp и minusp, принимающие только один аргумент и возвращающие истину, если он =, > или < нуля соответственно. Эти предикаты взаимоисключающие. Что касается -0.0 (если реализация его использует), то, несмотря на знак минус, это число = 0