;;; Private but portable code.

(define FIXME 'FIXME)

(define precision-bits    ; IEEE double has 53 bits of precision
  (let loop ((bits 0)
             (x 1.0))
    (if (= x (+ x 1.0))
        bits
        (loop (+ bits 1)
              (* 2.0 x)))))

(define (check-flonum! name x)
  (if (not (flonum? x))
      (error (string-append "non-flonum argument passed to "
                            (symbol->string name))
             x)))

;;; Given a symbol naming a flonum procedure and a generic operation,
;;; returns a flonum procedure that restricts the generic operation
;;; to flonum arguments and result.

(define (flop1 name op)
  (lambda (x)
    (check-flonum! name x)
    (let ((result (op x)))
      (if (not (flonum? result))
          (error (string-append "non-flonum result from "
                                (symbol->string name))
                 result))
      result)))

(define (flop2 name op)
  (lambda (x y)
    (check-flonum! name x)
    (check-flonum! name y)
    (let ((result (op x y)))
      (if (not (flonum? result))
          (error (string-append "non-flonum result from "
                                (symbol->string name))
                 result))
      result)))

(define (flop3 name op)
  (lambda (x y z)
    (check-flonum! name x)
    (check-flonum! name y)
    (check-flonum! name z)
    (let ((result (op x y z)))
      (if (not (flonum? result))
          (error (string-append "non-flonum result from "
                                (symbol->string name))
                 result))
      result)))

;;; Given a flonum x and a list of flonum coefficients for a polynomial,
;;; in order of increasing degree, returns the value of the polynomial at x.

(define (polynomial-at x coefs)
  (if (null? coefs)
      0.0
#;    (fma x (polynomial-at x (cdr coefs)) (car coefs)) ; doesn't help
      (fl+ (car coefs)
           (fl* x (polynomial-at x (cdr coefs))))))

;;; This uses Simpson's rule.

(define (definite-integral lower upper f . rest)
  (let* ((range (fl- upper lower))
         (kmax (if (or (null? rest)
                       (not (and (exact-integer? (car rest))
                                 (even? (car rest))
                                 (positive? (car rest)))))
                   1024 ; FIXME: must be even, should be power of 2
                   (car rest)))
         (n2 (inexact kmax))
         (h (fl/ range n2)))
    (define (loop k n sum)    ; n = (inexact k)
      (cond ((= k 0)
             (loop 1 1.0 (f lower)))
            ((= k kmax)
             (fl+ sum (f upper)))
            (else
             (let ((fn (f (+ lower (fl/ (fl* n range) n2)))))
               (loop (+ k 1)
                     (fl+ n 1.0)
                     (fl+ sum (fl* (if (even? k) 2.0 4.0) fn)))))))
    (fl/ (fl* h (loop 0 0.0 0.0))
         3.0)))

;;; Given x between x0 and x1, interpolates between f0 and f1.
;;; Can also extrapolate.

(define (interpolate x x0 x1 f0 f1)
  (let ((delta (fl- x1 x0)))
    (fl+ (fl* (fl/ (fl- x1 x) delta) f0)
         (fl* (fl/ (fl- x x0) delta) f1))))

(define (iota n)
  (do ((n (- n 1) (- n 1))
       (x '() (cons n x)))
      ((< n 0) x)))

;;; Given a exact non-negative integer, returns its factorial.

(define (fact x)
  (if (zero? x)
      1
      (* x (fact (- x 1)))))

;;; Given a non-negative integral flonum x, returns its factorial.

(define (factorial x)
  (if (flzero? x)
      1.0
      (fl* x (factorial (fl- x 1.0)))))

; eof
