math.rkt
#lang racket
;;; Racket Science Collection
;;; math.rkt
;;; Copyright (c) 2004-2011 M. Douglas Williams
;;;
;;; This file is part of the Racket Science Collection.
;;;
;;; The Racket Science Collection 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 3 of the License or
;;; (at your option) any later version.
;;;
;;; The Racket Science Collection is distributed in the hope that it will be
;;; useful, but WITHOUT 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 the Racket Science Collection.  If not, see
;;; <http://www.gnu.org/licenses/>.
;;;
;;; -----------------------------------------------------------------------------
;;;
;;; 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. (MDW)
;;; 1.0.0    09/28/04  Removed the code for small integer powers. Marked as ready
;;;                    for Release 1.0. Added contracts for functions (MDW)
;;; 2.0.0    11/17/07  Added unchecked versions and preparing for PLT Scheme
;;;                    V4.0. (MDW)
;;; 3.0.0    06/09/08  Changes required for V4.0. (MDW)
;;; 3.1.0    10/04/09  Added use of unsafe operations. Added real->float to
;;;                    protect unsafe operations. Added log10 and dB. Removed
;;;                    the unchecked operations. (MDW)
;;; 4.0.0    05/12/10  Changed the header and restructured the code. Changed
;;;                    the infinite? to return a boolean and added infinite for
;;;                    the old functionality. (MDW)
;;; 4.0.1    05/16/10  Moved unsafe ops utility function to unsafe-ops-utils.ss
;;;                    and expanded the use of unsafe operators. (MDW)

(require scheme/unsafe/ops)
(require "unsafe-ops-utils.rkt")
(require "machine.rkt")

;;; -----------------------------------------------------------------------------
;;;
;;; 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?
;;;   x: any/c
;;; Returns #t 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 #f otherwise.
(define (infinite x)
  (cond ((eqv? x +inf.0)
         1)
        ((eqv? x -inf.0)
         -1)
        (else
         #f)))

;;; (infinite? x) -> boolean?
;;;  x : any/c
;;; Returns #t if x is +inf.0 or -inf.0.
(define (infinite? x)
  (if (infinite x) #t #f))

;;; (finite? x) -> boolean?
;;;   x : real?
;;; Returns #t 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)))
      (unsafe-fl- (unsafe-fllog 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- (unsafe-flexp 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 (unsafe-flabs x))
          (yabs (unsafe-flabs 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)))
      (if (unsafe-fl= min 0.0)
          max
          (let ((u (unsafe-fl/ min max)))
            (unsafe-fl* max (unsafe-flsqrt
                             (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+ (unsafe-fllog x) ln2))
          ((unsafe-fl> x 2.0)
           (log (unsafe-fl-
                 (unsafe-fl* 2.0 x)
                 (unsafe-fl/ 1.0
                             (unsafe-fl+
                              (unsafe-flsqrt (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 (unsafe-flabs 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+ (unsafe-fllog a) ln2)))
            ((unsafe-fl> a 2.0)
             (unsafe-fl* s (unsafe-fllog
                            (unsafe-fl+
                             (unsafe-fl* 2.0 a)
                             (unsafe-fl/
                              a
                              (unsafe-flsqrt
                               (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
                          (unsafe-flsqrt (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 (unsafe-flabs 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 e)
    (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 (unsafe-flceiling
                    (unsafe-fllog (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)
;;;   x : real?
;;; 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 epsilon)
    (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 (unsafe-fllog 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 (unsafe-fllog x)))))

;;; 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)

(provide/contract
 (nan?
  (-> any/c boolean?))
 (infinite
  (-> any/c (or/c -1 #f 1)))
 (infinite?
  (-> any/c boolean?))
 (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?)))