math.ss
#lang scheme
;;; PLT Scheme Science Collection
;;; math.ss
;;; Copyright (c) 2004-2008 M. Douglas Williams
;;;
;;; This library is free software; you can redistribute it and/or
;;; modify it under the terms of the GNU Lesser General Public
;;; License as published by the Free Software Foundation; either
;;; version 2.1 of the License, or (at your option) any later version.
;;;
;;; This library is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;;; Lesser General Public License for more details.
;;;
;;; You should have received a copy of the GNU Lesser General Public
;;; License along with this library; if not, write to the Free
;;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
;;; 02111-1307 USA.
;;;
;;; ----------------------------------------------------------------------------
;;;
;;; This code is based on the Mathematical Functions in the GNU
;;; Scientific Library (GSL).
;;;
;;; Version  Date      Description
;;; 0.1.0    08/13/04  This is the initial realease of the mathematical function
;;;                    routines ported from GSL. (Doug Williams)
;;; 1.0.0    09/28/04  Removed the code for small integer powers. Marked as ready
;;;                    for Release 1.0. Added contracts for functions (Doug
;;;                    Williams)
;;; 2.0.0    11/17/07  Added unchecked versions and preparing for PLT Scheme
;;;                    V4.0. (Doug Williams)
;;; 3.0.0    06/09/08  Changes required for V4.0. (Doug Williams)
;;; 4.0.0    10/04/09  Added use of unsafe operations. Added real->float to
;;;                    protect unsafe operations. Added log10 and dB. Removed
;;;                    the unchecked operations. (Doug Williams)

(require scheme/unsafe/ops)
(require "machine.ss")

;;; -----------------------------------------------------------------
;;;
;;; Mathematical Constants
;;;
;;; The library ensures that the standard BSD mathematical constants
;;; are defined.

;;; The base of exponentials, e
(define e 2.71828182845904523536028747135)

;;; The base-2 logarithm of e, log_2(e)
(define log2e 1.44269504088896340735992468100)

;;; The base-10 logarithm of e, log_10(e)
(define log10e 0.43429448190325182765112891892)

;;; The square root of two, sqrt(2)
(define sqrt2 1.41421356237309504880168872421)

;;; The square root of one half, sqrt(1/2)
(define sqrt1/2 0.70710678118654752440084436210)

;;; The square root of three, sqrt(3)
(define sqrt3 1.73205080756887729352744634151)

;;; The constants pi, 2*pi, and 4*pi
(define pi 3.14159265358979323846264338328)
(define 2*pi (* 2.0 pi))
(define 4*pi (* 4.0 pi))

;;; Pi divided by two, pi/2
(define pi/2 1.57079632679489661923132169164)

;;; Pi divided by four, pi/4
(define pi/4 0.78539816339744830966156608458)

;;; The square root of pi, sqrt(pi)
(define sqrtpi 1.77245385090551602729816748334)

;;; Two divided by the square root of pi, 2/sqrt(pi)
(define 2/sqrtpi 1.12837916709551257389615890312)

;;; The reciprocal of pi, 1/pi
(define 1/pi 0.31830988618379067153776752675)

;;; Twice the reciprocal of pi, 2/pi
(define 2/pi 0.63661977236758134307553505349)

;;; The natural logarithm of ten, ln(10), and its inverse
(define ln10 2.30258509299404568401799145468)
(define 1/ln10 (/ ln10))

;;; The natural logarithm of two, ln(2), and its inverse
(define ln2 0.69314718055994530941723212146)
(define 1/ln2 (/ ln2))

;;; The natural logarithm of pi, ln(pi)
(define lnpi 1.14472988584940017414342735135)

;;; Euler's constant, gamma
(define euler 0.57721566490153286060651209008)

;;; -----------------------------------------------------------------
;;;
;;; Infinities and Not-a-Number
;;;
;;; PLT Scheme provides +inf.0 (infinity), -inf.0 (negative
;;; infinity), +nan.0 (not a number) and -nan.0 (same as +nan.0) as
;;; inexact numerical constants.

