#lang typed/racket/base

#|
Compute log and log1p with 105-bit accuracy

Both implementations do some argument reduction, find excellent 53-bit initial estimates, and then
perform one Newton step.
|#

(require "../flonum-functions.rkt"
         "../flonum-error.rkt"
         "../flonum-log.rkt"
         "expansion-base.rkt"
         "expansion-exp.rkt")

(provide fl2log fl2log1p)

;; ===================================================================================================
;; log

#|
Argument reduction for log:

  log(x) = log(x*2^k) - k*log(2)

A value of k that reduces any x to [0.5, 1.0] is
  
  k = -truncate(log(x)/log(2))
|#

(define-values (log2-hi log2-lo) (values 0.6931471805599453 2.3190468138462996e-17))

(: fl2log-reduction (Flonum Flonum -> (Values Flonum Flonum Flonum)))
(define (fl2log-reduction x2 x1)
  (define k (- (fltruncate (fl/ (fllog+ x1 x2) (fllog 2.0)))))
  (cond [(k . fl> . 1023.0)
         ;; This can happen if x is subnormal; just multiply in pieces
         (define k0 1023.0)
         (define k1 (fl- k k0))
         (define 2^k0 (flexpt 2.0 k0))
         (define 2^k1 (flexpt 2.0 k1))
         (let*-values ([(x2 x1)  (values (* x2 2^k0 2^k1) (* x1 2^k0 2^k1))])
           (values k x2 x1))]
        [else
         (define 2^k (flexpt 2.0 k))
         (let*-values ([(x2 x1)  (values (fl* x2 2^k) (fl* x1 2^k))])
           (values k x2 x1))]))

(: fl2log (Flonum Flonum -> (Values Flonum Flonum)))
(define (fl2log x2 x1)
  (define x (fl+ x1 x2))
  (cond [(x . fl<= . 0.0)  (cond [(fl= x 0.0)  (values -inf.0 0.0)]
                                 [else  (values +nan.0 0.0)])]
        [(x . fl= . +inf.0)  (values +inf.0 0.0)]
        [(or (x . fl< . 0.5) (x . fl> . 2.5))
         ;; Reduce arguments
         (let*-values ([(k x2 x1)  (fl2log-reduction x2 x1)]
                       [(y2 y1)  (fl2log x2 x1)]
                       [(z2 z1)  (fl2* log2-hi log2-lo k)])
           (fl2- y2 y1 z2 z1))]
        [else
         ;; Estimate log(x) and do a Newton iteration using expm1
         (let*-values ([(y)  (fllog+ x2 x1)]
                       [(x2 x1)  (fl2+ x2 x1 -1.0)]
                       [(z2 z1)  (flexpm1/error y)]
                       [(w2 w1)  (fl2+ z2 z1 1.0)]
                       [(dy2 dy1)  (fl2- x2 x1 z2 z1)]
                       [(dy2 dy1)  (fl2/ dy2 dy1 w2 w1)])
           (fl2+ dy2 dy1 y))]))

;; ===================================================================================================
;; log1p

#|
Argument reduction for log1p:

  log1p(x) = k*log(2) + log1p(x/2^k + (1/2^k - 1))

A `k'  that reduces any argument `x' to (-1/2,1/2) is

  k = round(log1p(x)/log(2))
|#

(: fl2log1p-reduction (Flonum Flonum Flonum Flonum Flonum -> (Values Flonum Flonum Flonum)))
(define (fl2log1p-reduction x2 x1 a2 a1 y)
  (define k (flround (fl/ y (fllog 2.0))))
  (define 2^k (flexpt 2.0 k))
  (define-values (j2 j1) (fast-fl-/error (/ 1.0 2^k) 1.0))
  (let*-values ([(x2 x1)  (values (/ x2 2^k) (/ x1 2^k))]
                [(x2 x1)  (fl2+ x2 x1 j2 j1)])
    (values k x2 x1)))

(: fl2log1p (Flonum Flonum -> (Values Flonum Flonum)))
(define (fl2log1p x2 x1)
  (define-values (a2 a1) (fl2+ x2 x1 1.0))
  (define y (fllog+ a2 a1))
  (cond
    [(or (y . fl< . -0.5) (a2 . fl> . 2.0))  (fl2log a2 a1)]
    [(fl= (fl+ x2 x1) 0.0)  (values x2 0.0)]
    [(y . fl> . 0.5)
     (let*-values ([(k x2 x1)  (fl2log1p-reduction x2 x1 a2 a1 y)]
                   [(y2 y1)  (fl2log1p x2 x1)]
                   [(z2 z1)  (fl2* log2-hi log2-lo k)])
       (fl2+ y2 y1 z2 z1))]
    [else
     (let*-values ([(z2 z1)  (flexpm1/error y)]
                   [(w2 w1)  (fl2+ z2 z1 1.0)]
                   [(dy2 dy1)  (fl2- x2 x1 z2 z1)]
                   [(dy2 dy1)  (fl2/ dy2 dy1 w2 w1)])
       (fl2+ dy2 dy1 y))]))
