dotnet / csharplang

The official repo for the design of the C# programming language
11.06k stars 1.01k forks source link

Proposal: Exponentiation operator #2585

Open dharmatech opened 8 years ago

dharmatech commented 8 years ago

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 *
MouseProducedGames commented 8 years ago

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

gafter commented 8 years ago

Is this really so much better than

using static System.Math;

    var result = Pow(a, b);
paul1956 commented 8 years ago

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 notifications@github.com 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.

whoisj commented 8 years ago

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

:+1:

dharmatech commented 8 years ago

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

HaloFour commented 8 years ago

@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.

LMLB commented 8 years ago

VB's ^ operator can be used in constants.

MgSam commented 8 years ago

@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.

gregsdennis commented 7 years ago

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

JeffreySax commented 7 years ago

@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.

CyrusNajmabadi commented 7 years ago

it is a common mathematical operation

Citation? :)

CyrusNajmabadi commented 7 years ago

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)"

JeffreySax commented 7 years ago

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.

MgSam commented 7 years ago

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.

iam3yal commented 7 years ago

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

CyrusNajmabadi commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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.

JeffreySax commented 7 years ago

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:

dharmatech commented 7 years ago

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#:

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.

CyrusNajmabadi commented 7 years ago

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 "ab" 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.

dharmatech commented 7 years ago

It would cause a syntactic ambiguity. Today we parse "ab" 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.

CyrusNajmabadi commented 7 years ago

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 commented 7 years ago

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.

svick commented 7 years ago

@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).

CyrusNajmabadi commented 7 years ago

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. :)

gregsdennis commented 7 years ago

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

CyrusNajmabadi commented 7 years ago

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

gregsdennis commented 7 years ago

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

JeffreySax commented 7 years ago

@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).

iam3yal commented 7 years ago

@CyrusNajmabadi

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

Can't this be a constant expression?

t9mike commented 6 years ago

Scientific programming is more cumbersome, error prone, and difficult to read when done in C# due to missing exponent operator. I am considering spiting some equation code out into a small F# library just to make it cleaner and avoid a series of Pow method calls. I should not have to do this: exponential operator IS a standard need for many use cases. Why else would so many languages have it?

CyrusNajmabadi commented 6 years ago

Why else would so many languages have it?

By this argument, every language should have every feature that is in several other languages :)

I am considering spiting some equation code out into a small F# library just to make it cleaner

That seems reasonable. I mean... this is why there are so many .net languages (including several owned by MS). Each have their respective strengths, and you should pick the right one for the code you're working on, rather than trying to make each one have all the features of the others. :)

t9mike commented 6 years ago

Having to refactor projects to gain easy access to an operator is a big ask. To accomplish what I need, I'd also have to move common types needed by both libraries into a new library. So library A is split into A1,A2,A3, simply to have more readable exponential code.

sharpninja commented 5 years ago

I completely agree with t9mike. This not just a semantic win, but a performance win. Also, being able to use exponentian in a const is the slam dunk.

Thaina commented 5 years ago

I don't go against the proposal but

a * b ** c is now parsed as (a * b) * (*c)

Do we have a way to correctly parsed this?

Isn't *c could be pointer denotes expression?

Korporal commented 5 years ago

Is this really so much better than

using static System.Math;

    var result = Pow(a, b);

@gafter

Not hugely but it would be neat when defining constants that are powers of some other constant, e.g.

    public static class Values
    {
        public const double RATE_1 = Math.Pow(10, 2); // elicits CS0133
        public const double RATE_2 = 10 * 10;
        public const double RATE_3 = 10 ** 2; // would be a nice feature
    }

There are occasions where its very helpful to define sets of constants explicitly in terms of powers of 2 or 10.

A similar thing applies to enums where we want to explicitly define flags for example, it is very natural and readable to define these as explicit powers of 2 sometimes (yes there are other ways).

YairHalberstadt commented 5 years ago

@korporal

A neater solution to that particular problem would be something like https://github.com/dotnet/csharplang/issues/2379

Korporal commented 5 years ago

@YairHalberstadt - Yes you're right but that in turn depends on C# supporting "pure functions" (or rather referential transparency in functional parlance). I do not know if this latter feature is moving along though.

tannergooding commented 5 years ago

Not hugely but it would be neat when defining constants that are powers of some other constant, e.g.

This wouldn't be possible without the compiler implementing a software version of Pow internally

Most of the math functions currently call into the C Runtime library and are non-deterministic across platforms (and are also not IEEE compliant).

It's a hard backlog item I've been tracking and hope to get fixed eventually, but that will only be available from CoreCLR.

Korporal commented 5 years ago

Not hugely but it would be neat when defining constants that are powers of some other constant, e.g.

This wouldn't be possible without the compiler implementing a software version of Pow internally

Most of the math functions currently call into the C Runtime library and are non-deterministic across platforms (and are also not IEEE compliant).

It's a hard backlog item I've been tracking and hope to get fixed eventually, but that will only be available from CoreCLR.

@tannergooding - C runtime library? the C# compiler has a dependency on that? I guess there's a reason why the compiler can't simply call Pow?

tannergooding commented 5 years ago

