Bug 15288 – The precedence of the exponentiation operator ^^ is too high.
Status
RESOLVED
Resolution
WONTFIX
Severity
enhancement
Priority
P4
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2015-11-05T08:39:59Z
Last change time
2023-06-28T10:09:38Z
Assigned to
No Owner
Creator
thomas.bockman
Comments
Comment #0 by thomas.bockman — 2015-11-05T08:39:59Z
Currently, the exponentiation operator `x ^^ y` always returns a value of the same type as the base `x`.
I believe this should be changed to follow type promotion rules like those used by the conceptually related shift operators, or by the Phobos `std.math.pow()` function.
Key changes needed:
1. Promote small integral types (like `byte` or `ushort`) to `int`, as all other operators do.
2. If the exponent is floating-point, promote the base to floating-point as well.
Benefits:
1. Aligning the behaviour of exponentiation with the other operators is necessary to satisfy the "principle of least surprise".
2. Promoting small integer types generally leads to more efficient code generation.
The following sample code highlights the discrepancies between the built-in operator and the Phobos function:
module main;
import std.stdio;
import std.traits : Unqual, NumericTypeList;
import std.math : pow;
void main(string[] args) {
foreach(N; NumericTypeList) {
foreach(M; NumericTypeList) {
const builtIn = cast(N)1 ^^ cast(M)1;
const phobos = pow(cast(N)1, cast(M)1);
static if(!is(typeof(builtIn) == typeof(phobos))) {
writeln("(" ~ N.stringof ~ ") ^^ (" ~ M.stringof ~ ") == " ~ Unqual!(typeof(builtIn)).stringof);
writeln("pow(" ~ N.stringof ~ ", " ~ M.stringof ~ ") == " ~ Unqual!(typeof(phobos)).stringof);
writeln();
}
}
}
stdin.readln();
}
Comment #1 by thomas.bockman — 2015-11-05T18:03:22Z
So, I just realized what's actually going on here: the exponentiation operator *is* using the correct promotion rules, but, bizarrely, has higher precedence than the cast operator:
I expect this:
cast(N)1 ^^ cast(M)1
To be evaluated like this:
(cast(N)1) ^^ (cast(M)1)
But it is instead evaluated like this:
cast(N)(1 ^^ cast(M)1)
I'm not sure if anyone else actually uses this operator with integers, but if so I'd bet there are some bugs in the wild because of this.
Looking at the operator precedence table on the dlang wiki:
http://wiki.dlang.org/Operator_precedence
I would say that groups 12 and 13 should be swapped. Otherwise you get truly weird stuff like:
int[2] xs = [ 1, 2 ];
int* y = &(xs[0]);
int z = *++y ^^ 7; // ERROR: incompatible types for ((y) ^^ (7)): 'int*' and 'int'
Because this:
*++y ^^ 7
Is interpreted as this:
*++(y ^^ 7)
Rather than what I would expect:
(*++y) ^^ 7
Not only is this unexpected, it is also silly:
* Exponentiation is not even defined for pointer types
* The result of exponentiation cannot be incremented: it is an rvalue.
* The dereference operator is not defined for integer types
Comment #2 by dkorpel — 2021-09-08T15:35:19Z
-2^2 in math evaluates to -(2^2) = -4, not (-2)^2 = 4. This is fairly consistent in e.g. Wolfram Alpha, Google search's calculator, Python (`-2**2` in that case) etc. So I think it's good that D also has -2^^2 = -4. It makes less sense for the cast operator or pointer operators I agree, but do you really want to add another precedence level for that? To me, that sounds like it would only complicate things more. At least now you know the precedence of every unary operator is the same.
Comment #3 by razvan.nitu1305 — 2023-06-28T10:09:38Z
Yes, we are far too late in the game to be doing such precedence changes now.