mirror of
https://gitlab.com/embeddable-common-lisp/ecl.git
synced 2026-01-25 05:51:55 -08:00
Merge branch 'branch-cuts' into 'develop'
numbers: be consistent with branch cuts and signed zero Closes #661 See merge request embeddable-common-lisp/ecl!266
This commit is contained in:
commit
19112f0c95
3 changed files with 115 additions and 13 deletions
|
|
@ -6,6 +6,7 @@
|
|||
* Numbers - Floating point exceptions::
|
||||
* Numbers - Random-States::
|
||||
* Numbers - Infinity and Not a Number::
|
||||
* Numbers - Branch cuts and signed zero::
|
||||
* Numbers - Dictionary::
|
||||
* Numbers - C Reference::
|
||||
@end menu
|
||||
|
|
@ -156,6 +157,27 @@ All rounding functions signal an @code{arithmetic-error} if any of the
|
|||
given parameters are not number valued or infinite.
|
||||
@end table
|
||||
|
||||
@node Numbers - Branch cuts and signed zero
|
||||
@subsection Branch cuts and signed zero
|
||||
For multi-valued complex functions like @code{asin}, @code{acos}, etc.
|
||||
the @ansi{} standard specifies in detail the location of the branch cuts
|
||||
and in particular the value of these functions on the branch cuts. ECL
|
||||
diverges from the standard in that the sign of zero distinguishes two
|
||||
sides of a branch cut. For example, the @code{asin} function includes a
|
||||
branch cut running from 1 to ∞ on the real axis. The ANSI standard
|
||||
specifies a negative imaginary part for @code{asin} on this branch cut
|
||||
consistent with approaching the cut from below. Evaluating @code{(asin
|
||||
z)} in ECL, on the other hand, returns a result with positive imaginary
|
||||
part if the imaginary part of @code{z} is @code{+0.0} (consistent with
|
||||
approaching the cut from above) and a result with negative imaginary
|
||||
part if the imaginary part of @code{z} is @code{-0.0} (consistent with
|
||||
approaching the cut from below)@footnote{The reason for this behaviour
|
||||
is twofold: first, the approach taken by ECL is mathematically more
|
||||
sensible in a number system with signed zero and second, it is
|
||||
consistent with the specification of multi-valued complex functions in
|
||||
the C programming language.}. This applies to @code{sqrt}, @code{asin},
|
||||
@code{acos}, @code{atan}, @code{asinh}, @code{acosh} and @code{atanh}.
|
||||
|
||||
@node Numbers - Dictionary
|
||||
@subsection Dictionary
|
||||
|
||||
|
|
|
|||
|
|
@ -212,6 +212,21 @@ RADIANS) and (SIN RADIANS) respectively."
|
|||
(otherwise
|
||||
(error 'type-error :datum ,arg :expected-type 'number))))))
|
||||
|
||||
;;; Branch cuts and signed zeros
|
||||
;;;
|
||||
;;; The value of the following multi-valued complex functions along
|
||||
;;; their branch cuts is chosen depending on the sign of zero when
|
||||
;;; applicable. For example, the imaginary part of (asin (complex x y))
|
||||
;;; for x real and x>1 is positive for y=+0.0 and negative for
|
||||
;;; y=-0.0, consistent with approaching the branch cut at y=0 from
|
||||
;;; above and respectively below.
|
||||
;;; Note that this differs from the specification in the ANSI
|
||||
;;; standard, which gives only one value (the ANSI standard is silent
|
||||
;;; about signed zeros with regards to branch cuts). We take this
|
||||
;;; approach because it is mathematically more sensible and consistent
|
||||
;;; with the specification for complex numbers in the C programming
|
||||
;;; language. -- mg 2022-01-06
|
||||
|
||||
(defun asin (x)
|
||||
"Args: (number)
|
||||
Returns the arc sine of NUMBER."
|
||||
|
|
@ -227,11 +242,14 @@ Returns the arc sine of NUMBER."
|
|||
(defun complex-asin (z)
|
||||
(declare (number z)
|
||||
(si::c-local))
|
||||
(let ((sqrt-1-z (sqrt (- 1 z)))
|
||||
(sqrt-1+z (sqrt (+ 1 z))))
|
||||
(let* ((re-z (realpart z))
|
||||
(im-z (imagpart z))
|
||||
;; add real and imaginary parts separately for correct signed
|
||||
;; zero handling around the branch cuts
|
||||
(sqrt-1+z (sqrt (complex (+ 1 re-z) im-z)))
|
||||
(sqrt-1-z (sqrt (complex (- 1 re-z) (- im-z)))))
|
||||
(complex (atan (realpart z) (realpart (* sqrt-1-z sqrt-1+z)))
|
||||
(asinh (imagpart (* (conjugate sqrt-1-z)
|
||||
sqrt-1+z))))))
|
||||
(asinh (imagpart (* (conjugate sqrt-1-z) sqrt-1+z))))))
|
||||
|
||||
(defun acos (x)
|
||||
"Args: (number)
|
||||
|
|
@ -248,11 +266,14 @@ Returns the arc cosine of NUMBER."
|
|||
(defun complex-acos (z)
|
||||
(declare (number z)
|
||||
(si::c-local))
|
||||
(let ((sqrt-1+z (sqrt (+ 1 z)))
|
||||
(sqrt-1-z (sqrt (- 1 z))))
|
||||
(let* ((re-z (realpart z))
|
||||
(im-z (imagpart z))
|
||||
;; add real and imaginary parts separately for correct signed
|
||||
;; zero handling around the branch cuts
|
||||
(sqrt-1+z (sqrt (complex (+ 1 re-z) im-z)))
|
||||
(sqrt-1-z (sqrt (complex (- 1 re-z) (- im-z)))))
|
||||
(complex (* 2 (atan (realpart sqrt-1-z) (realpart sqrt-1+z)))
|
||||
(asinh (imagpart (* (conjugate sqrt-1+z)
|
||||
sqrt-1-z))))))
|
||||
(asinh (imagpart (* (conjugate sqrt-1+z) sqrt-1-z))))))
|
||||
|
||||
(defun asinh (x)
|
||||
"Args: (number)
|
||||
|
|
@ -289,8 +310,12 @@ Returns the hyperbolic arc cosine of NUMBER."
|
|||
#+(or ecl-min (not complex-float))
|
||||
(defun complex-acosh (z)
|
||||
(declare (number z) (si::c-local))
|
||||
(let ((sqrt-z-1 (sqrt (- z 1)))
|
||||
(sqrt-z+1 (sqrt (+ z 1))))
|
||||
(let* ((re-z (realpart z))
|
||||
(im-z (imagpart z))
|
||||
;; add real and imaginary parts separately for correct signed
|
||||
;; zero handling around the branch cuts
|
||||
(sqrt-z+1 (sqrt (complex (+ re-z 1) im-z)))
|
||||
(sqrt-z-1 (sqrt (complex (- re-z 1) im-z))))
|
||||
(complex (asinh (realpart (* (conjugate sqrt-z-1)
|
||||
sqrt-z+1)))
|
||||
(* 2 (atan (imagpart sqrt-z-1) (realpart sqrt-z+1))))))
|
||||
|
|
@ -309,7 +334,13 @@ Returns the hyperbolic arc tangent of NUMBER."
|
|||
#+(or ecl-min (not complex-float))
|
||||
(defun complex-atanh (z)
|
||||
(declare (number z) (si::c-local))
|
||||
(/ (- (log (1+ z)) (log (- 1 z))) 2))
|
||||
(let* ((re-z (realpart z))
|
||||
(im-z (imagpart z))
|
||||
;; add real and imaginary parts separately for correct signed
|
||||
;; zero handling around the branch cuts
|
||||
(log-1+z (log (complex (+ 1 re-z) im-z)))
|
||||
(log-1-z (log (complex (- 1 re-z) (- im-z)))))
|
||||
(/ (- log-1+z log-1-z) 2)))
|
||||
|
||||
(defun ffloor (x &optional (y 1.0f0))
|
||||
"Args: (number &optional (divisor 1))
|
||||
|
|
|
|||
|
|
@ -509,8 +509,8 @@ Common Lisp type contagion rules."
|
|||
(for-all-infinities -infinity +infinity
|
||||
(type-and-value-check (asinh -infinity) -infinity)
|
||||
(type-and-value-check (asinh +infinity) +infinity)
|
||||
(without-fpe-traps
|
||||
(complex-equality #'approx= (acosh -infinity) +infinity pi))
|
||||
#| (without-fpe-traps
|
||||
(complex-equality #'approx= (acosh -infinity) +infinity pi))|#
|
||||
(type-and-value-check (acosh +infinity) +infinity)
|
||||
#| (complex-equality #'approx= (atanh -infinity) 0 (/ pi 2))
|
||||
(complex-equality #'approx= (atanh +infinity) 0 (/ pi 2))|#))
|
||||
|
|
@ -733,3 +733,52 @@ Common Lisp type contagion rules."
|
|||
(is (approx= (phase (complex -infinity +infinity)) (+ (* pi 3/4))))
|
||||
(is (approx= (phase (complex +infinity -infinity)) (- (* pi 1/4))))
|
||||
(is (approx= (phase (complex +infinity +infinity)) (+ (* pi 1/4)))) |#))
|
||||
|
||||
|
||||
|
||||
(test ieee-fp.0031.brach-cuts-signed-zero
|
||||
(for-all-number-subtypes (x float (+ 1.0 (random 10.0)))
|
||||
;; branch cuts in [1,infinity)
|
||||
(let ((z-above (complex x +0.0))
|
||||
(z-below (complex x -0.0)))
|
||||
(is (plusp (imagpart (asin z-above))))
|
||||
(is (minusp (imagpart (asin z-below))))
|
||||
(is (minusp (imagpart (acos z-above))))
|
||||
(is (plusp (imagpart (acos z-below))))
|
||||
(is (plusp (imagpart (atanh z-above))))
|
||||
(is (minusp (imagpart (atanh z-below)))))
|
||||
;; branch cuts in (-infinity,-1]
|
||||
(let ((z-above (complex (- x) +0.0))
|
||||
(z-below (complex (- x) -0.0)))
|
||||
(is (plusp (imagpart (asin z-above))))
|
||||
(is (minusp (imagpart (asin z-below))))
|
||||
(is (minusp (imagpart (acos z-above))))
|
||||
(is (plusp (imagpart (acos z-below))))
|
||||
(is (plusp (imagpart (atanh z-above))))
|
||||
(is (minusp (imagpart (atanh z-below)))))
|
||||
;; branch cuts in [i,i*infinity)
|
||||
(let ((z-left (complex -0.0 x))
|
||||
(z-right (complex +0.0 x)))
|
||||
(is (minusp (realpart (atan z-left))))
|
||||
(is (plusp (realpart (atan z-right))))
|
||||
(is (minusp (realpart (asinh z-left))))
|
||||
(is (plusp (realpart (asinh z-right)))))
|
||||
;; branch cuts in (-i*infinity,-i]
|
||||
(let ((z-left (complex -0.0 (- x)))
|
||||
(z-right (complex +0.0 (- x))))
|
||||
(is (minusp (realpart (atan z-left))))
|
||||
(is (plusp (realpart (atan z-right))))
|
||||
(is (minusp (realpart (asinh z-left))))
|
||||
(is (plusp (realpart (asinh z-right))))))
|
||||
(for-all-number-subtypes (x float (- 1.0 (random 10.0)))
|
||||
;; branch cuts in (-infinity,1]
|
||||
(let ((z-above (complex x +0.0))
|
||||
(z-below (complex x -0.0)))
|
||||
(is (plusp (imagpart (acosh z-above))))
|
||||
(is (minusp (imagpart (acosh z-below))))))
|
||||
(for-all-number-subtypes (x float (- (random 10.0)))
|
||||
;; branch cuts in (-infinity,0]
|
||||
(let ((z-above (complex x +0.0))
|
||||
(z-below (complex x -0.0)))
|
||||
(is (plusp (imagpart (sqrt z-above))))
|
||||
(is (minusp (imagpart (sqrt z-below)))))))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue