dotnet / csharplang

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

Proposal: New operator %% for positive-result Modulus operations #1408

Closed aaronfranke closed 3 years ago

aaronfranke commented 6 years ago

Note: The title is slightly incorrect because it's hard to describe the behavior completely in a short title. The idea is for a %% b to be on the range [0, b), so a %% -3 would be on the range [0, -3). As usual, the pattern holds that as a increases, so does the output. However, by far the most common case is for b to be positive, and in that case the title is correct.

Currently, C# has the operator % for the Remainder operation. This is different from the canonical Modulus when it comes to negative numbers. For example, -5 % 8 is -5 with the Remainder operation but it is 3 with the Modulus operation (proposed syntax: -5 %% 8 returns 3).

Currently, I implement Modulus on top of Remainder in my program like this:

public static float Mod(float a, float b)
{
    float c = a % b;
    if ((c < 0 && b > 0) || (c > 0 && b < 0)) {
        c += b;
    }
    return c;
}

A new operator, %%, would serve the following purposes:

Having only the wrong operator in the language means that the people will tend to choose it over some cryptic library function for their implementation, and will have bugs in their code. Having both operators will make people aware of the problem. So the advantage in bringing the alternate operator to the language is to promote better coding without sacrificing the C legacy.

A few use cases from the replies below:

CyrusNajmabadi commented 4 years ago

Why did that behavior become the thing people would now want to be compatible with?

Because there wasn't a compelling reason for us to go another path when designing the language. :) If we had a time machine, maybe someone could go back and make that argument 23 years ago.

That said, even if someone had made that argument, it's likely compatibility would still have won out. Just not enough of a reason to go against the ecosystem as a whole here.

kshetline commented 4 years ago

@CyrusNajmabadi, I'm talking more broadly than C#. I'm asking how, before C# even existed, the ball got rolling in the first place so that the current % behavior (which @aaronfranke and I consider broken) got so deeply established. (Given that this was well established before C# existed, I wouldn't at all have expected C# to have "gone rogue".)

I'm still curious if I'm wrong to call this behavior "broken". Is there is a non-compatibility-related use case where a negative result for a negative first operand and positive second operand is just perfect for some particular application, and I'd be oh-so-happy it works that way?

At any rate, back to the specific proposal: If I'm correct to consider the proposed %% behavior more mathematically useful than the existing %, it makes sense for the more useful behavior to rate the status of having its own operand, rather than being relegated to a user-defined or library function.

kshetline commented 4 years ago

@aaronfranke, would you agree that, rather than producing an always-positive (or zero) result, that the proposed %% should follow the sign of the divisor, such that 5 %% -3 would yield -1?

CyrusNajmabadi commented 4 years ago

I imagine any domain where a remainder is wanted.

kshetline commented 4 years ago

"any domain where a remainder is wanted"... which is to say, "it's useful when what it does is what you want it to do" 😄

CyrusNajmabadi commented 4 years ago

I mean... it makes sense to me. if we have integral division, you have two complimentary operations that makes sense together. i.e. (a / b) * b + a % b = a. Seems reasonable to have this to me.

If you want an actual modulus operator, it seems like having a simple method that does that would suffice.

kshetline commented 4 years ago

It would "suffice", but it still makes the modulus operation a second-class citizen. A "simple method" would also "suffice" for what % currently does now too, but it nevertheless gets an operator.

(a / b) * b + a % b == a comes out as it does because, as I see it, default integer division is just as broken as % in its legacy inherited behavior.

CyrusNajmabadi commented 4 years ago

It would "suffice", but it still makes the modulus operation a second-class citizen.

Only if we consider all APIs second class.

It's not hte goal of the language to obviate the need for apis, or to absorb all of them into the language itself.

A "simple method" would also "suffice" for what % currently does now too,

Yes. But see the above points about time machines and relitigating the past.

default integer division is just as broken

I honestly don't get what you want at this point. The language is what it is. it exists in an ecosystem of languages defined by behaviors created decades ago. There is little need or interest or benefit in changing any of that.

aaronfranke commented 4 years ago

would you agree that, rather than producing an always-positive (or zero) result, that the proposed %% should follow the sign of the divisor, such that 5 %% -3 would yield -1?

Yes, the idea is for a %% b to be on the range [0, b), so a %% -3 would be on the range [0, -3). As usual, the pattern holds that as a increases, so does the output.

Input a               -5  -4  -3  -2  -1   0   1   2   3   4   5
Output for b = 3       1   2   0   1   2   0   1   2   0   1   2
Output for b = -3     -2  -1   0  -2  -1   0  -2  -1   0  -2  -1

This is what the code in the OP does.

However, the most common use case is for b to be positive.

default integer division is just as broken

The argument for floor division is IMO a separate argument. Python has // for this, but C# already uses those characters for comments, so we would need other characters or a method.

kshetline commented 4 years ago

"I honestly don't get what you want at this point. The language is what it is."

I want what @aaronfranke wants. This doesn't change what the C# language is any more than any other proposal, and this is hardly a radical or non-backward compatible proposal. Neither of us are asking that % change, just for the addition of %% to provide the much more mathematically useful modulus operation.

You seem baffled that anyone would want a new operator, as if the sole value of existing operators is their history of usage in other languages, and nothing else.

If there was no + operator for addition, would you be just fine with Math.Add(x, y) instead? Why did any language ever include operators in addition to functions in the first place, if functions are so great, and operators are just frivolous and unnecessary syntactical sugar?

