Operator Precedence of "Unary Minus" (-) and Exponentiation (^) Outside VS. Inside Function

Operator precedence of unary minus (-) and exponentiation (^) outside vs. inside function

Because of the surprisingly low precedence of the "unary minus" (-) operator, combined with R's convention for raising negative numbers to fractional powers. -27^(1/3) is read as "calculate 27^(1/3) (==3), then invert the sign". If we instead compute (-27)^(1/3), we get NaN, because a negative number can't reliably be raised to a fractional power. Whether the evaluation is inside or outside of a function is a red herring; the issue is whether (-27) is evaluated as an expression first (which it is if you assign it to a function argument or a variable, or put it in parentheses).

The precedence of unary minus is discussed on this mailing list thread from 2009 and R FAQ 7.33, and more generally in this math.stackexchange question. There's a (rather technical) explanation of why a negative value to a fractional power is NaN in ?"^":

Users are sometimes surprised by the value returned, for example
why (-8)^(1/3) is NaN. For double inputs, R makes use of IEC
60559 arithmetic on all platforms, together with the C system
function pow for the ^ operator. The relevant standards
define the result in many corner cases. In particular, the result
in the example above is mandated by the C99 standard. On many
Unix-alike systems the command man pow gives details of the
values in a large number of corner cases.

`x^(1/3)` behaves differently for negative scalar `x` and vector `x` with negative values

I'm not looking for a workaround e.g. function(x) {sign(x) * (abs(x)) ^ (1/3)}.

I'm interested in an answer that explains what is happening differently to the vector than to the negative value when provided as a numeric scalar.

how does the ^ operator think differently about vectors and scalars?

You seem to believe that c(-0.2, 1)^(1/3) translates to c(-0.2^(1/3), 1^(1/3)). This is incorrect. Operator ^ is actually a function, that is, (a) ^ (b) is as same as "^"(a, b). Therefore, the correct interpretation goes as follows:

   c(-0.2, 1)^(1/3)
=> "^"(c(-0.2, 1), 1/3)
=> c( "^"(-0.2, 1/3), "^"(1, 1/3) )
=> c( (-0.2)^(1/3), (1)^(1/3) )
=> c( NaN, 1 )

Now, why doesn't -0.2^(1/3) give NaN? Because ^ has higher operation precedence than +, -, * and /. So as it is written, it really implies -(0.2^(1/3)) instead of (-0.2)^(1/3).

The lesson is that, to avoid buggy code, write your code as (a) ^ (b) instead of just a ^ b.


Additional Remark:

I often compare ^ and : when teaching R to my students, because they have different behaviors. But they all show the importance of protecting operands with brackets.

(-1):2
#[1] -1 0 1 2

-1:2
#[1] -1 0 1 2

-(1:2)
#[1] -1 -2
2*3:10
#[1] 6 8 10 12 14 16 18 20

(2*3):10
#[1] 6 7 8 9 10

2*(3:10)
#[1] 6 8 10 12 14 16 18 20

See ?Syntax for details of operator precedence.

How to deal with unary minus and exponentiation in an expression parser

Unless you have unusual requirements, putting both unary minus and exponentiation in the same non-terminal will work fine, because exponentiation is right-associative: (Yacc/bison syntax)

atom: ID
| '(' expr ')'
factor
: atom
| '-' factor
| atom '^' factor
term: factor
| term '*' factor
expr: term
| expr '+' term
| expr '-' term

Indeed, exponentiation being right-associative is virtually required for this syntax to be meaningful. Consider the alternative, with a left-associative operator.

Let's say we have two operators, ⊕ and ≀, with ⊕ being left associative and binding more tightly than ≀, so that ≀ a ⊕ b is ≀(a ⊕ b).

Since ⊕ is left associative, we would expect a ⊕ b ⊕ c to be parsed as (a ⊕ b) ⊕ c. But then we get an oddity. Is a ⊕ ≀ b ⊕ c the same as (a ⊕ ≀b) ⊕ c) or the same as a ⊕ ≀(b ⊕ c))? Both options seem to violate the simple patterns. [Note 1]

Certainly, an unambiguous grammar could be written for each case, but which one would be less surprising to a programmer who was just going by the precedence chart? The most likely result would be a style requirement that ≀ expressions always be fully parenthesized, even if the parentheses are redundant. (C style guides are full of such recommendations, and many compilers will chide you for using correct but "unintuitive" expressions.)


