Git Product home page Git Product logo

Comments (133)

MouseProducedGames avatar MouseProducedGames commented on August 15, 2024 50

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

from csharplang.

Happypig375 avatar Happypig375 commented on August 15, 2024 32

Let's have a vote...

Symbol Emoji Note
^^ 👍
*^ 🚀
** ❤️ I doubt that the LDM would approve changing the lexer for this though - it already lexes as multiplication + pointer dereference and lexing based on identifier types will result in a complex implementation
Another symbol 👀 Please comment the operator you want

from csharplang.

JeffreySax avatar JeffreySax commented on August 15, 2024 27

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

from csharplang.

JeffreySax avatar JeffreySax commented on August 15, 2024 26

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:

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024 24

it is a common mathematical operation

Citation? :)

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024 20

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

from csharplang.

gregsdennis avatar gregsdennis commented on August 15, 2024 19

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

from csharplang.

JeffreySax avatar JeffreySax commented on August 15, 2024 16

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.

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024 16

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.

from csharplang.

dharmatech avatar dharmatech commented on August 15, 2024 13

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

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024 13

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.

from csharplang.

LMLB avatar LMLB commented on August 15, 2024 12

VB's ^ operator can be used in constants.

from csharplang.

dharmatech avatar dharmatech commented on August 15, 2024 12

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.

from csharplang.

MgSam avatar MgSam commented on August 15, 2024 11

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

from csharplang.

gregsdennis avatar gregsdennis commented on August 15, 2024 11

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

from csharplang.

svick avatar svick commented on August 15, 2024 11

@Korporal Today:

   [FlagsAttribute] 
   enum SingleHue : short
   {
      None  = 0,
      Black = 1 << 0,
      Red   = 1 << 1,
      Green = 1 << 2,
      Blue  = 1 << 3
   };

Also today:

   [FlagsAttribute] 
   enum SingleHue : short
   {
      None  = 0b0000,
      Black = 0b0001,
      Red   = 0b0010,
      Green = 0b0100,
      Blue  = 0b1000
   };

from csharplang.

svick avatar svick commented on August 15, 2024 10

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

from csharplang.

gafter avatar gafter commented on August 15, 2024 9

Is this really so much better than

using static System.Math;

    var result = Pow(a, b);

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024 9

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

from csharplang.

iam3yal avatar iam3yal commented on August 15, 2024 7

@CyrusNajmabadi

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

Can't this be a constant expression?

from csharplang.

t9mike avatar t9mike commented on August 15, 2024 7

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?

from csharplang.

Unknown6656 avatar Unknown6656 commented on August 15, 2024 7

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

from csharplang.

MgSam avatar MgSam commented on August 15, 2024 6

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.

from csharplang.

tannergooding avatar tannergooding commented on August 15, 2024 6

or on the BCL side, some math functionality in classes, etc.; and then prioritize those and tackle 1 or 2 every version.

This is independent of the language and is as simple as opening an API proposal here: https://github.com/dotnet/runtime/issues/new?assignees=&labels=api-suggestion&template=02_api_proposal.yml

I'm the area owner and the person who would drive such proposals forward

  • Provided I agree with the use case and that it would be beneficial, etc. I'm generally open to hear ideas, discuss them, and get feedback, so please open an issue or start a discussion thread and tag me if you have an idea

Basically, it's not "all or nothing"; small steps can add up over time; and it does not mean turning away from generality.

Certainly, and both C# and .NET in general have made leaps and bounds here, particularly in the BCL and what it supports natively. I've listed per release changes below and I've probably missed a few :)


C# 1.0/.NET 1.0 (2002)

Integer Types

  • SByte, Int16, Int32, Int64
  • Byte, UInt16, UInt32, UInt64
  • IntPtr, UIntPtr

Floating-Point Types

  • Single, Double

System.Math

  • Mostly double-precision float support
  • A few operations had Integer or single-precision float support

.NET 4 (2010)

System.Numerics introduced

  • BigInteger
  • Complex

Initial SIMD Support, Out of Band (2014)

New Types

  • Vector
  • Vector2, Vector3, Vector4
  • Matrix3x2, Matrix4x4
  • Plane, Quaternion

Generally graphics oriented, minus Vector which was general purpose


.NET Core 2.0 & 2.1 (2017-2018)

System.MathF

  • APIs supporting float

Start of push for IEEE 754 compliance

  • IsFinite, IsNegative, IsNormal, IsSubnormal
  • Acosh, Asinh, Atanh, Cbrt

.NET Core 3.0 & 3.1 (2019)

More IEEE 754 Math

  • BitIncrement, BitDecrement
  • CopySign
  • FusedMultiplyAdd
  • MinMagnitude, MaxMagnitude
  • ILogB, Log2, ScaleB