Do you imagine that + and - and * and \ are in C# only as a concession to established history, and without that history everyone could and should be happy with, say:

(1) Math.Div(Math.Add(Math.Negate(b), Math.Sqrt(Math.Sub(Math.Pow(b, 2), Math.Mult(4, Math.Mult(a, c))))), Math.Mult(2, a))

...instead of:

(2) (-b + Math.Sqrt(Math.Pow(b, 2) - 4 * a * c) / (2 * a)?

If you see (2) as an improvement over (1), and the proposed change introduces no backwards compatibility problems whatsoever, why do you treat the idea of an operator for modulus as totally baffling? It's the same kind of improvement, just for a less-commonly used operation.

(And for that matter, C# could definitely use the ** operator as well, already in many other languages, for exponentiation: (-b + Math.Sqrt(b ** 2 - 4 * a * c) / (2 * a))

CyrusNajmabadi commented 4 years ago

You seem baffled that anyone would want a new operator,

Why do you think that?

if functions are so great, and operators are just frivolous and unnecessary syntactical sugar?

This is a strawman. I didn't say operators were frivolous. I said that i don't see the value in there being a dedicated operator for this.

why do you treat the idea of an operator for modulus as totally baffling?

I never said i was baffling. Maybe you're thinking of someone else?

And for that matter, C# could definitely use the ** operator as well, already in many other languages

Feel free to file a proposal on it. I don't really see hte value being there myself, but maybe someone on the ldm will.

--

Look, i'm sorry that you're not finding much sympathy from me on this particular issue. I know that every issue that people file on csharplang is important to some group of people. However, it's not going to be the cae that every issue is important ot everyone, or that every issue will find a supporter on the LDM. In this case, i've simply shared with you my thoughts here and why I would not champion this myself. I would have no problem with some other LDM member doing so, but i at least wanted to let you know the thinking that goes into it.

Knobibrot commented 3 years ago

I wholeheartedly agree with the OP. Furthermore, I find it unfortunately that % means remainder. % canonically means modulus and this leads to confusion. Just like you shouldn't overload the * or + operators with operations that don't map to addition or multiplication, % should map to a modulus operation. But since this can't be changed anymore, adding a %% (or similar) operator for modulus is the best course of action in my opinion.

Edit: I was wrong. I still agree that there should be a modulo operator, though.

quinmars commented 3 years ago

I find it unfortunately that % means remainder. % canonically means modulus and this leads to confusion.

Actually, % used to mean "divide by 100". Many programming languages use it, however, for the remainder operation and very few for modulus.

aaronfranke commented 3 years ago

@quinmars % is not ÷ EDIT: Sorry, completely missed the "by 100" part.

quinmars commented 3 years ago

@aaronfranke, correct. % is the "per cent" sign.

JansthcirlU commented 3 years ago

I think @aaronfranke and @kshetline have both articulated the issues with the existing implementation of % very clearly. In this very thread, I pretty much only see people commenting use cases where the proposed %% would be superior to %, but I yet have to see a single concrete use case where the inferior implementation of % is remotely better than the proposed %%.

%% does what % does, and more

As Eric Lippert explains in his blog post, both % and the canonical modulo as he calls it serve the same mathematical purpose, which is to filter integers into equivalence classes that satisfy divisibility by some non-zero number. And they both accomplish this goal, as you will find that both schools of thought yield the same equivalence classes.

However, while you can use both % and %% to the same end, the result produced by % is utterly useless half the time but the result produced by %% would never be useless. Again, plenty of people are presenting use cases where %% would save them a lot of trouble, but I yet have to see anyone provide a use case where a negative remainder is more useful than a positive remainder. It wouldn't make a difference to devs who never work with negative numbers, but it would save a lot of trouble for anyone who does.

%% is more intuitive than %

On another note, I personally disagree with one of Eric Lippert's comments in his blog. He argues that the magnitude of A / B should be the same as the magnitude of -A / B because it would be "bizarre" otherwise, in other words the integral part of a division should be symmetric around 0. But there's no real justification for why integer division should be symmetric around 0, it's perfectly okay if it isn't.

Although defining the remainder to be positive, as is the case for Euclidean division, is just as arbitrary a choice as making integer division symmetric around 0, the implications are different. And I would argue that the constraints proposed for Euclidean division are actually more intuitive and realistic than the ones described by Eric, even when using negative numbers.

The constraints for Euclidean division when dividing some integer a by some non-zero integer b:

Let's say I own 257$ and I want my money in 10$ bank notes. I could go to an ATM and withdraw 10$ at a time until I can't withdraw 10$ anymore (or until I'm out of money). For a total of 257$, I will be able to withdraw 25 notes until the ATM tells me I can't withdraw another 10$ note because I only have 7$ left. Therefore, q = 25 and r = 7$.

What about negative numbers? Let's say I owe you 257$ and I pay you back using 10$ bank notes. I could give you 25 notes, but I wouldn't have paid off my debt. Instead, I would have to give you 26 notes and you would pay me back the 3$ that I didn't owe you. Therefore, q = -26 and r = 3$.

Positive remainders all the way.

Final thoughts

The proposed %% is superior to % in every conceivable way, so it should have a proper operator. It would just be a huge game changer for anyone who works with numbers on a regular basis. I also understand that the archaic % cannot and will not be removed, but clearly that's not what this thread is about. It's about adding an actually useful operator to the language, not about changing an existing one.

jnm2 commented 3 years ago

Moving to a discussion which is the current path for new ideas.