csharplang icon indicating copy to clipboard operation
csharplang copied to clipboard

Proposal: Exponentiation operator

Open dharmatech opened this issue 9 years ago • 133 comments

Consider adding an exponentiation operator that is higher in precedence than the multiplicative operators but lower in precedence than the unary operators.

In the Symbolism computer algebra library, +, *, /, and - are overloaded to create Sum, Product, Quotient, and Difference objects respectively. ^ is also overloaded to create Power objects. However ^, being the logical XOR operator, is lower in precedence than the multiplicative operators. Thus parenthesis must be used in expressions like a + b * (c ^ e).

MSDN - C# Operators

Exponentiation operator in some other languages:

Language Operator
F# **
VB.NET ^
TypeScript **
JavaScript **
Python **
Haskell ^, ^^, **
OCaml **
Swift Not built-in but can be defined
FORTRAN **
Ada **
Nim ^
ALGOL 60 **
APL *

dharmatech avatar Aug 17 '15 19:08 dharmatech

Why not a ** b to represent a ^ b? Prevents problems and confusion with the XOR operator, and should be quickly apparent.

MouseProducedGames avatar Aug 17 '15 19:08 MouseProducedGames

Is this really so much better than

using static System.Math;

    var result = Pow(a, b);

gafter avatar Aug 18 '15 18:08 gafter

Yes, VB has it so it makes translation easier, also makes mathematical expression easier to read but the second point could be debated.

Sent from my iPhone I apologize for any typos Siri might have made. (503) 803-6077

On Aug 18, 2015, at 11:31 AM, Neal Gafter [email protected] wrote:

Is this really so much better than

using static System.Math;

var result = Pow(a, b);

— Reply to this email directly or view it on GitHub.

paul1956 avatar Aug 18 '15 18:08 paul1956

Why not a ** b to represent a ^ b? Prevents problems and confusion with the XOR operator, and should be quickly apparent.

:+1:

whoisj avatar Aug 18 '15 22:08 whoisj

Yes, a ** operator would be nice.

However, would it be possible to add this operator and not interfere with the dereference operator (*)? I guess since the dereference operator works on pointers, which aren't used in exponentiation operations, this seems like it would be OK.

F# also uses **. F# - Arithmetic Operators

dharmatech avatar Aug 18 '15 22:08 dharmatech

@paul1956

VB's ^ operator literally just translates into a call to Math.Pow so there is no additional benefit to porting between the languages. There is also nothing stipulating feature parity or direct porting of programs between the two languages and there are plenty of features between the two that cannot translate.

The second argument about math operations being easier to read is the more relevant argument in my opinion.

HaloFour avatar Aug 19 '15 03:08 HaloFour

VB's ^ operator can be used in constants.

LMLB avatar Apr 27 '16 07:04 LMLB

@LMLB 's argument is the most convincing to me- C# 7.0 is adding digit separators and binary literals for the "defining a constant" use case, and this seems at least as useful as either of those features.

MgSam avatar Apr 27 '16 12:04 MgSam

I think more pertinent to this specific use case is that the operator can be overloaded whereas Math.Pow() cannot.

gregsdennis avatar Jul 14 '16 23:07 gregsdennis

@gafter

Is this really so much better than

using static System.Math;

    var result = Pow(a, b);

It is.

Exponentiation is no different from addition or multiplication: it is a common mathematical operation for which there is a universal notation (including precedence rules) that is approximated in programming languages.

C doesn't have an exponentiation operator, while it does have operators for closer to the metal operations like bit-wise xor (^) and integer shifts. It seems java and C# inherited this design more or less by default without fully considering its merits. Most modern languages that are more oriented towards user-experience (Python, VB, R...) do have an exponentiation operator.

Furthermore, while operators are in scope and will be used when the defining type is in scope, this is not true for the static import in your example. It doesn't work within the scope of a type that itself defines a Pow method, such as a complex number type or a vector type.

@HaloFour

VB's ^ operator literally just translates into a call to Math.Pow

Not exactly. There are some small differences. For example, the LINQ expression tree for the VB operator uses a binary expression of type Power (where the Method is Math.Pow), while C#'s version uses a method call.

@gregsdennis It is possible to overload the VB operator from C# manually, i.e. by defining a static op_Exponent method with a SpecialName attribute. You can do the same for F#, but the operator method is called op_Exponentiation (section 4.1 of the F# language spec). This is an unfortunate and annoying inconsistency.

It would be better if this was all consistent: have all common mathematical operations available as operators that can be overloaded in the same way and that makes them accessible to all .NET languages.

JeffreySax avatar Jan 19 '17 04:01 JeffreySax

it is a common mathematical operation

Citation? :)

CyrusNajmabadi avatar Jan 19 '17 05:01 CyrusNajmabadi

Furthermore, while operators are in scope and will be used when the defining type is in scope, this is not true for the static import in your example. It doesn't work within the scope of a type that itself defines a Pow method, such as a complex number type or a vector type.