Additional Rounding Modes

  • ToNegativeInfinity, ToPositiveInfinity, ToZero

Floating-point parsing/formatting fixes

x86/x64 Hardware Intrinsics

  • Vector64, Vector128, Vector256
  • SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, AVX, AVX2, FMA
  • AES, BMI1, BMI2, LZCNT, PCLMULQDQ, POPCNT

System.Numerics.BitOperations introduced

  • LeadingZeroCount, TrailingZeroCount, PopCount
  • Integer based Log2
  • RotateLeft, RotateRight

.NET 5 (2020)

New Types

  • System.Half
  • nint, nuint

New Math APIs

  • Tau
  • BigMul

Interchange APIs between System.Numerics and System.Runtime.Intrinsics vector types

ARM64 Hardware Intrinsics

  • AdvSimd, DP
  • AES, CRC32, RDM, SHA1, SHA256

.NET 6 ( 2021)

More Math APIs

  • nint, nuint support
  • Tuple returning DivRem
  • ReciprocalEstimate, ReciprocalSqrtEstimate
  • SinCos

Random.NextInt64

System.Numerics APIs

  • BitOperations.IsPow
  • BitOperations.RoundUpToPowerOf2
  • Vector nint & nuint support
  • Vector.Sum

Generic Math Preview: https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/


.NET vFuture