Notes:

  1. If you use precedence declarations, you'll get a ⊕ ≀(b ⊕ c)), which might or might not be intuitive, depending on your intuitions.

R exponentiating mystery

Do you get the correct answer though? The following works:

-2.108844 ^ 0.9891197
#> [1] -2.091793

And gives the same result as the following:

-(2.108844 ^ 0.9891197)
#> [1] -2.091793

But look what happens if I move the negative sign inside the brackets:

(-2.108844) ^ 0.9891197
#> [1] NaN

Note that R quite appropriately gives NaN because I am raising a negative number to a fractional power.

So the answer to your question is that due to operator precedence, the R parser is interpreting

-2.108844 ^ 0.9891197

as

-(2.108844 ^ 0.9891197)

Which is giving you the wrong answer when you type the numbers directly.

JavaScript exponentiation unary operator design decision

I don't understand this design decision.

Read more about it at https://esdiscuss.org/topic/exponentiation-operator-precedence, https://esdiscuss.org/topic/power-operator-why-does-2-3-throws, https://github.com/rwaldron/tc39-notes/blob/master/es7/2015-09/sept-23.md#exponentiation-operator and https://github.com/rwaldron/tc39-notes/blob/master/es7/2015-09/sept-24.md#exponentiation-operator.

Who's really going to be surprised that -x ** 2 is negative?

Enough people to matter. Some relevant quotes from the above resources:

  • "making ** bind tighter than unary operators would break x**-2. And making it sometimes tighter and sometimes looser would be too confusing and lead to other opportunities for precedence inversion." - Waldemar Horwat
  • "Given the conflict between the history of ** in other languages, [and] the general pattern that unary binds tighter than binary, any solution at this point will confuse many people." - Mark S. Miller
  • "acknowledge the prospect of significant whitespace: -x**2 === -(x ** 2) and -x ** 2 === (-x) ** 2" - Alexander Jones
  • "The problem is, however rare unary minus before an exponentiation expression may be, the lack of superscript-with-smaller-font sugests that - binds tighter than **. And indeed apart from dot (a special form whose right operand must be a lexical identifier-name) and square brackets (which isn't an infix operator per se), unary operators bind tighter than binary in JS as in C and other C-derived languages." - Brendan Eich
  • "For math it seems obvious that -52. But for -5 ** 2, because of the whitespace around the infix operator. Even without space, - seems to be part of the literal." - Dave Herman
  • [Regarding programming language precedence], "effectively zero people have an intutition about this from other languages. Agree people have an itutition that ** is the exponentiation operator. But people usually try to avoid dark corners so they never develop an intuition for negative bases." - Dave Herman

In Javascript '1'+ 2 is '12' and '1'-2 is -1 but -1**2 raises an error because it could be ambiguous?

Well they put considerably more effort in the design of extensions to the language today :-) It's the best solution that they could reach consensus for.

What does the unary operator - do on unsigned data types in C/C++ (and on different compilers)?

The relevant quote from the Standard is actually this:

(§5.3.1/8) The operand of the unary - operator shall have arithmetic or unscoped enumeration type and the result is the negation of its operand. Integral promotion is performed on integral or enumeration operands. The negative of an unsigned quantity is computed by subtracting its value from 2n, where n is the number of bits in the promoted operand. The type of the result is the type of the promoted operand.

(This is from C++11; it used to be 5.3.1/7 in older versions.)

So -num will be evaluated as 2CHAR_BIT*sizeof(num) - num (‡). The result will be of the same type as the operand (after integer promotion), i.e. it will also be unsigned.

I just tested with GCC and it seems to perform the operation in precisely the way described by the Standard. I'll assume this is the case for Visual C++ as well; otherwise it's a bug.


(‡) This formula assumes that the relevant number of bits corresponds to the size (in bits) of the variable in memory. As Keith Thompson points out in the comment, this can't be true if there are padding bits (i.e. when not all bits participate in the representation of the numerical value, which is possible according to §3.9.1/1). On a system that uses more bits to store the value than are used to represent the numerical value, the formula will not be accurate. (I, personally, am not actually aware of any such system, though.)



Related Topics



Leave a reply



Submit