mirror of
https://gitlab.com/embeddable-common-lisp/ecl.git
synced 2026-01-27 23:11:18 -08:00
numbers: be consistent with branch cuts and signed zero
Let the sign of zero determine from which side branch cuts are approached, no matter whether we use C99 complex numbers or not. Disable the (acosh -∞) test. This test fails with the new code, but was supposed to be commented out anyway. In general, we don't guarantee anything about infinity if complex numbers are involved. Closes #661.
This commit is contained in:
parent
e83f278f17
commit
8da3475b02
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