Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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: New operator %% for positive-result Modulus operations #1408

Closed
aaronfranke opened this issue Mar 22, 2018 · 67 comments
Closed

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

aaronfranke opened this issue Mar 22, 2018 · 67 comments

Comments

@aaronfranke
Copy link

aaronfranke commented Mar 22, 2018

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

  • Modulus always returns between 0 (inclusive) and the second argument (exclusive). Yes, this means that it should keep the sign of the second argument. a %% 8 is on range [0, 8)

  • By contrast, Remainder can return between the negative second argument (exclusive) and the second argument (again, exclusive). a % 8 is on range (-8, 8)

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:

  • Easily allow the writing of true canonical Modulus operations, rather than Remainder operations. I can imagine more use cases for this.

  • As @quinmars noted, the compiler itself performs optimizations to change % to bitwise when it is passed uint types. Therefore, the compiler could also improve the performance of the %% true Modulus operation, when given int types rather than uint (int is much more common than uint), and the second argument is a power-of-two, by making it bitwise.

  • Make developers aware of the issue and endorse the correct usage. To quote @vladd:

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:

  • Indexes in arrays, tables, bucket hash tables, circular buffers, and ring buffers.

  • Finding sub-positions in a grid coordinate system.

  • Working with angles.

  • Working with dates and times.

@TheUnlocked
Copy link

TheUnlocked commented Mar 22, 2018

I like the idea of a true modulo operator. I'm not sure %% is the right operator for this task (the doubled remainder symbol could imply that it's a logical operator, like with & and && or | and ||), but some operator should be made to do this.

@Joe4evr
Copy link
Contributor

Joe4evr commented Mar 23, 2018

The title of this proposal doesn't make any sense. The entire point of the modulo operation is to get the remainder that is produced from long-dividing two numbers, so there is no such thing as "true modulo" that isn't obtaining that remainder.

This is different from the Modulus when it comes to negative numbers.

According to Wikipedia, there is no rigid mathematical specification of how modulo should work with negative numbers.

When either a or n is negative, the naive definition breaks down and programming languages differ in how these values are defined.

@aaronfranke
Copy link
Author

Let me be specific then: I want a Modulus operator which returns between 0 (incl) and divisor (excl).

Also, Microsoft's own definition of the % is "Remainder" not "Modulus" in their C# docs.

@Joe4evr
Copy link
Contributor

Joe4evr commented Mar 23, 2018

I want a Modulus operator which returns between 0 (incl) and divisor (excl).

Then call Math.Abs() on the result. If you look at the table on the Wikipedia article, the result in C# is defined as having the same sign as the Dividend (left-hand argument).

@mikedn
Copy link

mikedn commented Mar 23, 2018

I want a Modulus operator which returns between 0 (incl) and divisor (excl).

And you want this operation for what exactly? What is this is the mathematical meaning of this operation? What code is a %% b supposed to generate?

@aaronfranke
Copy link
Author

aaronfranke commented Mar 23, 2018

@Joe4evr That does not do what I want, at all. The output value should loop around the bounds. -1 % 5 is 4 with a true Modulus, -1 with Remainder, and 1 with Remainder + Math.Abs().

As I've already stated the work-around is to add the second value if the result is negative. But this is not trivial (well, not as trivial as an operator), and there are compiler optimizations only possible with a true Modulus operation.

@aaronfranke
Copy link
Author

aaronfranke commented Mar 23, 2018

Example use case: Let's say you divide a coordinate system into pieces of 100. If you are at position 5, you are at subposition 5 of piece 0. If you are at 230, you are at subposition 30 of piece 2. If you are at position -20, you are at subposition 80 of piece -1.

The reason you're not at subposition -20 or 20 is because the subpositions should always be positive AND increasing when position is increasing. If we were at subposition -20 of piece 0 then piece 0 would be two times as large as any other piece.

I am sure there are many, many more uses for Modulus, but this is what I have in mind.

@mikedn
Copy link

mikedn commented Mar 23, 2018

I am sure there are many, many more uses for Modulus, but this is what I have in mind.

Are there really so many uses do justify adding a new operator? What's wrong with adding a Mod method to System.Math?

@aaronfranke
Copy link
Author

aaronfranke commented Mar 23, 2018

Of course that's up for debate, but I would argue that a new operator is a good idea.

I would also support adding a Mod method to System.Math.

@aaronfranke aaronfranke changed the title Proposal: New operator %% for true Modulus operations (non-remainder) Proposal: New operator %% for positive-result Modulus operations Mar 23, 2018
@aaronfranke
Copy link
Author

aaronfranke commented Mar 23, 2018