If you're in the type itself, you don't need the static import. You can just directly call "Pow(x, y)"

CyrusNajmabadi avatar Jan 19 '17 05:01 CyrusNajmabadi

If you're in the type itself, you don't need the static import. You can just directly call "Pow(x, y)"

But you can't access another type's Pow method that is statically imported without qualifying.

In the code below, class A's M method is successfully called from class B. However, in class C, the presence of the M method prevents it from being considered, unless you qualify it:

namespace N1
{
    public class A
    {
        public static void M(string x) { }
    }
}
namespace N2
{
    using static N1.A;

    public class B
    {
        public void M2()
        {
            M("Test"); // OK
        }
    }

    public class C
    {
        public static void M(int x) { }
        public void M2()
        {
            M(12); // OK
            M("Test"); // 'Argument 1: Cannot convert from string to int'
            N1.A.M("Test"); // OK
        }
    }
}

Citation? :)

This is like someone from Texas asking a Canadian for a citation that snow is a common weather phenomenon. :) Just because something isn't common in your experience doesn't mean it's not common for other people.

But since you asked: the code sample in the documentation for the static using directive contains two squares, written out as multiplications.

JeffreySax avatar Jan 19 '17 12:01 JeffreySax

Is exponentiation more common or important than square root? Where do you draw the line of which operations are important enough to deserve an operator?

The only benefit, IMHO, is that you can use mathematical operators statically. However, I think the constexpr feature (https://github.com/dotnet/roslyn/issues/15079) is a lot more general so I'd be in favor of that to fulfill that use case rather than adding a new operator.

MgSam avatar Jan 19 '17 14:01 MgSam

Related #14665 but like @MgSam said please check #15079, if you like the idea join the discussion. :)

iam3yal avatar Jan 19 '17 14:01 iam3yal

Just because something isn't common in your experience doesn't mean it's not common for other people.

And just because it's common for you, doesn't mean it's common enough to warrant inclusion in the language. :)

This is like someone from Texas asking a Canadian for a citation that snow is a common weather phenomenon

No. This is like a member of the LDM asking you what the basis of your claim is.

CyrusNajmabadi avatar Jan 19 '17 15:01 CyrusNajmabadi

Claim: the scientific computing community needs better representation on the C# LDM. Citation: this thread.

This thread has 16 comments over an entire year of being open. There are only about 8 non-MS people even commenting on this. This seems to have very little interest (contrast with threads with dozens of people and hundreds of messages). Furthermore, the existence of interest does not mean that we should ,or will, take a request. We have literally hundreds to thousands of small requests like this. We definitely will not be taking the vast majority of them. We have to actually consider how important this is to the entirety of our ecosystem. Right now, the interest and impact of this feature are both low.

CyrusNajmabadi avatar Jan 19 '17 16:01 CyrusNajmabadi

I have now seen that the comment i replied to has been deleted. So i'd like to nip this side of the conversation in the bud. My overall point is simply that this seems to be a feature without that much impact with very little interest.

--

From a technical perspective, i'm also worried about breaking changes here. Today, this is already legal code:

unsafe void X(int* p, int c)
{
    var v = c**p
}

We'd likely have to do some sort of unpleasant contortions to ensure we didn't break existing code like this.

CyrusNajmabadi avatar Jan 19 '17 17:01 CyrusNajmabadi

I agree that the syntactic ambiguity of ** is a large negative to the point where it's likely not worth the trouble.

D and Haskell use ^^, which isn't great, but isn't terrible, either. It does not have any syntax issues that I'm aware of.

To give you an idea of how common the operation is, I looked at one of our projects, which is heavy on technical computing. About 0.9% of code lines contains an exponentiation. It is much more common than shifts or xors. Element-wise operations on arrays/collections are also quite common.

Although it is not primary evidence of commonality, the fact that the operator is included in many programming languages suggests that others found it sufficiently common to include in the language. Some of these languages had very tight constraints, like Altair BASIC, which fit in 8192 bytes (see manual (PDF), page 27).

Microsoft provides tooling for many languages that have the exponentiation operator:

JeffreySax avatar Jan 24 '17 18:01 JeffreySax

It's not clear to me that ** as an operator would cause an issue due to a conflict with the dereferencing operator.

Currently, in C#:

  • & is used to obtain an address
  • & is used for logical AND
  • && is conditional AND

There clearly aren't any implementation issues there.

Considering the example given above:

var v = c**p;

The above ** cannot be interpreted as a exponentiation operator because that would require treating p as an exponent and p is a pointer.

I'm fairly neutral regarding ** vs ^^. However, ** seems more common. If there isn't a strong technical reason against **, it seems like a good option.

dharmatech avatar Jan 24 '17 23:01 dharmatech

It's not clear to me that ** as an operator would cause an issue due to a conflict with the dereferencing operator.

It would cause a syntactic ambiguity. Today we parse "a**b" as "a * (*b)" i.e. "a times the deref of b". With this new operator, we'd now parse that as "a ** b".

We do not change parse trees for correct code. So we would have to still parse things like the old way, but at semantics time, see if we had this pattern, and we'd have to determine what would be the right thing to do.

CyrusNajmabadi avatar Jan 25 '17 00:01 CyrusNajmabadi

It would cause a syntactic ambiguity. Today we parse "a**b" as "a * (*b)" i.e. "a times the deref of b". With this new operator, we'd now parse that as "a ** b".

We do not change parse trees for correct code. So we would have to still parse things like the old way, but at semantics time, see if we had this pattern, and we'd have to determine what would be the right thing to do.

If the code isn't in unsafe mode, then ** is unambiguously exponentiation.

If you are in unsafe mode and the right operand of ** is a pointer, then dereference and multiply. Otherwise, exponentiation.

dharmatech avatar Jan 25 '17 00:01 dharmatech

If you are in unsafe mode and the right operand of ** is a pointer, then dereference and multiply. Otherwise, exponentiation.

yes. this could be done. But now we have the same syntax meaning different things with operators. I'm not a fan of this. ++ doesn't mean something different depending on context. i.e. we don't say "oh, in a++b, there is no ++ operator for 'a', this this is 'a added to plus-b'".

CyrusNajmabadi avatar Jan 25 '17 00:01 CyrusNajmabadi

It's not clear to me that ** as an operator would cause an issue due to a conflict with the dereferencing operator.

There is clearly a conflict here. The question is: is that acceptable? For example, there's a syntactic ambiguity with: (A)-B. That could be "minus B, casted to the A type". Or it should be "the value of parenthesized expr A, minus the value of B". We went with the former for C#. But once we decide on something, we don't change it**.

Here, we'd have to always parse this as "A times the deref of B". But we'd then have to have some special rule that says "if you have that, and there is no space between the operators, and B is derefable, then behave like so, otherwise, behave like so". That's pretty janky, and not something we've ever done before really.

--

** Ok, we also changed it for generic parsing: X(A<B, D>(E))

We changed the parsing here to make this into a nested invocation call, instead of two relational expressions. However, we're very wary of changing parsing. And i'm personally of hte opinion that it's very janky for us to special case the treatment of "a ** b" to processed differently depending on if 'b' is dereferencable or not.

CyrusNajmabadi avatar Jan 25 '17 00:01 CyrusNajmabadi

@CyrusNajmabadi

it is a common mathematical operation

Citation? :)