C runtime library? the C# compiler has a dependency on that?

Not the C# compiler directly, but the runtime does (as does basically every program in existence, since basically no one wants to reimplement everything themselves 😄).

I guess there's a reason why the compiler can't simply call Pow?

As I mentioned, Math.Pow just forwards to the C Runtime implementation for the current platform. This currently makes the result it returns non-deterministic as various platform/architecture/os configurations all implement it slightly differently.

Unknown6656 commented 5 years ago

Is this really so much better than

using static System.Math;

    var result = Pow(a, b);

It also would enable operator overloading for custom numerical types (e.g. complex numbers etc.)

Korporal commented 5 years ago

C runtime library? the C# compiler has a dependency on that?

Not the C# compiler directly, but the runtime does (as does basically every program in existence, since basically no one wants to reimplement everything themselves 😄).

I guess there's a reason why the compiler can't simply call Pow?

As I mentioned, Math.Pow just forwards to the C Runtime implementation for the current platform. This currently makes the result it returns non-deterministic as various platform/architecture/os configurations all implement it slightly differently.

@tannergooding - OK thanks I think I understand now, but how does the compiler perform this calculation then:

        const double RATE = 1.03458711 * 2.88767357;

If the compiler can do that without calling the C runtime surely it's feasible to do exponentiation? Surely that line above involves some implementation of double multiplication and if so is there not a risk that that might differ slightly from what is eventually present at runtime?

In other words - if I understand you - the value of RATE when calculated by the C# compiler could in principle differ when the same two values are multiplied at runtime on some platform?

I see the ECMA C# standard states:

"The product is computed according to the rules of IEC 60559 arithmetic" (for float/double multiplication, page 173). I can see also that IEC 60559 is (it seems) identical to IEEE 754.

So how do you guarantee that floating point multiplication performed at compile time is identical to floating point multiplication performed at runtime?

tannergooding commented 5 years ago

Because multiplication is a primitive operation supported directly by IL and, generally speaking, directly by your hardware as well. There is a strict definition for its implementation and the C# spec requires that things do that correctly.

Math.Pow is a function call that is generally built by using multiple primitive operations, and while there is a strict IEEE compliant answer (which is, given the input values, it should compute the infinitely precise result and then round to the nearest representable result), not every platform implements that correctly and some make minor trade-offs favoring speed instead of precision.

This is, ultimately, the same reason why C# has it's own floating-point parser. Even though double.Parse has a strict IEEE compliant result it should return, .NET used to have some bugs (which I fixed for .NET Core 3.0), that would cause it to sometimes return an incorrect result.

tannergooding commented 5 years ago

not every platform implements that correctly and some make minor trade-offs favoring speed instead of precision.

It's also very easy to do implement something that isn't correct by accident. IEEE floating-point math != normal math.

For example, normal math states that a + b + c is the same as (a + b) +c which is the same as a + (b + c). However, in IEEE floating-point, this isn't the case as each value is actually just a rounded approximation of the actual value and each value has an epsilon (some even have two) which is the amount you need to add or subtract to become the next or previous representable value.

For example, say you have a function which computes the sum of all values in an array:

public static float Sum(float[] values)
{
    var sum = 0.0f;

    for (int i = 0; i < values.Length; i++)
    {
        sum += values[i];
    }

    return sum;
}

Now say you have 50,000 values where the first value is 500000f and the remaining 49,999 values are 0.000001f. The result of this sum is 500000f, because 0.000001f is less than the epsilon between 500000f and the next representable value (500000.03).

Now, lets switch it up so that the first 49,999 values are 0.000001f and the last element is 500000f. The result of this sum is 500000.06, because the sum of the first 49,999 values is 0.049973357 and the nearest representable value to 500000 + 0.049973357 is 500000.06 (as it is slightly closer than 500000.03).

It's also worth calling out that, if we were falling normal math rules the sum of the first 49,999 values would have been 0.049999, (for which the nearest representable value is 0.0499989986419677734375), but this falls into a similar situation as the first scenario, where due to values generally being an approximation (the nearest representable value) and due to values having different epsilons (which may make the addition do nothing) we don't actually return the "expected" result.

Korporal commented 5 years ago

@tannergooding - OK thanks for the explanation, clearly there are a lot of subtle issues to consider. But much of what you say is only relevant in the case of float/double yes?

The compiler could calculate integer exponentiation without too much trouble, an int raised to an int powers is always an it.

Also in almost all cases that I see, constants expressed using exponentiation are almost always integer, and most of those are either powers of 2 or 10.

So could this be implemented for integers at least?

Thaina commented 5 years ago

@Korporal

an int raised to an int powers is always an it.

I think the reason that Math.Pow was return double even you input an int because 2^33 is larger than int. And 2^65 is even larger than long. We have no integer type to capture it

iam3yal commented 5 years ago

@Thaina

We have no integer type to capture it

Off-topic but you can use BigInteger.Pow even though Math.Pow(2, 65) is perfectly valid.

Thaina commented 5 years ago

@eyalsk Well because I don't think that was the integer type. It actually a class and it was not support by Math.Pow

And also will it really fine to have exponent operator return BigInteger as default?