Generic Math (dotnet/designs#205)

Cross Platform Hardware Intrinsic Helper APIs (dotnet/runtime#49397)

Numerous other approved APIs and changes: https://github.com/dotnet/runtime/issues?q=is%3Aopen+is%3Aissue+label%3Aarea-System.Buffers%2Carea-System.Numerics%2Carea-System.Numerics.Tensors%2Carea-System.Runtime%2Carea-System.Runtime.Intrinsics+label%3Aapi-approved+approved

  • nint/nuint support for hardware intrinsics
  • General Purpose CRC32 APIs
  • Support for parsing/formatting hexadecimal based floats
  • DivMod intrinsic for x86/x64
  • TableVectorLookup intrinsic for ARM64
  • ShiftLeft/ShiftRight APIs for Vector and xplat hardware intrinsics
  • more missing math functions
  • etc

from csharplang.

t9mike avatar t9mike commented on August 15, 2024 5

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.

from csharplang.

HaloFour avatar HaloFour commented on August 15, 2024 5

Having said that, given how many other languages have an operator for exponents, and that it's a very common notation in mathematical equations/formulas, it does seem like a glaring omission.

But it's not all that common in general programming. It's a slippery slope as to which of the many arithmetic operations should be promoted to language operators. C# will never be written like arithmetic formulas. Neither will Python for that matter. Having to change the language seems unnecessary when the operation can be exposed as a method.

from csharplang.

paul1956 avatar paul1956 commented on August 15, 2024 4

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.

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024 4

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

from csharplang.

TahirAhmadov avatar TahirAhmadov commented on August 15, 2024 4

Part of what makes C# C# is that it is a generalist language.

I agree, and that's exactly why I prefer it to other languages.

Put another way. Adding exponentiation isn't going to be the feature that substantively moves the dial here (IMO). So we should either commit to 'math oriented' being a goal of hte language (with a broad look at the future direction and what that would mean), or we should take it as what it is, and not just add a one-off feature here and there because it's an annoyance to a specific group.

I understand this perfectly, and here's my suggestion: why not collect a list of potential improvements to make C# more math-friendly, be it an operator here, or some construct there, or on the BCL side, some math functionality in classes, etc.; and then prioritize those and tackle 1 or 2 every version. That way, eventually the language will be decently usable for math projects and it can be offered as a viable alternative to beginners who want to solve math problems, particularly scientists and science students. That would be a great new source of user base. Some recent changes have already been made in that direction, such as top level programs (whether intentionally or not). Another addition might be a REPL that's supported by the language team.
Basically, it's not "all or nothing"; small steps can add up over time; and it does not mean turning away from generality.

from csharplang.

gregsdennis avatar gregsdennis commented on August 15, 2024 3

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

from csharplang.

JeffreySax avatar JeffreySax commented on August 15, 2024 3

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

from csharplang.

sharpninja avatar sharpninja commented on August 15, 2024 3

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.

from csharplang.

Korporal avatar Korporal commented on August 15, 2024 3

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

from csharplang.

TahirAhmadov avatar TahirAhmadov commented on August 15, 2024 3

@CyrusNajmabadi think about it this way: yes currently there aren't many people on this thread requesting the exponent operator, and that's because there aren't many who use C# for mathematically-focused workloads. And that's because they're all using other languages, specifically one very venomous one. I think this is actually a very worthy investment for C#, and this is coming from a programmer who almost never uses exponents.

from csharplang.

dharmatech avatar dharmatech commented on August 15, 2024 2

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.

from csharplang.

Korporal avatar Korporal commented on August 15, 2024 2

@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

@Thaina

I know.

However a purely int ** int support would be a neat addition to the language. It doesn't exist yet and so would not be a breaking change.

Defining constants that involve exponentiation - as I mentioned - seems to me to almost always be int ** int anyway and this is where I'd find the feature most useful as well as for operator overloading.

Of course a negative exponent will lead to non integer results but these are always rational numbers and could be represented as decimal...

from csharplang.

Korporal avatar Korporal commented on August 15, 2024 2

So do others think this proposal should be amended to restrict the scope of a new ** operator to integers and with positive exponents? Since this would be an entirely new operator it would not be a breaking change and since we already have Math.Power() which is itself confined to float/double the restricted scope of ** is not unreasonable?

The benefits of this restricted operator are therefore:

  1. Requires no support from the C runtime library.
  2. Can be reliably calculated at compile time and runtime by sharing the (new) implementation.
  3. Would be platform agnostic.
  4. Becomes available for use in defining compile time constant expression that use ** for readability.

from csharplang.

Happypig375 avatar Happypig375 commented on August 15, 2024 2

C++ once considered *^ but nobody pursued this option.
This is like private protected - it's similar to existing operators but a bit different. Thoughts?

from csharplang.

Happypig375 avatar Happypig375 commented on August 15, 2024 2

^^ would be more natural to type though. C++'s rationale for this breaking the expectation of && being short-circuit & and || being short-circuit | doesn't really hold because there is no way to short-circuit ^.

from csharplang.

Rekkonnect avatar Rekkonnect commented on August 15, 2024 2

Just noting here that neither ^^ and *^ can be used, much like *, due to the existence of ^ as both a binary and a preceding unary operator (indexing from the end). This means we have to either consider a completely unrelated symbol to denote exponentation, or use a 3-symbol operator like:

  • **^
  • ^**
  • *^*

None of those can cause syntactic ambiguity yet, but ^** could prohibit usage of the ^ operator after an expression for whatever purpose it could be reserved for, causing the ambiguity between (a^) * (*b) and (a) (^**) (b) (parentheses used for grouping; actual syntax would be invalid).

Speaking of the above, parenthesizing the operators for grouping is another valid solution, which would allow for extending the ** operator to supporting higher hyperoperators by concatenating *s together (*** for power towers, **** for power tower towers, etc.). In this case, exponentation would be denoted as a (**) b, resolving any syntactic ambiguities.

from csharplang.

tannergooding avatar tannergooding commented on August 15, 2024 2

My concern, particularly as someone who works frequently with unsafe code and numerics, is that its going to make being able to read and reason about the code that much harder.

My own cognitive load is going to increase as it can now also mean exponent (in addition to multiply or dereference) and the exact whitespace, left/right hand side types, and more can change how its interpreted which is going to make reviewing PRs involving these a pain.

It would be slightly better if whitespace requirements were consistent, but its not. Likewise, while multiplying by a pointer doesn't necessarily make sense, its also already allowed for user-defined operators which makes it even more complicated:

  • public unsafe static C operator *(C left, int* right) => throw null;

More in general (and I believe I've commented on similar proposal before) I'm still not convinced of the overall value here:

  • The general perf of these functions isn't going to be great
  • A deterministic and IEEE 754 compliant function would need to be written for floating-point
  • Such an operator likely needs to get worked into generic math (and all the considerations it entails)
  • This is going to have to consider checked behavior which will overflow for even simple inputs because the maximum inputs is 2^63 for a ulong. As the LHS goes up, the RHS goes down; so most inputs will overflow and not be useful (the allowed inputs end up being fairly precise otherwise: 4294967296^2, 2097152^3, 65536^4, 4096^5, etc; where after x^5 you start getting into fractionals and numbers that don't work for integers)
    • I imagine the most common usages here are for powers of 2, in which case better BCL based options exist
    • For integer types, the RHS cannot be negative (or must truncate to 0 if it is)
    • BigInteger itself is a slightly different story where it can be beneficial to support and where the type already exposes an API

To me, it would be helpful to see some real world sample code that:

  1. Needs exponent operations and frequently
  2. Where the readability of the code is significantly improved by having an exponentiation operator rather than breaking apart the algorithm with a Math.Pow call.

In my own experience, both of these are rare and are limited to BigInteger or floating-point. The places where I commonly need similar for integers deals with powers of 2 (where left/right shift is a better option) or constants.

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024 1

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.

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024 1

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.

from csharplang.

dharmatech avatar dharmatech commented on August 15, 2024 1

@tompazourek

Can you explain what you'd consider a full numeric tower? Maybe some links?

Ah, sorry. :-) It's a term used in the Lisp and Scheme worlds.

So I just meant that, once you have arbitrary precision integers and rationals available in a language, an exponential operator starts to make sense for many more of the cases that were considered awkward when only the native types are available (the cases you mentioned like fractional, negative, and large exponents).

The exponentiation operator proposal here is not only intended for the native types (it should have some reasonable defaults for these wherever possible) but to also serve in the case where the user would like to use the operator in the context of a more complete numerical library.

So for example, even if the team decided that the basic ^^ operator only worked on double (I'd be OK with that. This is how the default ** operator works in F#, for example.), it could be overloaded by users and library designers of more complete numeric libraries.

from csharplang.

Xyncgas avatar Xyncgas commented on August 15, 2024 1

I need this

from csharplang.

heartacker avatar heartacker commented on August 15, 2024 1

maybe we can use # as pow operator.
let us SHARP #:

var a = b#c;

:)

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024 1

That way, eventually the language will be decently usable for math projects

Here's hte disconnect. The view on what it 'decently usable'. There isn't consensus on this, and you'll have the spectrum of people going:

  1. it already caters too much to math. Yes, we have this with people who don't like things like operating overloading.
  2. it caters just fine to it. lots of thigns work nicely, and it's not a great burden to colorize outside of those lines.
  3. it doesn't go far enough at all. we need lots more features.

We cannot, and should not necessarily cater to '3' all the time. Even if it's trickling in, it's not necessarily a goal for c# to get to that point, and it can actively be seen as harmful as all that complexity now has to be worked through (both at the individual level, and how it affects everything else).

small steps can add up over time;

So, IMO, we already do this. It's just that that genuinely may mean that a particular feature (like this one) just may not be relevant. It might be, for example, that we extend things like operators to work on tuples/arrays/etc. as that may be more pressing and much more painful to not have.

So even if we go this route, I would say that you should not expect that that would mean that exponentiation would ever meet the bar as needing direct lang syntax.

from csharplang.

whoisj avatar whoisj commented on August 15, 2024

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

👍

from csharplang.

HaloFour avatar HaloFour commented on August 15, 2024

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

from csharplang.

iam3yal avatar iam3yal commented on August 15, 2024

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

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024

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.

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024

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

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024

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

from csharplang.

Thaina avatar Thaina commented on August 15, 2024

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?

from csharplang.

YairHalberstadt avatar YairHalberstadt commented on August 15, 2024

@Korporal

A neater solution to that particular problem would be something like #2379

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

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

from csharplang.

tannergooding avatar tannergooding commented on August 15, 2024

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.

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

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?

from csharplang.

tannergooding avatar tannergooding commented on August 15, 2024

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.

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

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?

from csharplang.

tannergooding avatar tannergooding commented on August 15, 2024

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.

from csharplang.

tannergooding avatar tannergooding commented on August 15, 2024

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.

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

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

from csharplang.

Thaina avatar Thaina commented on August 15, 2024

@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

from csharplang.

iam3yal avatar iam3yal commented on August 15, 2024

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

from csharplang.

Thaina avatar Thaina commented on August 15, 2024

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

from csharplang.

iam3yal avatar iam3yal commented on August 15, 2024

@Thaina I doubt it.

from csharplang.

Thaina avatar Thaina commented on August 15, 2024

@Korporal My point is what is the default return type you want for this operation?

If you think it should be Int32. Then whenever there was a code var x = 2**33; should it be error in the compile time or throwing integer overflow at the runtime. Or just silently become int.MaxValue ?

If it was const int x = 2**33; should it error or when it should throw exception?

Exponent was so easily go out of bound of the integer. Just 9^10 is larger than int32 so what should we do?

I think exponent is not practical to return as int32. We might need BigInteger or just accept double as return type

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

@Thaina - Here's my quick analysis of that question.

The smallest int power we can use is 2, the smallest int we can raise to a power is 2 (well 1 but that leads to no change).

This means that a Byte is sufficient for the exponent (disregarding -ve exponent support) because we can never even reach an exponent of 256 even for the smallest base of 2.

The largest result we can support is an Int64 (supporting +ve or -ve bases) that has a Max of 9223372036854775807.

The square root of that is the max value we could ever square (the smallest power we could use) which is 3037000499 which is B504 F333.

That exceeds the capacity of an Int32 so must be made an Int64.

Therefore the signature is a single function:

        public static Int64 Raise(Int64 Base, Byte Power)

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

Another use of such a feature (that is when used to define compile time constants) is for defining flag enum values.

today:

   [FlagsAttribute] 
   enum SingleHue : short
   {
      None  = 0,
      Black = 1,
      Red   = 2,
      Green = 4,
      Blue  = 8
   };

tomorrow (?)

   [FlagsAttribute]    
   enum SingleHue : short
   {
      None  = 0,
      Black = 2 ** 0,
      Red   = 2 ** 1,
      Green = 2 ** 2,
      Blue  = 2 ** 3
   };

The latter is easier and less error prone to review when scanning the code visually and gaps or errors are likely easier to see, pretty helpful especially if the flags is a 32 or 64 bit int type where we might have a large list of flags.

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

@Korporal Today:

   [FlagsAttribute] 
   enum SingleHue : short
   {
      None  = 0,
      Black = 1 << 0,
      Red   = 1 << 1,
      Green = 1 << 2,
      Blue  = 1 << 3
   };

Also today:

   [FlagsAttribute] 
   enum SingleHue : short
   {
      None  = 0b0000,
      Black = 0b0001,
      Red   = 0b0010,
      Green = 0b0100,
      Blue  = 0b1000
   };

@svick - Yes I know, I was waiting for someone to point that out!

from csharplang.

john-h-k avatar john-h-k commented on August 15, 2024

should it be error in the compile time or throwing integer overflow at the runtime. Or just silently become int.MaxValue ?

It should mirror the current behaviour, and error with "compile time overflow" which can be overridden with the unchecked syntax

from csharplang.

Thaina avatar Thaina commented on August 15, 2024

To be honest I don't like ** operator personally. But this is just totally personal. I can bear with it because some language already have it so it not unfamiliar

But because of reason above. I am more fond of #2379 and if I have to choose 1 I will choose to use Math.Pow instead

But I won't go against this proposal and it would be useful to have if we can't use const Math.Pow

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

@Thaina - Yes but bear in mind

To be honest I don't like ** operator personally. But this is just totally personal. I can bear with it because some language already have it so it not unfamiliar

But because of reason above. I am more fond of #2379 and if I have to choose 1 I will choose to use Math.Pow instead

But I won't go against this proposal and it would be useful to have if we can't use const Math.Pow

@Thaina - Yes but bear in mind that #2379 itself relies on a - as yet - unavailable capability to tag code (either manually (dangerous) or automatically), as having referential integrity.

Unless the compiler "knows" that some static method has referential integrity #2379 cannot be delivered. I'm far from knowledgeable about how C# might do this but it strikes me as non-trivial. I don't even think F# can do #2379.

In addition I am now aware that the existing Math.Pow() has no overloads and relies entirely on floating point arithmetic and can be (it seems) inconsistent across platforms.

Expressions of the form: int ** -int generate fractional results but these results are always rational numbers. This means that 10 ** -1 involves only integers and generates a result that can be expressed wholly as an integer fraction.

However in the case of Math.Pow() this result (0.1) is always represented as a float/double and 0.1 cannot be represented finitely as a binary fraction, so even with integer values Math.Pow() has scope for errors.

Of course int ** -int cannot have an integer result but it could (if we wanted it) express it's result as decimal.

So we could include this and have ** basically be:

public decimal (Int64, SByte)

The decimal type can (I think) represent any rational fraction possible when raising an Int64 to any negative value of an SByte without loss.

from csharplang.

gafter avatar gafter commented on August 15, 2024

The decimal type can (I think) represent any rational fraction possible when raising an Int64 to any negative value of an SByte without loss.

No, 3 ** -1 is not exactly representable as a decimal.

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

The decimal type can (I think) represent any rational fraction possible when raising an Int64 to any negative value of an SByte without loss.

No, 3 ** -1 is not exactly representable as a decimal.

Yes you're quite right, I was confusing rational with finite expansion. So int ** -int could be removed, scope could be confined to Int64 ** Byte with a Int64 result...

from csharplang.

tompazourek avatar tompazourek commented on August 15, 2024

In my mind, the Pow function is a little bit more complicated than the standard +, -, *, and /.

To me, it feels more like a sibling to functions like Sqrt and Log. There are all these cases with negative exponents, fractional exponents, large exponents, etc. I personally just consider exponentiation to be a complex mathematical function, not your everyday operator.

When I was implementing this functionality for rational numbers (for my library Rationals), I wrote it as a static function Rational.Pow with an integer exponent and it didn't feel to me like I am missing an operator.

It's just my personal opinion though.

from csharplang.

Thaina avatar Thaina commented on August 15, 2024

Now your proposal is too limited for the sake of pow operator. In the real case I think when we require intensive math operation to the point that we need ** it's more likely that we are dealing with mostly real number, not integer

And because we introduce ** while there were already many language have these same operator. I think people would expect it to have as similar behaviour as all other language, such as F#

Also for the sake of same pattern to other arithmetic operator. Maybe I was thinking too much. It should just be the same as * that, it will just upcast to the largest input number involve to be it's return type, and so the programmer has to choose the type of number by themselves when use **

Just so that 10 ** 10 is overflow exception and might just error in runtime. But 10l ** 10 or 10 ** 10l just return long and 10 ** 10f just return float

10 ** -1 just being 0 and 10 ** -1f just being 0.1f

from csharplang.

spydacarnage avatar spydacarnage commented on August 15, 2024

The obvious (although probably very difficult) option would be to allow the definition of custom operators, as in F#... Then, if you want to define the following, you could:

public static long operator ** (int value1, int value2) => Math.Pow(value1, value2);

And then you could deal with the ifs and buts and maybes that crop up in your code...

But we can dream, right?

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

@tompazourek - That's a very interesting library, you'll certainly have deep insights into this are.

@Thaina - Yes it is limited but then so is the existing Math.Pow (limited to double arguments and return type).

Recall that I'm particularly interested in being able to define constants via expressions of type:

public const long SCALE = 10 ** 3;
public const long MAPPER = 2 ** 6;

and so on. This is often very expressive when there are patters in the values and make it easier to read some kinds of code.

This is not possible today but the prospect of introducing some support for ** brings this within reach.

Having said that it should also be possible to leverage a library like @tompazourek has created. That could be used at runtime and compile time because it is a static method designed to have referential integrity.

Unfortunately it seems unlikely we'll see the ability to have the compiler routinely use static functions that have referential integrity because the necessary basis just isn't there.

Besides as explained by others above exponentiation today is platform specific in the sense that Math.Pow relies in the C runtime library and the compiler can't do that because the C runtime the compiler might use could differ from the C runtime that the code itself may later run against.

I suppose that dotnet/roslyn#7561 could be reopened and given a restricted scope too, for example it could be implemented so that a method is pure if and only if it refers only to it's arguments and other constants or pure methods.

So any method that never refers to external or internal state could be tagged as "pure".

Actually the current issue for "pure" is here.

from csharplang.

dharmatech avatar dharmatech commented on August 15, 2024

Replying to @tompazourek

To me, it feels more like a sibling to functions like Sqrt and Log.

When a full numeric tower is available, I would consider Sqrt to be a special case of exponentiation, not so much a sibling.

There are all these cases with negative exponents, fractional exponents, large exponents, etc.

My library Symbolism also implements arbitrary precision rational numbers (uses BigInteger underneath).

Loading up Symbolism into C# interactive:

> #r "Symbolism.dll"
> using Symbolism;

Rational raised to integer power:

> (Integer) 1 / 2 ^ 2
[1/4]

(Note that ^ is overloaded in Symbolism to indicate exponentiation. The precedence of ^ is low so parenthesis are not needed in that example.)

Integer raised to fractional power:

> (4 ^ (Integer) 1 / 3)
[4 ^ 1/3]

Large exponent:

> ((Integer) 2 ^ 500)
[3273390607896141870013189696827599152216642046043064789483291368096133796404674554883270092325904157150886684127560071009217256545885393053328527589376]

When a full numeric tower is available, the special cases that you mentioned make sense and have straightforward implementations.

from csharplang.

tompazourek avatar tompazourek commented on August 15, 2024

@dharmatech Can you explain what you'd consider a full numeric tower? Maybe some links?

from csharplang.

dharmatech avatar dharmatech commented on August 15, 2024

@tompazourek

Can you explain what you'd consider a full numeric tower? Maybe some links?

Another example of a math library providing a numeric tower; the ginac computer algebra library for C++ uses the CLN library which provides their their numeric tower:

                        Number
                      cl_number
                    <cln/number.h>
                          |
                          |
                 Real or complex number
                        cl_N
                    <cln/complex.h>
                          |
                          |
                     Real number
                        cl_R
                     <cln/real.h>
                          |
      +-------------------+-------------------+
      |                                       |
Rational number                     Floating-point number
    cl_RA                                   cl_F
<cln/rational.h>                         <cln/float.h>
      |                                       |
      |                +--------------+--------------+--------------+
   Integer             |              |              |              |
    cl_I          Short-Float    Single-Float   Double-Float    Long-Float
<cln/integer.h>      cl_SF          cl_FF          cl_DF          cl_LF
                 <cln/sfloat.h> <cln/ffloat.h> <cln/dfloat.h> <cln/lfloat.h>

from csharplang.

333fred avatar 333fred commented on August 15, 2024

If a community member wants to contribute here, we need a proposal that specifies the exact symbol and precedence for the operator. As an example, something like https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/null-coalescing-assignment.md that details the changes necessary to support the new operator.

from csharplang.

Happypig375 avatar Happypig375 commented on August 15, 2024

Regarding precedence,
image
I think aligning with VB and reusing the operator precedence and code generation would be desirable here.

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

Just noting here that neither ^^ and *^ can be used, much like *, due to the existence of ^ as both a binary and a preceding unary operator (indexing from the end). This means we have to either consider a completely unrelated symbol to denote exponentation, or use a 3-symbol operator like:

  • **^
  • ^**
  • *^*

None of those can cause syntactic ambiguity yet, but ^** could prohibit usage of the ^ operator after an expression for whatever purpose it could be reserved for, causing the ambiguity between (a^) * (*b) and (a) (^**) (b) (parentheses used for grouping; actual syntax would be invalid).

Speaking of the above, parenthesizing the operators for grouping is another valid solution, which would allow for extending the ** operator to supporting higher hyperoperators by concatenating *s together (*** for power towers, **** for power tower towers, etc.). In this case, exponentation would be denoted as a (**) b, resolving any syntactic ambiguities.

@alfasgd

I wonder why no separator was required for stuff like this:

int data = 3;

var p = &data;
var x = &p;
var r = 9***x;

That flexible syntax, though nice and terse, makes it impossible to ever create a new tokens like ** or ***, because a sequence of unseparated * is always regarded as a succession of single * tokens, deferences. The above is treated identically to:

int data = 3;

var p = &data;
var x = &p;
var r = 9 * * * x;

Contrast that with this:

var five = 5;

number = + + five;
number = ++ five;

Here + and ++ are different, the latter is not treated as + + but in the case of dereferencing ** is treated the same as * *.

We could create a new token +++ but not ***.

This can never change of course, this is deeply entrenched in the language now but might be something to learn from.

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024

This can never change of course, this is deeply entrenched in the language now but might be something to learn from.

It definitely could change. We have the flexibility to change things. Where it would be matter would be if there was code that could legally mean both. In this case, that would not be true. As 9 could not be raised to the power of a pointer, it would be fine to make this work.

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024

I wonder why no separator was required for stuff like this:

Because it would not be a problem to solve this in the future. In general we only require whitespace based on general 'max consumption' rules normally used in lexical analysis. In this case, there's no concern about * being consumed into anythign else. So we dont' need anything else to go between the tokens. Requiring whitespace would be onerous.

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

I wonder why no separator was required for stuff like this:

Because it would not be a problem to solve this in the future. In general we only require whitespace based on general 'max consumption' rules normally used in lexical analysis. In this case, there's no concern about * being consumed into anythign else. So we dont' need anything else to go between the tokens. Requiring whitespace would be onerous.

Well clearly whitespace with + was not considered onerous and + + is distinct from ++.

Besides typically a tokenizer can tokenize based purely on character sequences, here that seems to not be the case.

number = 9 ** X;

So here, would the lexical analyzer return a single ** token or two * tokens? It can't decide it seems, not without some additional information normally not available until the parsing stage.

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024

Well clearly whitespace with + was not considered onerous and + + is distinct from ++.

Because we shipped with a ++ operator. If we didn't, it would have worked the same way. There's been no need to do the same with ** because taht hasn't shipped. If it did, we'd address appropriately. We have precedence for thsi (no pun intended) as we've already done it in teh past.

from csharplang.

tannergooding avatar tannergooding commented on August 15, 2024

Without disambiguation on + and ++, then x++y could be interpreted as Add(x, UnaryPlus(y)) (x + (+y)) or possibly several other patterns.

* I think is interesting because it also means the dereference operation and so the following is already valid code:

public unsafe int M(int x, int* y) {
    return x**y; // same as x ** y; x** y; x **y; x * * y; etc
}

It is interpreted as Multiply(x, Dereference(y)) (x * (*y)). It would, at least to me, be slightly problematic for Int32 ** Int32 to mean Exponent(x, y) but Int32 ** Pointer<Int32> to mean Multiply(x, Dereference(y)) (x * (*y))

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024

I don't see ** or *** being interesting. We would just make it a new operator. And that new operator would mean the following: If i have pointers on the right, then deref (as raising to the power of a pointer doesn't seem sensible). Otherwise, lookup exponentiation operator.

Alternatively, we could always just lookup exponentiation, and if we find something viable, use it. If not, fall back to prior logic. either approach seems like it be fine. I honeslty doubt any change in behavior would ever occur. And, if we were worried about that, or found a case where it would, we could just have it use existing semantics if possible, and new semantics if not.

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024

Besides typically a tokenizer can tokenize based purely on character sequences, here that seems to not be the case.

Our tokenizer and parser are implementation details. They are a means to an end. They do not normally force the language's hand. The language is defined however it wants, and then the impl does what is necessary to support that.

In this case, we can simply define that we have this operator (and optionally rules for if there's any sort of potential ambiguity). We already have this well understood as we had to do much more involved stuff for things like 'generics'. that was much more involved impl-wise, but wasn't really a big deal either. This lang feature would be much more trivial to handle.

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024

More in general (and I believe I've commented on similar proposal before) I'm still not convinced of the overall value here:

I agree with this. i think it's solvable, but i also think this is not really something i think is worth investing into.

from csharplang.

Korporal avatar Korporal commented on August 15, 2024

The only thoughts I had here was that it would be neat to be able to use exponentiation when initializing constants. Often powers of 10 and powers of 2 are needed and being able to express that as an exponentiation would be neat. One can't use a function call to to init const variables.

However since Math.Pow(<numeric-constant>, <numeric-constant>) is itself constant perhaps there's a case for having the compiler evaluate the method call at runtime and embed the result into the code? That's common optimization I've seen in some languages/compilers.

private const double Rate = Math.Pow(2, 6); // always 64 no matter when it's evaluated

today we get a compile diagnostic, an error. Perhaps several Math functions could be treated that way, Sqrt, Sin and many more could be evaluated during compile time.

I guess there are cases where despite the args being constants the compiler can't know or assume the result is also always constant.

So this in turn might need an attribute [FoldableResult] on the method, then the compiler could rely on that to tell it it's safe to do the constant substitution, if that attribute's present and the args are all compile time constants, then it's safe to optimize it away.

C# 11.0 perhaps?...

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024

#1028

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on August 15, 2024

Part of what makes C# C# is that it is a generalist language. It doesn't strive to be the best particular language in all domains. Math-focused is one domain, of many, that it can play in. However, it doesn't strive to be the ideal math language out there. This is primarily because there's genuinely no end in terms of what would be needed in order to play well there. Yes, Python (no need to avoid the name) may be preferred by some in this space. However, python itself is just one of many popular languages here (like Matlab, R, Wolfram, Idris, Julia, etc.). Attempting to ape the feature sets of those that make them desirable is not only a significant investment, but also a significant departure of what C# strives to be.

Put another way. Adding exponentiation isn't going to be the feature that substantively moves the dial here (IMO). So we should either commit to 'math oriented' being a goal of hte language (with a broad look at the future direction and what that would mean), or we should take it as what it is, and not just add a one-off feature here and there because it's an annoyance to a specific group.

from csharplang.

dharmatech avatar dharmatech commented on August 15, 2024

After reading some of the devs chime in regarding the implementation challenges involved I do have to say that I'm quite sympathetic to their points of view. It definitely should only be undertaken if it's seen as worth the added implementation complexity and overall effort.

I just wanted to get the feature on the radar for discussion and I do appreciate the time everyone has spent so far digging into the subtleties involved. (I've learned a lot from reading the comments.)

from csharplang.

dharmatech avatar dharmatech commented on August 15, 2024

why not collect a list of potential improvements to make C# more math-friendly, be it an operator here, or some construct there, or on the BCL side, some math functionality in classes, etc.; and then prioritize those and tackle 1 or 2 every version.

Regarding other math oriented features on the BCL side, it'd be nice to see a rational number library there someday.

from csharplang.

TahirAhmadov avatar TahirAhmadov commented on August 15, 2024

There isn't consensus on this, and you'll have the spectrum of people going:
...

This probably applies to any C# feature set, and I think that's normal to have this spectrum of opinions. I agree that we shouldn't listen to '3' all the time.

So, IMO, we already do this. It's just that that genuinely may mean that a particular feature (like this one) just may not be relevant. It might be, for example, that we extend things like operators to work on tuples/arrays/etc. as that may be more pressing and much more painful to not have.

So even if we go this route, I would say that you should not expect that that would mean that exponentiation would ever meet the bar as needing direct lang syntax.

As listed above, it certainly looks like progress has been made, at least on the .NET side - and I apologize for not being fully aware of the extent of the work in this area - as I said, I don't tackle math projects almost at all.
Having said that, given how many other languages have an operator for exponents, and that it's a very common notation in mathematical equations/formulas, it does seem like a glaring omission.
Regarding the issues with ** conflicting with pointers, we could go with ^^ instead; it sidesteps a lot of issues and makes it a straightforward addition.
Regarding constants, (playing devil's advocate here) something like "deterministic functions" (for which there is a proposal somewhere) could solve a lot of those challenges, not just exponents.

from csharplang.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.