API Port telemetry says that Math.Pow() is used in 11 % of apps. Not sure if that is common enough.

@MgSam

Is exponentiation more common or important than square root? Where do you draw the line of which operations are important enough to deserve an operator?

The same data says that Math.Sqrt() is used in 7 % of apps. So it is less common than exponentiation, but not by much. But while it's fairly easy to come up with decent operators for exponentiation (whether it's ** or ^^), I don't see what would be a good operator for square root (I don't think √ a.k.a. U+221A SQUARE ROOT is an option).

svick avatar Jan 25 '17 01:01 svick

API Port telemetry says that Math.Pow() is used in 11 % of apps. Not sure if that is common enough.

I'd want to know how often it was actually used. If 11% of all apps use the call once, then i have no problem with them using Math.Pow. :)

CyrusNajmabadi avatar Jan 25 '17 01:01 CyrusNajmabadi

A sqrt operator would be superfluous. It's just x^^0.5; a special case of <1 exponentiation.

gregsdennis avatar Jan 25 '17 01:01 gregsdennis

A ** operator would be superfluous. It's just a call to Math.Pow. :)

CyrusNajmabadi avatar Jan 25 '17 01:01 CyrusNajmabadi

An exponentiation operator is generic (general-use, not type generic), not a special case like a sqrt operator would be.

gregsdennis avatar Jan 25 '17 01:01 gregsdennis

@dharmatech There is no ambiguity with && because && is always parsed as the short-circuit and operator. Same thing with ++ and --: c = a++b is a syntax error because ++ is interpreted as the increment operator. It would have been a little bit harder to do for ** (because multiple derefs on one value are possible and meaningful), but it's simply too late now.

@gregsdennis There is decades of precedent for an exponentiation operator (FORTRAN-66 had it), but none for a square root operator. Not even APL has one.

@CyrusNajmabadi It actually gets even more complicated with precedence rules and the fact that pow is right-associative. For example, a * b ** c is now parsed as (a * b) * (*c), but the correct interpretation with pow is a * (b ** c). So you can have a semantic tree that is very different, even structurally, from the syntax tree. You can't just say that the multiplication is really a power, and the deref is really a no-op: The arguments are different.

@svick Very interesting, thanks! That's probably a slight under-estimate because squares (and other small powers) are often optimized into multiplications. Math.Pow is 2 orders of magnitude slower than squaring by multiplying (but that's a different issue).

JeffreySax avatar Jan 25 '17 04:01 JeffreySax