2017-01-17 2 views
2

Я читаю SICP и делать упражнения 2.5:SICP Упражнение 2.5 - селектор ведет себя непостоянно

Exercise 2.5. Показать, что мы можем представлять пары неотрицательных целых чисел используя только числа и арифметические операции, если мы представим пару a и b как целое число, являющееся продуктом 2^a*3^b. Дайте соответствующие определения процедур cons, car, и cdr.

Вот мое решение:

;;; Exercise 2.5 
;;; ============ 

(define (cons x y) 
    (* (expt 2 x) 
    (expt 3 y))) 

(define (car z) 
    ; n is a power of 2, which is greater than z 
    (let ((n (expt 2 (ceiling (/ (log z) (log 2)))))) 
    (/ (log (gcd z n)) (log 2)))) 

(define (cdr z) 
    ; n is a power of 3, which is greater than z 
    (let ((n (expt 3 (ceiling (/ (log z) (log 2)))))) 
    (/ (log (gcd z n)) (log 3)))) 

Мой код хорошо работает с относительно небольшими тестовыми:

(define x 12) 
(define y 13) 
(define z (cons x y)) 

(car z) 
;Value: 12. 
(cdr z) 
;Value: 12.999999999999998 

Однако он производит неправильные результаты, когда число растет больше:

(define x 12) 
(define y 14) 
(define z (cons x y)) 

(car z) 
;Value: 12. 
(cdr z) 
;Value: 2.8927892607143724 <-- Expected 14 

Я хочу знать, что не так с моя реализация. Что-то не так с алгоритмом? Идея заключается в том, что наибольший общий генератор z = 2^x * 3^y и n (мощность 2, которая больше z) составляет точно 2^x.

Если мой алгоритм верен, это непоследовательность вызвана ошибкой округления и/или переполнением?

+0

В ваших определениях есть асимметрия, которая выглядит неправильно. Я подозреваю, что вы написали 'cdr', используя copy-and-paste. – molbdnilo

+0

@molbdnilo Да, я так и думал, что они аналогичны. Оба 'car' и' cdr' use '(потолок (/ (log z) (log 2)))', потому что для 'z = 2^x * 3^y',' log_2 (z) 'будет больше, чем как 'x', так и' y', тогда как 'log_3 (z)' гарантированно будет больше, чем 'y'. –

+0

@SunQingyao Я просто изменил '2' на' 3' в определении 'cdr', и я действительно получаю 14.0 вместо 2.89, поэтому он, кажется, имеет эффект. – Sylwester

ответ

2

Одним из решений является предотвращение чисел с плавающей запятой.

Рассмотрим max-power-dividing, которая находит максимальный показатель k таким образом, что p^k разделяющую n:

(define (max-power-dividing p n) 
    (if (zero? (remainder n p)) 
     (+ 1 (max-power-dividing p (/ n p))) 
     0)) 

Тогда мы можем написать:

(define (car z) (max-power-dividing 2 z)) 
(define (cdr z) (max-power-dividing 3 z)) 

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

+0

Благодарим вас за предоставление другого решения этого вопроса. Однако я хотел бы улучшить свое решение, а не просто использовать другое решение. В любом случае, отладка - это половина удовольствия от кодирования. Итак, скажите, пожалуйста, как заставить MIT-Scheme не использовать вычисления с плавающей запятой. (Также, мой алгоритм, похоже, имеет тенденцию роста theta-of-log (n), в то время как у вас есть порядок theta-of-n роста ...) –

+0

@SunQingyao Я ожидаю, что 'log' обязательно является плавающей точкой, но вы можете проверить документы. что касается эффективности, вы можете улучшить код, приведенный здесь, стандартным трюком * удвоения * на каждом этапе итерации. –