I changed the title to better reflect the issue, but technically it's wrong.

  • If the divisor (2nd arg) is positive, the result of my definition of "true Modulus" should be non-negative.

  • If the divisor (2nd arg) is negative, the result of my definition of "true Modulus" should be non-positive (as it should loop the other way around).

I couldn't fit all this into a short title, but hopefully it gets the point across.

@mikedn
Copy link

mikedn commented Mar 23, 2018

A programmer may write a % b but someone reviewing the code may not be sure if they wanted a Modulus and never tested negative numbers, or if they did indeed want a Remainder. A method such as Math.Rem would be unambiguous. So too would be Math.Mod and%% (hard to write two % by accident)

It probably makes more sense to do what Java does - add FloorDiv and FloorRem to System.Math, especially if you consider your own example with the coordinate system.

@jnm2
Copy link
Contributor

jnm2 commented Mar 23, 2018

@aaronfranke I'm afraid the terminology you're using doesn't seem to be ideal. If people search the term ‘modulus,’ they'll discover that it's a synonym for ‘absolute value.’
Since we're talking about modular arithmetic, the term I've always heard is ‘modulo.’ It seems like the less ambiguous term would be better. (FloorRem is also quite a communicative name.)

Also, specifically, you're looking for the Euclidean variant of the modulo operation. (Or possibly the floored division variant? Can't tell.) C# already has the truncated division variant.

@Joe4evr
Copy link
Contributor

Joe4evr commented Mar 23, 2018

It turns out Eric Lippert blogged about this exact thing back in 2011.

@aaronfranke
Copy link
Author

aaronfranke commented Mar 23, 2018

@jnm2 I want what Eric Lippert calls "Canonical Modulus". I don't care if Modulus has conflicting definitions in the mathematical world (nobody in programming calls absolute value as "modulus"). I just want a math operator.

@CyrusNajmabadi
Copy link
Member

I don't care if Modulus has conflicting definitions in the mathematical world. I just want a math operator.

Can you not just provide that operator in a library? Why does hte language need first class support for it?

@aaronfranke
Copy link
Author

I already have a method for implementing it, but I think it should be in the language because it's very useful and I am not the only one who would like having this functionality.

@theunrepentantgeek
Copy link

I think it should be in the language because it's very useful and I am not the only one who would like having this functionality.

Anecdotally, I'm sure you believe this to be true. My own experience tells me the opposite.

Without hard data from a significant percentage of the C# userbase, neither of us can be sure.

@aaronfranke
Copy link
Author

@TheUnlocked

I'm not sure %% is the right operator for this task (the doubled remainder symbol could imply that it's a logical operator, like with & and && or | and ||)

There are already other doubled operators which are not boolean logic, such as ++.

@aaronfranke
Copy link
Author

aaronfranke commented Mar 25, 2018

@theunrepentantgeek Here is some data from some users: https://stackoverflow.com/questions/10065080/mod-explanation People are confused when they expect positive-result (really sign-of-divisor) answers and get negative answers in C#, and compare to languages like Python where positive-only (really sign-of-divisor) is the standard.

Judging by "Viewed 68k times", and the amount of upvotes (remember that not every viewer of the question would upvote it), I would estimate that this is a fairly common problem. Providing an operator for canonical Modulus in C# would help these users out.

@CyrusNajmabadi
Copy link
Member

Providing an operator for canonical Modulus in C# would help these users out.

Having the functionality be available would certainly be useful. That does not mean having the operator is necessary.

For example, there are times i need to do an unsigned right shift. But do i need it to be an operator (like how java has it)? Nope. I just switch to unsigned math, do the right shift, then switch back. Unsigned shifts are still useful. But i can live without it being an operator.

The same is true for this sort of mod/remainder for me. I can totally see the usefulness of it. I've even needed it at times. But that doesn't make me think i needed an operator for it. I would be totally fine with this coming in through the BCL, or just some numerics package.

@aaronfranke
Copy link
Author

I would still like an operator, but, I agree with you @CyrusNajmabadi

@Richiban
Copy link

I remember in my early days of programming being really confused as to why my code wasn't working... and it's because everyone calls % the "mod operator" (incorrectly so). My extension method I'd written for safe array access (i.e. indexes would wrap around)...

public static class ArrayExtensions
{
	public static T WrapIndex<T>(this T[] source, int index)
	    => source[index % source.Length];
}

...works just find for indexes greater than the size of the array but unfortunately doesn't work for negative indexes. To implement this function we need to detect whether the result came out negative and adjust it accordingly:

public static class ArrayExtensions
{
	public static T WrapIndex<T>(this T[] source, int index)
	{
		var wrapIndex = index % source.Length;
		
		if (wrapIndex < 0)
			wrapIndex += source.Length;
		
		return source[wrapIndex];
	}
}

So in my opinion there's definitely a need for this functionality. Whether it needs its own operator or not is up for debate. I'd say yes, but that's because 99% of the time I want mod not rem.


