12.1 Точность, неявное приведение и явное приведение

Вычисления с числами с плавающей точкой являются приблизительными. Точность чисел с плавающей точкой не обязательно коррелирует с «аккуратностью» числа. Например, 3.142857142857142857 имеет более точное приближение к pi чем 3.14159, но последнее число более «аккуратно». Точность указывает на количество бит используемых при представлении числа. Если операция объединяла числа с плавающей точкой короткого формата и длинного, то результат будет иметь длинный формат. Это правило создано для уверенности, что при вычислениях аккуратности будет как можно больше. Однако это не гарантированно. Однако, численные процедуры Common Lisp’а предполагают, что аккуратность аргумента не превышает его точность. Поэтому, когда объединяются два числа с плавающей точкой небольшой точности, результатом всегда будет число с плавающей точкой небольшой точности. Это предположение может быть изменено первым же явным преобразованием число с плавающей точкой в более точное представление. (Common Lisp никогда не преобразует числа из более точного формата в менее точный.)

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

X3J13 voted in June 1989 to address certain problems relating to floating-point overflow and underflow, but certain parts of the proposed solution were not adopted, namely to add the macro without-floating-underflow-traps to the language and to require certain behavior of floating-point overflow and underflow. The committee agreed that this area of the language requires more discussion before a solution is standardized.

For the record, the proposal that was considered and rejected (for the nonce) introduced a macro without-floating-underflow-traps that would execute its body in such a way that, within its dynamic extent, a floating-point underflow must not signal an error but instead must produce either a denormalized number or zero as the result. The rejected proposal also specified the following treatment of overflow and underflow:

These points refer to conditions floating-point-overflow and floating-point-underflow that were approved by X3J13 and are described in section 29.5.

Когда числовой функцией между собой сравниваются или объединяются рациональное и число с плавающей точкой, то вступает в силу правило неявного приведения к плавающей точке. Когда рациональное встречает число с плавающей точкой, то рациональное преобразуется в тот же формат второго числа. Для функций, например +, которые принимает более двух аргументов, может быть, что все рациональные будут вычислены и результат будет преобразован в число с плавающей точкой.

X3J13 voted in January 1989 to apply the rule of floating-point contagion stated above to the case of combining rational and floating-point numbers. For comparing, the following rule is to be used instead: When a rational number and a floating-point number are to be compared by a numerical function, in effect the floating-point number is first converted to a rational number as if by the function rational, and then an exact comparison of two rational numbers is performed. It is of course valid to use a more efficient implementation than actually calling the function rational, as long as the result of the comparison is the same. In the case of complex numbers, the real and imaginary parts are handled separately. ________________________________________________________________

Обоснование: In general, accuracy cannot be preserved in combining operations, but it can be preserved in comparisons, and preserving it makes that part of Common Lisp algebraically a bit more tractable. In particular, this change prevents the breakdown of transitivity. Let a be the result of (/ 10.0 single-float-epsilon), and let j be the result of (floor a). (Note that (= a (+ a 1.0)) is true, by the definition of single-float-epsilon.) Under the old rules, all of (<= a j), (< j (+ j 1)), and (<= (+ j 1) a) would be true; transitivity would then imply that (< a a) ought to be true, but of course it is false, and therefore transitivity fails. Under the new rule, however, (<= (+ j 1) a) is false.

___________________________________________________________________________________________________________

Для функций, которые математически ассоциативны (и возможно коммутативны), реализация Common Lisp’а может обрабатывать аргументы любым подходящим методом с ассоциативной (и возможно коммутативной) перестановкой. Это конечно не влияет на порядок вычисления форм, данный порядок всегда слева направо, как и во всех Common Lisp’овых вызовах функций. Порядок, который может быть изменён, это обработка значений аргументов. Смысл всего этого в том, что реализация может отличаться в том, какие автоматические приведения типов и в каком порядке производятся. Например, рассмотрим выражение:

(+ 1/3 2/3 1.0D0 1.0 1.0E-15)

Одна реализация может обрабатывать аргументы слева направо, складывая сначала 1/3 и 2/3 для получения 1, затем преобразовывая результат в число с плавающей точкой двойной точности для сложения с 1.0D0, затем преобразовывая и добавляя 1.0 и 1.0E-15. Другая реализация может обрабатывать значения аргументов справа налево, сначала выполняя сложение чисел с плавающей точкой 1.0 и 1.0E-15 (и возможно теряя аккуратность в процессе!), затем преобразовывая результат в число двойной точности и прибавляя 1.0D0, затем преобразовывая 2/3 к числу с плавающей точкой двойной точности, и затем преобразовывая 1/3 и добавляя его. Третья реализация может сначала просканировать все аргументы, и сгруппировав их по типам, выполнить сложения сначала одинаковых типов, затем преобразовать результаты к наиболее точному типу и сложить их. В этом случае все три стратегии являются допустимыми. Пользователь конечно может контролировать порядок обработки аргументов явно задавая вызовы вычислений, например:

(+ (+ 1/3 2/3) (+ 1.0D0 1.0E-15) 1.0)

Пользователь может также контролировать приведения, явно используя для этого функцию.

В целом, тип результата числовой функции является числом с плавающей точкой наиболее точного формата, который был в аргументах данной функции. Но если все аргументы были рационального типа, тогда результат будет рациональным (за исключением функций, который математически возвращают иррациональные результаты, в случае который используется одинарная точность с плавающей точкой)

Другое правило для комплексных чисел. Комплексные числа никогда не возвращаются из числовых функций, если только в аргументах не было использовано хоть одно комплексное число. (Исключением из этого правила являются иррациональные и трансцендентальные функции, в частности expt, log, sqrt, asin, acos, acosh и atanh. Смотрите раздел 12.5.) Когда некомплексное число встречает комплексно, то первое сначала конвертируется во второе с нулевой мнимой частью, а потом вычисляется результат.

Если любое вычисление привело к дробному результату, в котором числитель нацело делится на знаменатель, то результат немедленно преобразуется к эквивалентному целому числу. Это правило называется канонизацией дробей.

Если результат любого вычисления должен быть комплексным числом с рациональными частями и нулевой мнимой частью, то результат немедленно преобразуется в некомплексное рациональное число и равен действительной части исходного. Это называется правилом канонизации комплексного числа. Следует отметить, что это правило не применяется к комплексным числам с компонентами из чисел с плавающими точками. Таким образом #C(5 0) и 5 равны eql, а #C(5.0 0.0) и 5.0 не равны eql, но равны equalp.