36

I tried two different ways to find the square root in Java:

Math.sqrt(Double.NEGATIVE_INFINITY); // NaN
Math.pow(Double.NEGATIVE_INFINITY, 0.5); // Infinity

Why doesn't the second way return the expected answer which is NaN (same as with the first way)?

Mark Dickinson
  • 29,088
  • 9
  • 83
  • 120
Pratik
  • 908
  • 2
  • 11
  • 34
  • Getting the square root of something negative is not defined. If it does not result in some error handling behaviour, like throwing, I'd consider it undefined behaviour, and thus allow the compiler to summon nasal demons as you'd say in C. – Aziuth Dec 29 '17 at 10:30
  • 17
    @Aziuth: the behavior of both `Math.sqrt` and `Math.pow` is fully defined even for negative arguments, and "undefined behavior" isn't a concept Java borrowed from C (fortunately). Java has what the C standard would call implementation-defined and unspecified behavior in some very select cases, but no worse than that. – Jeroen Mostert Dec 29 '17 at 10:33
  • 2
    Ah, one of those "why designers, designed it this way? I would totally do it differently!" questions. They cannot be objectively answered by anyone but the designers... – Andrew Savinykh Dec 29 '17 at 11:38
  • 1
    Related: https://stackoverflow.com/questions/10367011/why-is-pow-infinity-positive-non-integer-infinity – jpa Dec 29 '17 at 14:08
  • 6
    @Aziuth: ***Nothing*** in IEEE754 is undefined; and for good reason, due to Floating Point being an inexact representation of the Real Numbers. See [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19422-01/819-3693/ncg_goldberg.html#pgfId-674) for more details. – Pieter Geerkens Dec 29 '17 at 14:24
  • 8
    @AndrewSavinykh: I beg to differ - IEEE754 is ***very well*** thought out, addressing not only efficiency concerns of great importance 40 years ago, but the need to be able, at times, to continue computations even in the occurrence of overflow and invalid operations. – Pieter Geerkens Dec 29 '17 at 14:27
  • 4
    See https://bugs.python.org/issue32171, where almost the exact same issue was raised for Python. Turns out that this behaviour is *not* specified in IEEE 754, but that the omission is either accidental or due to the IEEE 754 folks thinking it was obvious enough not to need specification. See also: http://grouper.ieee.org/groups/754/email/msg03969.html – Mark Dickinson Dec 29 '17 at 16:43
  • @jpa: Looks close enough to be a duplicate, to me. (And I think the accepted answer there is a good one.) – Mark Dickinson Dec 29 '17 at 20:27
  • 2
    @PieterGeerkens: I wouldn't say it's "very well thought out". There are some good engineering decisions, but some horrible ones as well. One of the most important concepts in mathematics is the equivalence relation, but IEEE-754 provides no clean and efficient way to test whether two values x and y are equivalent (evaluating `x==y || (x!=x && y!=y)` seems neither clean nor efficient). – supercat Dec 30 '17 at 02:05

2 Answers2

49

A NaN is returned (under IEEE 754) in order to continue a computation when a truly undefined (intermediate) result has been obtained. An infinity is returned in order to continue a computation after an overflow has occurred.

Thus the behaviour

Math.sqrt(Double.NEGATIVE_INFINITY); // NaN

is specified because it is known (easily and quickly) that an undefined value has been generated; based solely on the sign of the argument.

However evaluation of the expression

Math.pow(Double.NEGATIVE_INFINITY, 0.5); // Infinity

encounters both an overflow AND an invalid operation. However the invalid operation recognition is critically dependent on how accurate the determination of the second argument is. If the second argument is the result of a prior rounding operation, then it may not be exactly 0.5. Thus the less serious determination, recognition of an overflow, is returned in order to avoid critical dependence of the result on the accuracy of the second argument.

Additional details on some of the reasoning behind the IEEE 754 standard, including the reasoning behind returning flag values instead of generating exceptions, is available in

What Every Computer Scientist Should Know About Floating-Point Arithmetic (1991, David Goldberg),

which is Appendix D of

Sun Microsystems Numerical Computation Guide.

Mark Dickinson
  • 29,088
  • 9
  • 83
  • 120
Pieter Geerkens
  • 11,775
  • 2
  • 32
  • 52
  • 3
    Well, if the second argument is not exactly 0.5, then complex phase of the resulting infinity is still not even close to `Pi*N`, so mathematically, it's still invalid operation (for an R→R function). Thus this doesn't seem to be a good enough explanation for why it doesn't generate a NaN. – Ruslan Dec 29 '17 at 15:54
  • @Ruslan: I disagree. If the second argument is supposed to be exactly 2/3, passed in as ~0.6666666667, then the correct return value is **Overflow** rather than **NaN**. – Pieter Geerkens Dec 29 '17 at 15:59
  • 1
    Well this depends on the choice of branch cuts... they could have chosen to have a complex result (e.g. `DirectedInfinity[-0.4999999998186198 + 0.8660254038891585*I]`) from this and thus return NaN. I suppose they chose instead that if there's a chance to get a real result, then it should be returned... – Ruslan Dec 29 '17 at 16:08
  • 2
    I find the argument here questionable. In particular, it's missing an explanation of (a) why the possible inexactness of the second argument makes a difference (*why* should we consider `pow(-inf, 0.5+epsilon)` to be `inf` rather than `nan`?), and (b) an explanation of what's special about `-inf`: `pow(-10.0, y)` gives a `nan` for *any* non-integer finite `y`. I think it's much more likely that the rationale comes simply from the behaviour of `pow(-0.0, y)`, the desire to preserve the symmetry `pow(1/x, y) == 1/pow(x, y)`, and the fact that `1/-0.0` is `-inf`. – Mark Dickinson Dec 29 '17 at 20:32
  • @MarkDickinson: I counter that my presentation not only implies your symmetry argument, but also explains why an **infinity** symmetry is chosen to be preserved rather than the **NaN** symmetry that is also available. – Pieter Geerkens Dec 29 '17 at 21:08
  • @PieterGeerkens: You say "the less serious determination [...] is returned in order to avoid critical dependence of the result on the accuracy of the second argument". What _is_ this dependence you're avoiding? You seem to be imagining some hypothetical alternative result where `pow(-inf, 0.5)` and `pow(-inf, 0.5+epsilon)` return different results. But the obvious alternative is to return `nan` for both an exact `0.5` and an "inexact" 0.5; you don't explain why this inexactness is important. Maybe you could clarify the answer? – Mark Dickinson Dec 29 '17 at 21:51
  • @MarkDickinson: There are countably many real numbers (ie all the rationals with an odd denominator > 1 in reduced form) that, when the exponent of a negative real number, produce at least one real result. But NONE of those countably many values can be EXACTLY expressed by a IEEE754 floating point expression. None-the-less, when the 1st argument is `-inf` then the `Math.Pow(,)` function will ALWAYS overflow in magnitude (your symmetry argument restated), regardless of whether a real result might exist or not for a finite first argument. – Pieter Geerkens Dec 29 '17 at 22:16
  • @PieterGeerkens: Is there any non-integer value of x for which Math.Pow(-8.0,x) should sensibly yield a positive value? Certainly a positive result should be expected if x is any even integer, and a negative result if x is any odd integer, but if x isn't an integer value I don't see any number as being sensible. Personally, I subscript to the notion that Pow(double, int) and Pow(double, double) are fundamentally different operations, and it is perfectly reasonable to say that the second is defined only if the first operand is positive, regardless of whether the second happens to... – supercat Dec 30 '17 at 01:28
  • ...be a whole number. I think FORTRAN specified things that way, and it seems more reasonable than the alternative. Such a specification would also make clear that Pow(anyValue, 0) is unambiguously 1.0, while Pow(0.0, 0.0) and Pow(anyInfinity, 0.0) and Pow(NaN, 0.0) should both be NaN. – supercat Dec 30 '17 at 01:30
  • @supercat: Yes; x = +2/3 yields +4.0. Unfortunately, it is impossible to represent 2/3 exactly in floating point representation due to the odd denominator. – Pieter Geerkens Dec 30 '17 at 01:32
  • @PieterGeerkens: Are there any representable value of x for which `Math.Pow(-8.0,x)` should sensibly yield a positive value? The mathematical function `f(x) = (-8)**x` is discontinuous everywhere. It may make sense to define it for exact values of x where its value is a real number, but it doesn't converge in a way that would render an approximate value meaningful anywhere else. – supercat Dec 30 '17 at 02:01
  • Another interesting URL is http://floating-point-gui.de/ and that URL is easy to remember – Basile Starynkevitch Dec 30 '17 at 04:37
  • @PieterGeerkens Great. Did u find this from the links you mentioned above ? It's good to know any such references somewhere, useful to clear understanding of computations. – Pratik Mar 12 '18 at 04:39
  • 1
    @Pratik: I applied my knowledge; experience; and understanding of arithmetic and mathematical calculation to the information and description (of both motive and method) contained in the links provided. – Pieter Geerkens Mar 12 '18 at 04:49
30

It is just acting as is described in the documentation of Math.

For Math.sqrt:

If the argument is NaN or less than zero, then the result is NaN.

For Math.pow:

If

  • the first argument is negative zero and the second argument is less than zero but not a finite odd integer, or
  • the first argument is negative infinity and the second argument is greater than zero but not a finite odd integer,

then the result is positive infinity.

As to why they made that design choice - you'll have to ask the authors of java.

cs95
  • 379,657
  • 97
  • 704
  • 746
piet.t
  • 11,718
  • 21
  • 43
  • 52
  • 5
    I agree, but should not be inconsistency in using different methods, especially when it comes to Math, there has to be a standard answer, isn't it ? From my perception, second method is returning incorrect answer. – Pratik Dec 29 '17 at 09:16
  • 2
    from my own personnal point of view, both are wrong. You should throw an exception, not returning a value like Nan or Infinity. – spi Dec 29 '17 at 09:20
  • 3
    @Pratik: the return values of `Math.pow()` align with the C standard definition of `pow()`. It seems likely that they both align with the IEEE 754 definition of the operation, but I have no copy of the standard handy. The IEEE 754 designers have very detailed reasons for why things are the way they are, though not always comprehensible by mere mortals. – Jeroen Mostert Dec 29 '17 at 10:28
  • @JeroenMostert yes, I have thought of IEEE 754 too, but I didn't know whether the standard also defines the actual operations. If someone had access to it it would be good to have additional information from there. – piet.t Dec 29 '17 at 11:51
  • @Pratik: You can only say that because you ***know*** that the second argument is exact. The Floating Point specification has to allow for the possibility that the second argument is an approximation (either an estimate or the result of a prior operation), and thus that while an *overflow* definitely occurs, an invalid operation only ***might*** have occurred. – Pieter Geerkens Dec 29 '17 at 14:16
  • The question is not about whether it obeys the documenation, but about the why. In that sense, the answer by @PieterGeerkens does a better job. – Whirl Mind Dec 29 '17 at 18:31