If anyone would like a visual representation of the difference between remainder and mod I've made a little diagram:

x        -10 -9 -8 -7 -6 -5 -4 -3 -2 -1  0  1  2  3  4  5  6  7  8  9 10
                          |              |              |
x % 5     -0 -4 -3 -2 -1  0 -4 -3 -2 -1  0  1  2  3  4  0  1  2  3  4  0
                          |              |              |
x mod 5    0  1  2  3  4  0  1  2  3  4  0  1  2  3  4  0  1  2  3  4  0

You can see that for positive numbers the two sequences are the same, but the differ for negative. As we pass through zero (approaching from the positive side) the mod numbers continue the same pattern through zero whereas the rem pattern turns negative and reflects through zero.

@aaronfranke
Copy link
Author

aaronfranke commented Mar 29, 2018

Here is another example of when Modulus is more useful than Remainder. Let's say you're writing a program that finds out which day of the week it is. You can accomplish this by taking the amount of days and performing a Modulus by 7 on the days. Then it will always return between 0 and 6, 7 total values, for each day of the week.

Unfortunately, this doesn't work with % if you try to perform math on days of the week before your initial reference day at zero. 5 days before the time when you started counting would be -5, but -5 % 7 would return -5. There is no negative-fifth day of the week, that's just ridiculous. In reality, that would be index 2 of the -1th week, so it should return 2. -5 %% 7 would fix this problem.

@mikedn
Copy link

mikedn commented Mar 29, 2018

Here is another example of when Modulus is more useful than Remainder

Examples can be found but the problem is that in reality dealing with negative numbers is less common so % works just fine in many cases. And the bigger problem with these examples is that they can be used to also show that the current / division is also not suitable. Your coordinate system example shows just that - -20 / 100 == 0 but in that example you'd need it to be -1. The day of week example can also be turned into a "how many weeks" example and then division would say -5 and 5 are both in the "current week" but you'd probably want -5 to be "last week" or something like that.

It's a bit baffling why everyone keeps taking about this modulus thing, I guess it's because many programmers have heard about modular arithmetic but less know about flooring and truncation.

Also many seems to be miss the fact that the current reminder is defined in relation to division - a % b == a - b * (a / b). While a %% b would be a - b * (a // b).

So what's next? Propose adding // as well? Adding a single new operator is not something that will happen easily, but two? That's not going to happen. Math methods on the other hand, sure why not. They're easy to implement and the names are pretty good at communicated the meaning so there's little chance of confusion (assuming, of course, that developers know what floor is).

@aaronfranke
Copy link
Author

I'm not asking floored-Division in this proposal (some languages like Python do have an operator for this: // in which -8//7 is -2) although this would also be nice to have too.

It's more common to need the modulus. You may want to know what day of the week or what sub-position etc but maybe not the sur-position. %% is more important than // IMO but both are nice.

Operators are nice, but new methods in System.Math are also useful. I would be satisfied with that.

@CyrusNajmabadi
Copy link
Member

You may want to know what day of the week or what sub-position etc but maybe not the sur-position.

that is not a compelling use case to need an operator for me :)

@vladd
Copy link

vladd commented Apr 2, 2018

Yet another place which would profit from having this functionality available:
https://referencesource.microsoft.com/#System.Core/System/Collections/Generic/HashSet.cs,1421

return m_comparer.GetHashCode(item) & Lower31BitMask;

Here the mask is needed only in order to workaround the remainder behavior on negative numbers.


I understand that the ship has already sailed, but from my experience remainder is almost exclusively used as modulus, so silent replacing of remainder with modulus would be not mentioned by the majority (modulo some workarounds for negative divisors, which would be just not necessary any more).

@mikedn
Copy link

mikedn commented Apr 2, 2018

Here the mask is needed only in order to workaround the remainder behavior on negative numbers.

And what problem would removing the & solve?

@CyrusNajmabadi
Copy link
Member

Yet another place which would profit from having this functionality available:

Why would you need an operator for this? Why would a library method not be sufficient?

@mikedn
Copy link

mikedn commented Apr 4, 2018

I'm not an x86 assembly guru, so I don't know if modulus is directly available in x86 instruction set.

As already stated in my previous post, there are no such instructions on any CPUs (well, at least on the ones that matter - x86/x64/arm32/arm64). x86/x64 CPUs have idiv that also returns the reminder. arm64 and some arm32 CPUs have a sdiv instruction that only returns the quotient. The JIT replaces a % b with a - b * (a / b). Some arm32 don't have even an sdiv instruction, in such cases the JIT calls helper functions for both / and %.

Absence of IL instruction is not big problem: if rem deserves its dedicated IL opcode, then so does mod.

