racket/collects/math/private/flonum/flonum-search.rkt
Neil Toronto f2dc2027f6 Initial math library commit. The history for these changes is preserved
in the original GitHub fork:

  https://github.com/ntoronto/racket

Some things about this are known to be broken (most egregious is that the
array tests DO NOT RUN because of a problem in typed/rackunit), about half
has no coverage in the tests, and half has no documentation. Fixes and
docs are coming. This is committed now to allow others to find errors and
inconsistency in the things that appear to be working, and to give the
author a (rather incomplete) sense of closure.
2012-11-16 11:39:51 -07:00

113 lines
5.5 KiB
Racket

#lang typed/racket/base
(require "flonum-constants.rkt"
"flonum-functions.rkt"
"flonum-bits.rkt")
(provide find-least-flonum flfind-least-integer)
(define +inf-ordinal (flonum->ordinal +inf.0))
(: find-least-flonum (case-> ((Flonum -> Any) Flonum -> (U Flonum #f))
((Flonum -> Any) Flonum Flonum -> (U Flonum #f))))
(define find-least-flonum
(case-lambda
[(pred? x-start)
(when (eqv? +nan.0 x-start)
(raise-argument-error 'find-least-flonum "non-NaN Flonum" 1 pred? x-start))
(let loop ([n-end (flonum->ordinal x-start)] [step 1])
(define x-end (ordinal->flonum n-end))
(cond [(pred? x-end) (find-least-flonum pred? x-start x-end)]
[(fl= x-end +inf.0) #f]
[else (loop (min +inf-ordinal (+ n-end step)) (* step 2))]))]
[(pred? x-start x-end)
(when (eqv? x-start +nan.0)
(raise-argument-error 'find-least-flonum "non-NaN Flonum" 1 pred? x-start x-end))
(when (eqv? x-end +nan.0)
(raise-argument-error 'find-least-flonum "non-NaN Flonum" 2 pred? x-start x-end))
(cond [(pred? x-start) x-start]
[(not (pred? x-end)) #f]
[else
(let loop ([n-start (flonum->ordinal x-start)] [n-end (flonum->ordinal x-end)])
(cond [(= n-start n-end) (define x (ordinal->flonum n-end))
(if (pred? x) x #f)]
[else
(define n-mid (quotient (+ n-start n-end) 2))
(cond [(pred? (ordinal->flonum n-mid))
(loop n-start n-mid)]
[else
(loop (+ n-mid 1) n-end)])]))])]))
(: sub-or-prev (Flonum Flonum -> Flonum))
(define (sub-or-prev k i)
(define prev-k (fl- k i))
(if (fl= prev-k k) (flprev* k) prev-k))
(: add-or-next (Flonum Flonum -> Flonum))
(define (add-or-next k i)
(define next-k (fl+ k i))
(if (fl= next-k k) (flnext* k) next-k))
(: flmidpoint (Flonum Flonum -> Flonum))
(define (flmidpoint x y)
(let ([x (flmin x y)]
[y (flmax x y)])
(cond [(fl= x -inf.0) (cond [(fl= y +inf.0) 0.0]
[(fl= y -inf.0) -inf.0]
[else (+ (* 0.5 -max.0) (* 0.5 y))])]
[(fl= y +inf.0) (cond [(fl= x +inf.0) +inf.0]
[else (+ (* 0.5 x) (* 0.5 +max.0))])]
[else (+ (* 0.5 x) (* 0.5 y))])))
(: flfind-least-integer (case-> ((Flonum -> Any) -> Flonum)
((Flonum -> Any) Flonum -> Flonum)
((Flonum -> Any) Flonum Flonum -> Flonum)
((Flonum -> Any) Flonum Flonum Flonum -> Flonum)))
;; Finds the least integer k such that (pred? k) is #t, given optional bounds and an optional
;; initial estimate. If the predicate is not monotone in the bounds, the result of this function is
;; indeterminate, and depends in an unspecified way on the initial estimate.
;; Formally, to get a unique answer, one of the following cases must be true.
;; 1. Exists k, forall mn <= i < k, (pred? i) is #f /\ forall k <= j <= mx, (pred? j) is #t
;; 2. Forall k, (pred? k) is #f
;; 3. Forall k, (pred? k) is #t
;; where mn0 <= k <= mx0. For case #1, this function returns k. For case #2, it returns +nan.0. For
;; case #3, it returns mn0.
(define (flfind-least-integer pred? [mn0 -inf.0] [mx0 +inf.0] [k0 +nan.0])
(let ([mn (flceiling (flmin mn0 mx0))]
[mx (flfloor (flmax mn0 mx0))])
;; Make sure the initial estimate is in-bounds
(define k (cond [(and (k0 . >= . mn) (k0 . <= . mx)) (flfloor k0)]
[else (flfloor (flmidpoint mn mx))]))
(define k? (pred? k))
;; Find an integer k-min <= k for which (pred? k-min) is #f; increment exponentially
(define-values (k-min k-min?)
(let: loop : (Values Flonum Any) ([k-min : Flonum k] [k-min? : Any k?] [i : Flonum 1.0])
;(printf "min: ~v~n" k-min)
(cond [(k-min . fl<= . mn) (cond [(fl= k-min mn) (values k-min k-min?)]
[else (values mn (pred? mn))])]
[k-min? (define prev-k-min (sub-or-prev k-min i))
(loop prev-k-min (pred? prev-k-min) (* 2.0 (- k-min prev-k-min)))]
[else (values k-min #f)])))
;; Find an integer k-max >= k0 for which (pred? k-max) is #t; increment exponentially
(define-values (k-max k-max?)
(let: loop : (Values Flonum Any) ([k-max : Flonum k] [k-max? : Any k?] [i : Flonum 1.0])
;(printf "max: ~v~n" k-max)
(cond [(k-max . fl>= . mx) (cond [(fl= k-max mx) (values k-max k-max?)]
[else (values mx (pred? mx))])]
[k-max? (values k-max #t)]
[else (define next-k-max (add-or-next k-max i))
(loop next-k-max (pred? next-k-max) (* 2.0 (- next-k-max k-max)))])))
;; Quickly check cases #2 and #3; if case #1, do a binary search
(cond [(not k-max?) +nan.0]
[k-min? mn]
[else
;; Loop invariant: (pred? k-max) is #t and (pred? k-min) is #f
(let loop ([k-min k-min] [k-max k-max])
;(printf "~v ~v~n" k-min k-max)
(define k (flfloor (flmidpoint k-min k-max)))
;; Check whether k-min + 1 = k-max or (flnext k-min) = k-max
(cond [(or (= k k-min) (= k k-max)) k-max]
[(pred? k) (loop k-min k)]
[else (loop k k-max)]))])))