Я изучаю SICP и написал две процедуры для вычисления суммы 1 / n ^ 2, первая генерирует рекурсивный процесс, а вторая - итерационный процесс:

(define (sum-rec a b)
  (if (> a b)
      0
      (exact->inexact (+ (/ 1 (* a a)) (sum-rec (1+ a) b)))))

(define (sum-it a b)
  (define (sum_iter a tot)
    (if (> a b)
        tot
        (sum_iter (1+ a) (+ (/ 1 (* a a)) tot))))
  (exact->inexact (sum_iter a 0)))

Я проверил, что обе процедуры дают одинаковые результаты при вызове с небольшими значениями b, и что результат приближается к $ pi ^ 2/6 $, поскольку b становится больше, как и ожидалось.

Но удивительно, что вызов (sum-rec 1 250000) почти мгновенный, тогда как вызов (sum-it 1 250000) занимает вечность.

Есть ли объяснение этому?

2
volatile 18 Авг 2019 в 14:06

2 ответа

Лучший ответ

Как упоминалось в комментариях, sum-it в его нынешнем виде добавляет числа с использованием точной арифметики, которая медленнее, чем неточная арифметика, используемая в sum-rec. Чтобы сделать эквивалентное сравнение, вот как вы должны это реализовать:

(define (sum-it a b)
  (define (sum_iter a tot)
    (if (> a b)
        tot
        (sum_iter (1+ a) (+ (/ 1.0 (* a a)) tot))))
  (sum_iter a 0))

Обратите внимание, что замена 1 на 1.0 заставляет интерпретатора использовать неточную арифметику. Теперь это вернется немедленно:

(sum-it 1 250000)
=> 1.6449300668562465
5
Óscar López 18 Авг 2019 в 20:23

Вы можете перефразировать обе эти версии, чтобы они точно или неточно выполняли арифметику, просто контролируя, какое значение они используют для нуля, и полагаясь на правила заражения. Эти два в Racket, который не имеет 1+ по умолчанию, но имеет хороший синтаксис для необязательных аргументов со значениями по умолчанию:

(define (sum-rec low high (zero 0.0))
  (let recurse ([i low])
    (if (> i high)
      zero
      (+ (/ 1 (* i i)) (recurse (+ i 1))))))

(define (sum-iter low high (zero 0.0))
  (let iterate ([i low] [accum zero])
    (if (> i high)
        accum
        (iterate (+ i 1) (+ (/ 1 (* i i)) accum)))))

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

0
tfb 19 Авг 2019 в 09:35