;;; (nan? x) -> boolean?
;;;  any/c
;;; Returns true if x is equivalent to +nan.0.
(define (nan? x)
  (eqv? x +nan.0))

;;; (infinity? x) -> (or/c -1 1 #f)
;;;   x : any/c
;;; Returns 1 if x is +inf.0, -1 if x is -inf.0, and false otherwise.
(define (infinite? x)
  (if (eqv? x +inf.0)
      1
      (if (eqv? x -inf.0)
          -1
          #f)))

;;; (finite? x) -> boolean?
;;;   x : real?
;;; Returns true of x is a finite, real number.
(define (finite? x)
  (and (real? x)
       (not (nan? x))
       (not (infinite? x))))

;;; -----------------------------------------------------------------------------
;;;
;;; Elementary Functions

;;; (log1p x) -> number?
;;;   x : real?
;;; Computes the value of log(1+x) is a way that is accurate for small x.
(define (log1p x)
  (with-float (x)
    (let ((y (unsafe-fl+ 1.0 x)))
      (- (log y)
         (unsafe-fl/ (unsafe-fl- (unsafe-fl- y 1.0) x) y)))))

;;; (expm1 x) -> inexact-real?
;;;   x : real?
;;; Computes the value of exp(x)-1 in a way that is accurate for
;;; small x.
(define (expm1 x)
  (with-float (x)
    (if (unsafe-fl< (unsafe-flabs x) ln2)
        ;; Compute the taylor series S = x + (1/2!) x^2 + (1/3!) x^3 +
        ;; ...
        (let ((i 1.0)
              (sum x)
              (term (unsafe-fl/ x 1.0)))
          (let loop ()
            (set! i (unsafe-fl+ i 1.0))
            (set! term (unsafe-fl* term (unsafe-fl/ x i)))
            (set! sum (unsafe-fl+ sum term))
            (when (unsafe-fl> (unsafe-flabs term)
                              (unsafe-fl* (unsafe-flabs sum) double-epsilon))
              (loop)))
          sum)
        (unsafe-fl- (exp x) 1.0))))

;;; (hypot x y) -> inexact-real?
;;;   x : real?
;;;   y : real?
;;; Computes the value of sqrt(x^2 + y^2) in a way that avoids overflow.
(define (hypot x y)
  (with-float (x y)
    (let ((xabs (abs x))
          (yabs (abs y))
          (min 0.0)
          (max 0.0))
      (if (unsafe-fl< xabs yabs)
          (begin
            (set! min xabs)
            (set! max yabs))
          (begin
            (set! min yabs)
            (set! max xabs)))
      (when (unsafe-fl= min 0.0)
        max)
      (let ((u (unsafe-fl/ min max)))
        (unsafe-fl* max (sqrt (unsafe-fl+ 1.0 (unsafe-fl* u u))))))))

;;; (acosh x) -> inexact-real?
;;;   x : real?
;;; Computes the value of arccosh(x).
(define (acosh x)
  (with-float (x)
    (cond ((unsafe-fl> x (unsafe-fl/ 1.0 sqrt-double-epsilon))
           (unsafe-fl+ (log x) ln2))
          ((unsafe-fl> x 2.0)
           (log (unsafe-fl-
                 (unsafe-fl* 2.0 x)
                 (unsafe-fl/ 1.0
                             (unsafe-fl+ (sqrt (unsafe-fl- (unsafe-fl* x x) 1.0))
                                         x)))))
          ((unsafe-fl> x 1.0)
           (let ((t (unsafe-fl- x 1.0)))
             (log1p (unsafe-fl+ (unsafe-fl* 2.0 t) (unsafe-fl* t t)))))
          ((unsafe-fl= x 1.0)
           0.0)
          (else
           +nan.0))))

;;; (asinh x) -> inexact-real?
;;;   x : real?
;;; Computes the value of arcsinh(x).
(define (asinh x)
  (with-float (x)
    (let ((a (abs x))
          (s (if (unsafe-fl< x 0.0) -1.0 1.0)))
      (cond ((unsafe-fl> a (unsafe-fl/ 1.0 sqrt-double-epsilon))
             (unsafe-fl* s (unsafe-fl+ (log a) ln2)))
            ((unsafe-fl> a 2.0)
             (unsafe-fl* s (log (unsafe-fl+
                                 (unsafe-fl* 2.0 a)
                                 (unsafe-fl/
                                  a
                                  (sqrt (unsafe-fl+ (unsafe-fl* a a) 1.0)))))))
            ((unsafe-fl> a sqrt-double-epsilon)
             (let ((a2 (unsafe-fl* a a)))
               (unsafe-fl*
                s
                (log1p (unsafe-fl+
                        a
                        (unsafe-fl/
                         a2
                         (unsafe-fl+ 1.0 (sqrt (unsafe-fl+ 1.0 a2)))))))))
            (else
             x)))))

;;; (atanh x) -> inexact-real?
;;;   x : real?
;;; Computes the value of arctanh(x).
(define (atanh x)
  (with-float (x)
    (let ((a (abs x))
          (s (if (unsafe-fl< x 0.0) -1.0 1.0)))
      (cond ((unsafe-fl> a 1.0)
             +nan.0)
            ((unsafe-fl= a 1.0)
             (if (unsafe-fl< x 0.0) -inf.0 +inf.0))
            ((unsafe-fl>= x 0.5)
             (unsafe-fl* s
                         (unsafe-fl* 0.5
                                     (log1p (unsafe-fl/
                                             (unsafe-fl* 2.0 a)
                                             (unsafe-fl- 1.0 a))))))
            ((unsafe-fl> a double-epsilon)
             (unsafe-fl* s
                         (unsafe-fl*
                          0.5
                          (log1p (unsafe-fl+ (unsafe-fl* 2.0 a) 
                                             (unsafe-fl/
                                              (unsafe-fl* 2.0
                                                          (unsafe-fl* a a))
                                              (unsafe-fl- 1.0 a)))))))
            (else
             x)))))

;;; (ldexp x e) -> inexact-real?
;;;   x : real?
;;;   r : integer?
;;; Computes the value of x * 2^e.
(define (ldexp x e)
  (with-float (x)
    (let ((p2 (expt 2.0 e)))
      (unsafe-fl* x p2))))

;;; (frexp x) -> inexact-real? integer?
;;; Splits the number x into its normalized fraction f and exponent
;;; e, such that x = f * 2^e and 0.5 <= f < 1.
(define (frexp x)
  (with-float (x)
    (if (unsafe-fl= x 0.0)
        (values
         0.0
         0)
        (let* ((ex (ceiling (log (unsafe-fl/ (unsafe-flabs x) ln2))))
               (ei (inexact->exact ex))
               (f (ldexp x (- ei))))
          (let loop ()
            (when (unsafe-fl>= (unsafe-flabs f) 1.0)
              (set! ei (unsafe-fx+ ei 1))
              (set! f (unsafe-fl/ f 2.0))
              (loop)))
          (let loop ()
            (when (unsafe-fl< (abs f) 0.5)
              (set! ei (unsafe-fx- ei 1))
              (set! f (unsafe-fl* f 2.0))
              (loop)))
          (values
           f
           ei)))))

;;; -----------------------------------------------------------------
;;; Testing the Sign of Numbers

;;; (sign x) -> (or/c -1 1)
;;; Return the sign of a real number, 1 for positive (or zero),
;;; -1 for negative.
(define (sign x)
  (if (>= x 0) 1 -1))

;;; -----------------------------------------------------------------
;;; Testing form Odd and Even Numbers
;;;
;;; Scheme provides even? and odd?

;;; -----------------------------------------------------------------
;;; Maximimum and Minimum Functions
;;;
;;; Scheme provides min and max

;;; -----------------------------------------------------------------
;;; Approximate Comparison of Floating Point Numbers

;;; (fcmp x1 x2) -> (or/c -1 0 1)
;;;   x1 : real?
;;;   x2 : real?
;;; Compare two real for difference less than the specified epsilon.
;;; Returns -1 if x1 < x2; 0 if x1 ~=  x2; 1 if x1 > x2.
(define (fcmp x1 x2 epsilon)
  (with-float (x1 x2)
    (let ((difference (unsafe-fl- x1 x2))
          (max (if (unsafe-fl> (unsafe-flabs x1) (unsafe-flabs x2)) x1 x2)))
      (let*-values (((dummy exponent) (frexp max))
                    ((delta) (ldexp epsilon exponent)))
        (if (unsafe-fl> difference delta)
            1                             ; x1 > x2
            (if (unsafe-fl< difference (unsafe-fl- 0.0 delta))
                -1                        ; x1 < x2
                0))))))                   ; x1 ~= x2

;;; log10 and dB

;;; (log10 x) -> inexact-real?
;;;   x : real?
;;; Returns the log base 10 of x.
(define (log10 x)
  (with-float (x)
    (unsafe-fl* 1/ln10 (log x))))

;;; (dB x) -> inexact-real?
;;;   x : real?
;;; Returns the value of x in decibels, 10*log10(x).
(define (dB x)
  (with-float (x)
    (unsafe-fl* 10.0 (unsafe-fl* 1/ln10 (log x)))))

;;; Assure floats for unsafe code

;;; (real->float x) -> inexact-real?
;;;   x : real?
;;; Returns an inexact real (i.e., a float) given real x. Raises an error if x
;;; is not a real. This can be used to ensure a real value is a float, even in
;;; unsafe code.
(define (real->float x)
  (if (real? x)
      (exact->inexact x)
      (error "expected real, given" x)))

;;; (real-vector->float-vector v) -> (vectorof inexact-real?)
;;;   v : (vectorof real?)
;;; Returns a vector of inexact reals (i.e., floats) given a vector of reals, v.
;;; Raises an error if an element of v is not a real.
(define (real-vector->float-vector v)
  (build-vector
   (vector-length v)
   (lambda (i)
     (real->float (vector-ref v i)))))

;;; (with-float (x ...)
;;;   expr ...)
;;; Executes the expr's with the x's guaranteed to be floats. All of the x's
;;; must be identifiers.
(define-syntax (with-float stx)
  (syntax-case stx ()
    ((_ (x ...) expr ...)
     (for ((id (in-list (syntax->list #'(x ...)))))
       (unless (identifier? id)
         (raise-syntax-error #f
                             "not an identifier"
                             stx
                             id)))
     #`(let ((x (if (real? x)
                    (exact->inexact x)
                    (error "expected real, given" x)))
             ...)
         expr ...))))

;;; Module Contracts

(provide
 e
 log2e
 log10e
 sqrt2
 sqrt1/2
 sqrt3
 pi
 2*pi
 4*pi
 pi/2
 pi/4
 sqrtpi
 2/sqrtpi
 1/pi
 2/pi
 ln10
 1/ln10
 ln2
 1/ln2
 lnpi
 euler
 real->float
 with-float)

(provide/contract
 (nan?
  (-> any/c boolean?))
 (infinite?
  (-> any/c (or/c -1 #f 1)))
 (finite?
  (-> any/c boolean?))
 (log1p
  (-> real? number?))
 (expm1
  (-> real? inexact-real?))
 (hypot
  (-> real? real? inexact-real?))
 (acosh
  (-> real? inexact-real?))
 (asinh
  (-> real? inexact-real?))
 (atanh
  (-> real? inexact-real?))
 (ldexp
  (-> real? integer? inexact-real?))
 (frexp
  (-> real? (values inexact-real? integer?)))
 (sign
  (-> real? (or/c -1 1)))
 (fcmp
  (-> real? real? real? (or/c -1 0 1)))
 (log10
  (-> real? inexact-real?))
 (dB
  (-> real? inexact-real?))
 (real-vector->float-vector
  (-> (vectorof real?) (vectorof inexact-real?))))