JavaScript exponentiation unary operator design decision
Asked Answered
G

2

8

So I was fooling around with the new exponentiation operator and I discovered you cannot put a unary operator immediately before the base number.

let result = -2 ** 2; // syntax error
let result = -(2 ** 2); // -4
let x = 3;
let result = --x ** 2; // 4

From the documentation on MDN:

In JavaScript, it is impossible to write an ambiguous exponentiation expression, i.e. you cannot put a unary operator (+/-/~/!/delete/void/typeof) immediately before the base number.

In most languages like PHP and Python and others that have an exponentiation operator (typically ^ or **), the exponentiation operator is defined to have a higher precedence than unary operators such as unary + and unary -, but there are a few exceptions. For example, in Bash the ** operator is defined to have a lower precedence than unary operators.

I understand this was made an error by design. I don't understand this design decision. Who's really going to be surprised that -x ** 2 is negative? This follows not only other mainstream programming languages but a mathematical notation that has been in common use for hundreds of years and is taught to every high school algebra student.

In Javascript '1'+ 2 is '12' and '1'-2 is -1 but -1**2 raises an error because it could be ambiguous? Help me understand this design decision.

Goldschmidt answered 2/11, 2017 at 6:20 Comment(6)
Writing -x ** 2 instead of - x**2 would make it ambiguous alone.Vagabond
Well, I believe I was always taught at school that -x raised to the power of y produces a positive number. So, -1**2 should produce 1. At least this is what I believe was taught, and I am sufficiently confident that my belief is correct.Angelenaangeleno
Why the heck was this question downvoted ?Angelenaangeleno
@Angelenaangeleno - Agreed, very strange (about the vote). BTW, in math, -1² is -1. It's -(1²), not (-1)². I went 'round and 'round in my head on that one and finally punted and asked the question. It's been that long since math class. But I'm also a bit embarrassed to have asked, since I routinely see things like -2³² + 1 and have no trouble interpreting that correctly (2³² = 4294967296, -4294967296 + 1 is -4294967295).Hispaniola
@T.J._Crowder I am actually quite ashamed of my comment now that I read it, -1**2 only takes a basic understanding of operator precedence in Math, and "(-x)" raised to the power of "y" does not always produce a positive.Angelenaangeleno
The question is "lame" -- in that one can make arguments in either direction to support ones desired outcome. Ex: find a calculator with a exponent or squaring function. Type in -2 and square it. or raise it to '2'. I think you would be hard pressed to find one that came up with -4 as an answer. Negative numbers were around long before "operators", let alone "unary ones". Yet from a practical point of view, why would same calculator allow -21 and -22, but not -2**1.5. See, you can justify it either way! "Lame"...Elysium
V
12

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/meetings/2015-09/sept-23.md#exponentiation-operator and https://github.com/rwaldron/tc39-notes/blob/master/meetings/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.

Vagabond answered 2/11, 2017 at 7:27 Comment(0)
H
-1

Since we usually interpret -52 as -25, I suggest we allow -5 ** 2 (which would be -25), then we disallow 5**-2 just like we also disallow 5--2, 5+* 2, 5*/2, etc where we do not put different operators one after another.

Allowing -5**2 would not break with the math community and their libraries where they usually write like -5^2 and 5^(-2) and 5-(-2) instead of just 5--2.

Hulburt answered 17/6 at 7:52 Comment(11)
This is hardly gonna change, but if you want to make suggestions the place would be es.discourse.group not on StackOverflow. Btw it's not possible to disallow syntax that's already valid and used everywhere, also there's nothing wrong (or ambiguous) with 5**-2, 5+-2, 5*-2, 5- -2 etc.Vagabond
Thanks for the comment, it's just that this is causing big problem in dealing with different treatments in python, php and javascript, as well as in math community and also the way we do math in school and how we key into calculator, so I think somehow we need to start thinking about standardising this across different programming as well as making it compatible with the math academy, in math, we try not to write 5 -- 2 but write as 5 - (-2) insteadHulburt
Other languages do it inconsistently already, and there's nothing JS can do about that. It just takes the safe bet and forces you to disambiguate with explicit parenthesis, which does work everywhere.Vagabond
Anyway it boils down to the big difference of javascript placing unary (-) operator at higher precedence over exponentiation ( ** ) while python and php put exponentiation higher (c, c++, c# and java do not have exponentiation), so -1 ** 2 in javascript becomes (-1) ** 2 while in python and php works to -(1 ** 2); but the argument that putting exponentiation higher than unary would break 1 **-2 can be countered as this: by right 1 **-2 is illegal as this work out to (1 ** -)2 and 1 ** - will trigger syntax error, but as compiler expect a number after ** so it can insert ( before - as 1**(-2)Hulburt
"-1 ** 2 in javascript becomes (-1) ** 2" and "1 **-2 is illegal" - no it doesn't. Have you tried it?Vagabond
What I meant was that -1 ** 2 according to javascript operator precedence would work out to (-1) ** 2 but javascript now chooses to raise syntax error, then 1 ** -2 by right should work out to (1 ** -)2 if using the operator precedence of ** over unary (which is the case with python and php), but the compiler instead of raising syntax error can cleverly treat it as 1 ** (-2) without breaking anything (perhaps this is how python and php did it?), so I'm saying that if javascript follows the same operator precedence as python and php, then -1 ** 2 is -1 and 1 ** -2 without breaking is 1Hulburt
"1 ** -2 by right should work out to (1 ** -)2" - that term makes no sense. 1 ** -2 is not ambiguous, it is accepted and parsed as expected in any language.Vagabond
I'm analyzing the statement from a technical computer point of view, say for statement 1 ** 2 + 3 would evaluate to (1 ** 2) + 3, this is because when the compiler parses to "+", because + has lower precedence the computer would process 1 ** 2 first, hence the bracket, then for 1 ** -2, if unary - has precedence over **, this would work out to 1 ** (-2), because when the computer parses to -, it would put opening bracket ( before -, but if ** has higher precedence, the computer would put in the closing bracket so it becomes (1 ** ) -2 [not (1 ** -) 2] just like 1 ** 2 + 3 becomes (1 ** 2) + 3Hulburt
No, that is not how parser grammars for compilers work. Precedence comes only into play for ambiguous terms. (1 ** -) 2 is not a valid parse, regardless of precedence.Vagabond
I'm not sure how compiler parser works, but this is how I as beginner write my own parser, I read the statement from left to right, say 1^2+3, internally I store it as postfix: 1 2 ^ for first 3 chars, then when I encounter +, as ^ has higher precedence so it evaluates 1 2 ^ first which becomes 1, then I do 1 3 + which becomes 4; now for 1^-2, when I encounter -, if - has lower precedence then I would do the previous operation first which is 1 ^, but this can't do as it only has 1 operand so syntax error, but I can tweak my parser to force look for 2nd operand lumping (-2) as the 2nd operandHulburt
This sounds like an implementation of the shunting yard algorithm, which is not able to handle unary operators at all. Yes, you'll need to tweak it to support both binary and unary -, and also you'll need to tweak your storage format as reverse polish notation cannot have overloaded operators. But this has nothing to do with the precedence of **, the naive algorithm doesn't work for a - - b or a + - b - - - c or a * - b either.Vagabond

© 2022 - 2024 — McMap. All rights reserved.