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:
Daniel Kochmański 2022-02-04 20:09:05 +00:00
commit 19112f0c95
3 changed files with 115 additions and 13 deletions

View file

@ -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

View file

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

View file

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