Are you saying that mod should be added to IL? That's simply not going to happen.

But my point is that this operator would help writing more expressive and correct code. [And can bring performance gain if the platform supports efficient modulus, too.]

Well, I was simply replying to your claim that removing & would speed up things. It won't, because %% would generate extra instructions compared to %. If you're lucky, it ends up making no difference. But it's more likely that it would actually get slower.

@mikedn
Copy link

mikedn commented Apr 4, 2018

Another example use case: When computing time, there are no negative times

So if there are no negative times how come is this a use case?

@drdbkarron
Copy link

Calling it a modulus is perhaps incorrect; I just found I needed a wrap or rotation operator to operate
n n-dimensions. It should rotate + and - , the modulus fails on negative rotations because negative modulus is undefined. Rotation should never go negative and enable fractional rotations and fractional
rotations in fractional dimensions (will cause instability at some angles)

@sergiokoo
Copy link

I've been coding for 20+ years and I never needed the version of modulo operator which would return me the negative reminder. Grids, tables, array access, circular buffers, date wrappers - there are lots of use cases where you need a positive only reminder. I would be very surprised to see any practical use-case where negative reminder is needed. Thus, I would vote for %% operator or at least a corresponding method in Math.

@daarong
Copy link

daarong commented Apr 29, 2020

I landed here because I was among those that believed % was modulus, not remainder... started googling since it was "buggy".

Oops. In fact I'm rather shocked that I got away with using it as modulus for so long, I'm suddenly concerned about where I may have left bugs. I have been programming in C# since its inception... it's embarrassing that I've not noticed what % really is until today when it went wrong.

I am definitely behind %% because it's shorthand.

Since we're talking use cases, mine: on setup I was auto selecting an item. The index to select may or may not be 'valid', and rarely it may be 'invalid' aka -1. In this case I just wanted it bound to a valid value, so I mistakenly used % operator on the index with list length as divisor.

@kshetline
Copy link

What I don't understand is why the way % works with negative numbers, as it does in C# and many, many other languages, is so common. When negative numbers get involved, in ANYTHING I've ever done with time and date calculations, graphics, astronomy, etc. I have NEVER found the default behavior of % in C# (or C, or Java, or JavaScript) at all useful. It's always a nuisance that needs a work-around for me.

I'd love to hear of a real-life use case where this common % behavior with negative numbers is actually beneficial rather than merely tolerated, because I've never run into it.

My suspicion is that the annoying but more common sign handling for % grew out of something that was easier to implement in a CPU instruction set sometime long ago, and has been propagated ever since in the name of compatibility.

I'd throw my support behind this %% proposal too. (I just learned that Python gets % right, which inspired me to do a search on this subject.)

@CyrusNajmabadi
Copy link
Member

I'd love to hear of a real-life use case where this common % behavior with negative numbers is actually beneficial

Porting between languages. I don't have to think about how the behavior will change when moving between existing code in many languages and C#, they just behave teh same way.

@aaronfranke
Copy link
Author

@kshetline It all started with C, and most other languages copied this same behavior, including C++, C#, Java, JavaScript, GDScript, etc. It was a mistake made in the 1970s that will haunt us for centuries. A few languages have % behaving as the proposed %%, including Python and Ruby. https://en.wikipedia.org/wiki/Modulo_operation#In_programming_languages "Dividend" is the C# behavior, while "Divisor" is the proposed %% behavior (the title is slightly misleading).

@kshetline
Copy link

@CyrusNajmabadi, I already said "has been propagated ever since in the name of compatibility", so I recognized the portability argument. My question was clearly about what else besides compatibility does the C# implementation of % have going for it? Why did that behavior become the thing people would now want to be compatible with?

Without a good example of that kind of utility, I'm inclined to go along with what @arronfranke just said, that it's "mistake made in the 1970s that will haunt us for centuries".

@CyrusNajmabadi
Copy link
Member

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
Copy link

kshetline commented May 22, 2020

@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
Copy link

@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
Copy link
Member

I imagine any domain where a remainder is wanted.

@kshetline
Copy link

kshetline commented May 22, 2020

"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
Copy link
Member

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
Copy link

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
Copy link
Member

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
Copy link
Author

aaronfranke commented May 22, 2020

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
Copy link

kshetline commented May 22, 2020

"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
Copy link
Member

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
Copy link

Knobibrot commented Feb 23, 2021

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
Copy link

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
Copy link
Author

aaronfranke commented Feb 23, 2021

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

@quinmars
Copy link

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

@JansthcirlU
Copy link

JansthcirlU commented May 12, 2021

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:

  • The quotient q is an integer such that q × b <= a and a - q × b < b
  • The remainder r is the amount that q × b is short of a

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
Copy link
Contributor

jnm2 commented May 12, 2021

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

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests