math.ss
#lang scheme/base
;;; 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)

(require (lib "contract.ss"))

(provide
 e
 log2e
 log10e
 sqrt2
 sqrt1/2
 sqrt3
 pi
 pi/2
 pi/4
 sqrtpi
 2/sqrtpi
 1/pi
 2/pi
 ln10
 ln2
 lnpi
 euler
 (rename-out (nan? unchecked-nan?)
             (infinite? unchecked-infinite?)
             (finite? unchecked-finite?)
             (log1p unchecked-log1p)
             (expm1 unchecked-expm1)
             (hypot unchecked-hypot)
             (acosh unchecked-acosh)
             (asinh unchecked-asinh)
             (atanh unchecked-atanh)
             (ldexp unchecked-ldexp)
             (frexp unchecked-frexp)
             (sign unchecked-sign)
             (fcmp unchecked-fcmp)))

(provide/contract
 (nan?
  (-> any/c boolean?))
 (infinite?
  (-> any/c (union (integer-in -1 1)
                   boolean?)))
 (finite?
  (-> any/c boolean?))
 (log1p
  (-> real? number?))
 (expm1
  (-> real? real?))
 (hypot
  (-> real? real? real?))
 (acosh
  (-> real? real?))
 (asinh
  (-> real? real?))
 (atanh
  (-> real? real?))
 (ldexp
  (-> real? integer? real?))
 (frexp
  (-> real? (values real? integer?)))
 (sign
  (-> real? (integer-in -1 1)))
 (fcmp
  (-> real? real? real? (integer-in -1 1))))

(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 constant pi
(define pi 3.14159265358979323846264338328)

;;; 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)
(define ln10 2.30258509299404568401799145468)

;;; The natural logarithm of two, ln(2)
(define ln2 0.69314718055994530941723212146)

;;; 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?: any -> boolean
;;; Returns true if x is equivalent to +nan.0.
(define (nan? x)
  (eqv? x +nan.0))

;;; infinite?: any -> boolean or (-1, 1)
;;; 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?: real -> boolean
;;; Returns true of x is a finite, real number.
(define (finite? x)
  (and (real? x)
       (not (nan? x))
       (not (infinite? x))))

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

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

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

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

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

;;; asinh: real -> real
;;; Computes the value of arcsinh(x)
(define (asinh x)
  (let ((a (abs x))
        (s (if (< x 0) -1.0 1.0)))
    (cond ((> a (/ 1.0 sqrt-double-epsilon))
           (* s (+ (log a) ln2)))
          ((> a 2.0)
           (* s (log (+ (* 2.0 a)
                        (/ a (sqrt (+ (* a a) 1.0)))))))
          ((> a sqrt-double-epsilon)
           (let ((a2 (* a a)))
             (* s (log1p (+ a (/ a2 (+ 1.0 (sqrt (+ 1.0 a2)))))))))
          (else
           x))))

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

;;; ldexp: real x integer -> real
;;; Computes the value of x * 2^e.
(define (ldexp x e)
  (let ((p2 (expt 2.0 e)))
    (* x p2)))

;;; frexp: real -> real x 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)
  (if (= x 0)
      (values
       0.0
       0)
      (let* ((ex (ceiling (log (/ (abs x) ln2))))
             (ei (inexact->exact ex))
             (f (ldexp x (- ei))))
        (let loop ()
          (when (>= (abs f) 1.0)
            (set! ei (+ ei 1))
            (set! f (/ f 2.0))
            (loop)))
        (let loop ()
          (when (< (abs f) 0.5)
            (set! ei (- ei 1))
            (set! f (* f 2.0))
            (loop)))
        (values
         f
         ei))))

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

;;; sign: real -> integer
;;; 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: real x real x real -> integer
;;; 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)
  (let* ((exponent 0)
         (delta 0.0)
         (difference (- x1 x2))
         (dummy 0.0)
         (max (if (> (abs x1) (abs x2)) x1 x2)))
    (set!-values (dummy exponent) (frexp max))
    (set! delta (ldexp epsilon exponent))
    (if (> difference delta)
        1                             ; x1 > x2
        (if (< difference (- delta))
            -1                        ; x1 < x2
            0))))                     ; x1 ~= x2