-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Exponentiation operator #2585
Comments
Why not |
Is this really so much better than using static System.Math;
var result = Pow(a, b); |
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
|
👍 |
Yes, a However, would it be possible to add this operator and not interfere with the dereference operator ( F# also uses |
VB's The second argument about math operations being easier to read is the more relevant argument in my opinion. |
VB's |
@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. |
I think more pertinent to this specific use case is that the operator can be overloaded whereas |
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 ( 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
Not exactly. There are some small differences. For example, the LINQ expression tree for the VB operator uses a binary expression of type @gregsdennis It is possible to overload the VB operator from C# manually, i.e. by defining a static 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. |
Citation? :) |
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 In the code below, class
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 |
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 (dotnet/roslyn#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. |
Related #14665 but like @MgSam said please check #15079, if you like the idea join the discussion. :) |
And just because it's common for you, doesn't mean it's common enough to warrant inclusion in the language. :)
No. This is like a member of the LDM asking you what the basis of your claim is. |
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. |
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. |
I agree that the syntactic ambiguity of D and Haskell use 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:
|
It's not clear to me that Currently, in C#:
There clearly aren't any implementation issues there. Considering the example given above:
The above I'm fairly neutral regarding |
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 If you are in |
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'". |
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. |
API Port telemetry says that
The same data says that |
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. :) |
A sqrt operator would be superfluous. It's just |
A ** operator would be superfluous. It's just a call to Math.Pow. :) |
An exponentiation operator is generic (general-use, not type generic), not a special case like a sqrt operator would be. |
For what I believe to be an exceptionally rare scenario I don't see why this is a problem. The language shouldn't optimize for cases that almost never occur.
It's close enough. And still, handling this for one specific case does exceptionally little to move the bar here compared to something like constant expressions which could enable the use of |
That's the thing - I think about this less in terms of "how often is it used today" and more "how do we bring many more to the language".
What are those examples? I don't see anything offered or needed quite as fundamental as exponents.
I fully agree with this. There is either a discussion or an issue about this - I can't remember which one it is at the moment. I would definitely support that one being championed ahead of the exponent operator. |
Suggestion for those working on heavily math projects currently: define some extension methods on public static double Pow(this double This, double exponent) => Math.Pow(This, exponent);
double area = side.Pow(2); // easier to write than Math.Pow(side, 2) and can almost be thought of as an operator placeholder |
I am highly suspicious of this statement. I don't believe the addition of any specific language support will move this needle at all. It's not the language that matters, it's the ecosystem and frameworks around it.
Also, with .NET 6.0 many of the https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-6/ |
It's all of the above; that's why I also mentioned REPL. I guess I hope the takeaway from this entire thread is not just that some people think exponents are valuable enough to warrant an operator, but that also there are other languages like Python which are inferior in a million ways to C# yet have a lead in the market, due to their simplicity in some facets, particularly for hobbyists and non-programmer scientists. I won't argue that this operator would close that gap all by itself, not even close; but we should think about ways to do so. Obviously C# is NOT a language for beginners, but I think we can move it in that direction with a few simple steps without sacrificing the quality that it has now.
Hmmm doesn't this entirely undo the need for "constant expressions" in C#? |
I would argue that pyspark and scikit do infinitely more to advance Python's adoption in the data sciences space than anything the language does itself. Scala is also very popular in that field and IMO Scala is substantially more complicated than C#. C# does have a REPL but I agree that improved tooling would help drive adoption. I'd like to see something akin to xcode Swift Playground that can combine features from C# Interactive, LINQPad and SharpLab into a very easy to use tool to write C# snippets and visualize their execution. |
An operator for exponents wouldn't be the thing to do this. The main argument being given above is around constants and that applies to so many things that aren't Additionally, the use of things like Its not uncommon to just right something like:
or to have a test to validate the constant value vs the computed expression, etc. Which is the generally more usable scenario where you have some "magic number".
Yesn't. There are still differences between C# constants and JIT constants including where they can be used and not. |
Is that in C# or in other languages as well? It wouldn't be unsurprising that it isn't used much in C#, there isn't a constant supported operator for it. |
Even in other languages. C/C++ don't because Ignoring Now that might sound like a lot, but lets actually break it down:
This means that there is a very very small window of valid integer values that can be represented as powers in the first place; most of which (~45,000 of them; which is ~93.7%) are only valid as a power of two (e.g. These values are also very specific and representa very small portion of the overall 2.14 billion positive integers you can otherwise have. Their usage outside |
You can do similar logic for other ranges, like For |
Something interesting to point out in regards to this is how Postgres uses Operators. To me an interesting way of solving this issue would be to allow people to override a bunch of symbols so they can use some strange operator syntax on classis they create. For example I don't see the need for anyone to use Pow(2,5) since you can calculate it before hand but something like x^2 could make sense for a custom math class. So if they could create a random operator on the class like |/ would be nice for a custom math number class or even a variable class. |
Something I feel is missing from a lot of this discussion is just how ugly math can get if you have to use ReInv = 1.0 / Re
A = (2.457 * LOG(1.0 / ((7.0 * ReInv) ** 0.9 + 0.27 * e_over_d))) ** 16
B = (37530.0 * ReInv) ** 16
FanningFactor = 2.0 * ((8.0 * ReInv) ** 12.0 + 1.0 / (A + B) ** (1.5)) ** Exponent That same code written in C# ReInv = 1.0 / Re
A = Math.Pow(2.457 * Math.Log(1.0 / (Math.Pow(7.0 * ReInv, 0.9) + 0.27 * e_over_d))), 16)
B = Math.Pow(37530.0 * ReInv, 16)
FanningFactor = 2.0 * Math.Pow(Math.Pow(8.0 * ReInv, 12.0) + 1.0 / Math.Pow(A + B, 1.5), Exponent) To me, the main issue is that |
You could add your own extension method to make this infix. then you'd have You full example would be: A = (2.457 * LOG(1.0 / ((7.0 * ReInv).Pow(0.9) + 0.27 * e_over_d))).Pow(16)
B = (37530.0 * ReInv).Pow(16)
FanningFactor = 2.0 * ((8.0 * ReInv).Pow(12.0) + 1.0 / (A + B).Pow(1.5)).Pow(Exponent) Which also reads fine to me. |
There is also notably not a whole lot of difference between the two. You're basically trading The entire expression as written (for either Fortran or C#) is pretty hard to read. If I saw that code in a PR to dotnet/runtime, I'd like flag it and ask for it to be explicitly broken apart so that it's easier to read and debug. I would likely also require an explanatory comment to exist which gives the mathematical algorithm it corresponds to and an explanation of how its accounting for binary floating-point inaccuracies. |
That's a good idea - I think I'll write that extension method. It more or less solves the readability problem for me. |
pow |
I'd have to disagree with this. Let's take a simpler example.
This makes makes my point for me - you're trading a context-giving operator,
Putting something up front doesn't make it clearer, especially when sub-expressions are involved. We read infix operators as infix operators. Clarity is lost, not gained, by rearranging standard math operators to fit syntax. Note, things like
Math-heavy code generally seems difficult at first, but breaking down equations doesn't always make them easier to read. You're inventing new variables for sub-expressions that don't necessarily mean anything by themselves. What about the distance formula (
Sure, but has nothing to do with whether |
I honestly disagree. This reads great to me. Maybe it's not 'better' per se. But it's easy and extremely clear. So there's little motivation to come up with an alternate given that.
|
You have lost no context, the method name makes it very clear what you're doing. In the case of You then have to rationalize that Beyond that, you also have to rationalize all the problems and edge cases that exist with this operator. The fact that it's not efficient for integers, that it can trivially overflow for integers (as per a different comment above, you get around 45k valid combinations and of those, ~42.1k of them are
Yes, it can make it harder to rationalize about, but so can very complex arbitrary expressions. That's why it's typical to break your code apart for readability/maintainability.
This is a terrible way to implement distance. Not only is it incredibly inefficient (calling a function, Even if you were defining it naively and not accounting for precision, you would want to explicitly store double dX = x1 - x2;
double dy = y1 - y2;
return double.Sqrt((dX * dX) + (dy * dy))` Even more ideally, you've appropriately exposed a public static Vector2 Distance(Vector2 x, Vector2 y)
{
float sum = Vector2.Sum(x - y);
return float.Sqrt(sum);
}
Documentation, which exists for the consumer of the API is not the same as code comments, which exists for the implementor/maintainer of an API. One is about the public surface area and the other is about the inner implementation details. |
Note, it's not somewhat ambiguous, It's very ambiguous. For example, this is legal today: using System;
public class C {
public unsafe void M(int b, int* a) {
var x = b ** a;
}
} :D |
What might be more interesting to discuss would be if the language could allow for certain static methods exposed on the type to be accessed like instance methods. That is to say, today you have There is, however, no way to allow for a Perhaps if the language opted to relax its lookup rules to be |
@CyrusNajmabadi My comment was quoting from and responding to @tannergooding's comment - I actually replied here and also acknowledge in replying that the infix nature of an extension method as you suggested is much more readable. |
I work with a lot of It'd be nice if C# comments (including XML doc) had more convenient built-in support for Markdown-like and/or TeX-like syntaxes. Comments are at least as important as "real code", and it feels like they're just ignored for planning and feature development. |
@tannergooding I think your reply really misses the mark on the whole point of this discussion: whether an exponentiation operator would be a useful addition to the C# language. Arguing about whether a specific example is optimal in terms of speed or accuracy doesn't have anything to do with whether an operator adds value. The purpose of my examples was to show the benefit of an operator. Yes, code needs documentation (obviously). Yes, penny pinching CPU cycles could lead someone to trade the brevity of x**3 for x * x * x. Yes, floating point precision should be weighed and accounted for. But what does that have to do with adding an operator? To that end, I won't be addressing the criticism of my specific implementation of the distance formula nor the proposed API changes.
Here's my original example
This is the context to which I was referring: the mental ability to parse what's going on. Is all the information technically there for you? Sure. Should the code be broken down? Probably! But it's (subjectively) harder to read, and thus to me serves as a good example. I will reiterate from other replies: I think the extension method
I'm not advocating for |
I don't really agree with this. The idea here is that we're not inventing anything. We're using what the language already has and supports. :) And our preference is that if the lang already has a good solution for a problem, we'd prefer to keep using that versus introducing a new solution (cuz now you have multiple :)). So we need strong reasons to add features. And that includes strong arguments for why the existing options available at the language level are not sufficient, and thus why something new does need to be "invented" :) |
A lot. If the operator is trivially rejected because it is the wrong thing nine times out of ten or has other super common problems, then that is a reason to not expose the operator. If there is then a simple alternative, like defining an infix API, which solves the problem and introduces less complexity to the language, that then becomes a better direction. Especially if that also gives users more flexibility for the entire set of math APIs rather than being scoped to simply
You can trivially create complex expressions even with purely using operators that are hard to read/understand and even where users will commonly get the logic wrong because they misunderstand things like order of operations. Consider, for example, the following which is only using multiplication and addition. It is part of the real algorithm used to implement (((r04 * C20) + ((((r * C19) + C18) * r02) + ((r * C17) + C16))) * r16) + (((((((r * C15) + C14) * r02) + ((r * C13) + C12)) * r04) + ((((r * C11) + C10) * r02) + ((r * C09) + C08))) * r08) + (((((r * C07) + C06) * r02) + ((r * C05) + C04)) * r04) + ((((r * C03) + C02) * r02) + r) Which is much more readable, but still very verbose and hard to parse/understand, if you break it apart like this (and it could be more readable/understandable with locals and/or the surrounding comments in the actual code): (((r04 * C20)
+ ((((r * C19) + C18) * r02)
+ ((r * C17) + C16))) * r16)
+ (((((((r * C15) + C14) * r02)
+ ((r * C13) + C12)) * r04)
+ ((((r * C11) + C10) * r02)
+ ((r * C09) + C08))) * r08)
+ (((((r * C07) + C06) * r02)
+ ((r * C05) + C04)) * r04)
+ ((((r * C03) + C02) * r02) + r) Code has to be broken apart for readability/legibility. This is even true in real math and is the whole reason Algebra has named variables. |
The probably one (now two) people who wrote a line that looked like that can afford a compilation error I think :) I have felt the few times I write exponentiation it does look ugly, and tears up expressions a bit, especially when I'm translating from a math formula. Not a hill I would die on, but I wouldn't mind an exponentiation operator either. |
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 createSum
,Product
,Quotient
, andDifference
objects respectively.^
is also overloaded to createPower
objects. However^
, being the logical XOR operator, is lower in precedence than the multiplicative operators. Thus parenthesis must be used in expressions likea + b * (c ^ e)
.MSDN - C# Operators
Exponentiation operator in some other languages:
**
^
**
**
**
^
,^^
,**
**
**
**
^
**
*
The text was updated successfully, but these errors were encountered: