Skip to content
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

Redefine MODULO so MOD is its infix form, Vary behavior by refinement #843

Closed
giuliolunati opened this issue Aug 7, 2018 · 15 comments
Closed
Assignees
Labels
type.wish a.k.a. new feature request
Milestone

Comments

@giuliolunati
Copy link

giuliolunati commented Aug 7, 2018

MOD -3 -2 --> -3
@hostilefork
Copy link
Member

hostilefork commented Oct 12, 2018

MOD -3 -2 --> -3

Complaint on that particular point is a duplicate of:

metaeducation/rebol-issues#1332

That ticket may provide some insight from Ladislav about why things are done the way they are. :-/

I don't have a grasp of the rationale behind mod, modulo, and remainder. It seems that Rebol2, R3-Alpha, and Red all have the same help, and says:

  • MOD: "Compute a nonnegative remainder of A divided by B." (so clearly, the description is wrong; Red also has the nonnegative statement and also says mod -3 -2 is -3)
  • MODULO: "Wrapper for MOD that handles errors like REMAINDER. Negligible values (compared to A and B) are rounded to zero."
  • REMAINDER: "Returns the remainder of first value divided by second."

I'm not savvy enough about the applications of these to know the difference. The only thing I would really like to have is:

mod: enfix tighten :modulo

If there is some subtlety in behavior between MODULO and MOD which might be interesting, I think that needs to be done with a refinement. Now that we have SHOVE, you could even say x -> mod/alteration y and use it infix.

It would be very helpful if you could reason through this and understand what exactly is going on, and what modulo means most usefully in programming languages by default. Then understand what this other property is. The comments might help:

; This function tries to find the remainder that is "almost non-negative"
; Example: mod (0.15 - 0.05 - 0.1) (0.1) is negative,
; but it is "almost" zero, i.e. "almost non-negative"

I tend to think it's a good idea to look at what languages used by serious mathematicians use, so Haskell is always good to research:

https://stackoverflow.com/questions/5891140/difference-between-mod-and-rem-in-haskell
http://www.prigrammer.com/?p=321

Sounds like it's up your alley! :-)

@hostilefork hostilefork changed the title MOD -3 -2 --> -3 Redefine MODULO so MOD is its infix form, Vary behavior by refinement Oct 12, 2018
@hostilefork hostilefork added the type.wish a.k.a. new feature request label Oct 12, 2018
@hostilefork hostilefork added this to the Beta/One milestone Oct 12, 2018
@giuliolunati
Copy link
Author

giuliolunati commented Oct 12, 2018

I'm decidedly for mod: enfix tighten modulo

About signs, some semi-random thoughts:

  1. I want abs ((x mod d) - (y mod d)) < abs d i.e. the range of the map x -> (x mod d) has to be minimal.

  2. that implies such a range can't be symmetrical around 0, so better to state it must be non-negative or non-positive

  3. So my proposal is: modulo x d has the same sign as d (while remainder x d has the same sign as x / d)

E.g:

(-3 mod 2) = 1

while

(3 mod -2) = -1

@hostilefork
Copy link
Member

hostilefork commented Oct 13, 2018

Here is some more reading, from Red issues:

red/red#2997 (discusses rounding modes, fmod)
red/red#2708 (regarding result type matching input type, e.g. at 0)
red/red#1515 (area to look at for bugs in extreme values)

Some of their tests for modulo can be found with this search:

https://github.com/red/red/search?q=modulo&unscoped_q=modulo

I'm decidedly for mod: enfix tighten :modulo

This is the main thing I want. (We might also consider rem: enfix tighten :remainder)

But it would be good before making changes to ensure that the decisions aren't losing any functionality. As I've said, refinements are our new tool for getting there, since there is a way of calling infix with refinements now.

So if you can take this all into account, integrate them into the tests, try not to wind up leaving out any code that's there today (but stick weird behaviors under less-used refinements)...I'm happy with whatever you decide!

@giuliolunati
Copy link
Author

What if REMAINDER was implemented as mezzanine, based on DIVIDE and ROUND?
Obviously, loss in performance; roughly 3 times for numbers, 10 times for pairs. But much less C code.

@giuliolunati
Copy link
Author

Finally, I decided to not touch REMAINDER. MOD was bugged and its difference from MODULO apparently not interesting.

ac52a71 defines MOD as the enfix form of MODULO

@hostilefork
Copy link
Member

hostilefork commented Oct 16, 2018

Okay, sorry I lied, I'm going to get involved despite saying I wouldn't...mostly because tests broke and then I checked with Wolfram Alpha and it looks like the old answers matched. Don't be mad! :-)

So my proposal is: modulo x d has the same sign as d (while remainder x d has the same sign as x / d)

Looking into it, it seems your definition almost agrees with Clojure/Haskell:

Haskell and Scheme offer two functions, remainder and modulo – PL/I has mod and rem, while Fortran has mod and modulo; in each case, the former agrees in sign with the dividend, and the latter with the divisor.: https://stackoverflow.com/a/28027235/211160

Would it be okay to go with their choice? I think the argument is that remainder being the same sign as x probably makes it more predictable.


Something about the tests is that if we compare not just Haskell/Clojure, but also Wolfram Alpha, and Google. Both of which say -3 mod -2 is -1. :-/

http://www.wolframalpha.com/input/?i=-3+mod+-2
https://www.google.com/search?&q=-3+mod+-2

Additionally they believe that -562949953421312.75 mod 1 is 0.25. By just setting MOD to enfix MODULO it seems to lose something a lot of people believe in...getting these fractional amounts.

If anything, it looks like this commit may be backwards, that MOD was the one that was right and MODULO is the one no one else knows about or agrees with. Is there a reason not to flip it the other way? I think it seems like if your inputs aren't integers then you should be willing to do rounding vs. have the MOD do it itself...but then again, maybe MOD should always be integers and MODULO/FLOAT be specialized as FMOD (?)

Really hard for me to say as I only use MOD on positive integers!

@hostilefork hostilefork reopened this Oct 16, 2018
@giuliolunati
Copy link
Author

giuliolunati commented Oct 16, 2018

@hostilefork let me explain.
MODULO tries to detect when the estimated error on result is >= of result itself, only in that case it rounds to integer returning 0. So difference with Wolfram seems due to different precision.
I think this rounding-to-zero behaviour is better than returning a random result, due to approximation.

@hostilefork
Copy link
Member

hostilefork commented Oct 16, 2018

MODULO tries to detect when the estimated error on result is >= of result itself, only in that case it rounds to integer returning 0. So difference with Wolfram seems due to different precision.

Hmmm, I see. Interesting. 562949953421311.25 is from the %mod.test.reb. I do see that Clojure seems to think this particular case is 0.25, where Rebol says 0.0:

Clojure 1.8.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_91-b14
user=> (mod (double 562949953421311.25) (double 1))
=> 0.25

Take the last 1 off before the decimal point, and Rebol modulo also says 0.25:

>> modulo 56294995342131.25 1  
== 0.25

But add a 1 before the decimal point instead, and Clojure will say 0.0 too.

Getting micro-technical for a moment, Clojure says of the two numbers:

(double 562949953421311.25)
=> 5.6294995342131125E14
(double 5629499534213111.25)
=> 5.629499534213111E15

So adding the decimal digit is enough to push it over bitwise into no longer having the resolution for the .25 at that point. Abstractly speaking, it seems like it may have the information to know in a case Rebol is reporting as 0.0, but if this really is just some edge case fringe behavior I'm very uninterested in whether it's optimal or not. :-)

I think this rounding-to-zero behaviour is better than returning a random result, due to approximation.

It looked like a lot of tests had started failing, but I guess it looks like maybe these tests were picked specifically to be on an edge of distinction between MOD and MODULO.

But if you wouldn't mind going over the tests and lining up the results are acting as you would expect, that would be helpful. You suggested (3 mod -2) = -1 which is consistent with the other sources but the committed code has (3 mod -2) = 1.

https://github.com/metaeducation/ren-c/blob/master/tests/math/mod.test.reb
https://github.com/metaeducation/ren-c/blob/master/tests/math/modulo.test.reb
https://github.com/metaeducation/ren-c/blob/master/tests/math/remainder.test.reb

Beyond the sign issues (and maybe the simpler/same rule for remainder as Clojure/Haskell), maybe making the tests make sense is as simple as knocking a digit off of those cases??

(Note: If you think there's a usefulness to the more complex remainder sign rule--since many languages do not have remainder anyway--I would say I really don't care about doing the same thing as Haskell and Clojure do that much. But the sign rule for mod seems pretty universal on Wolfram Alpha, Google, and your own suggestion...)

@giuliolunati
Copy link
Author

giuliolunati commented Oct 16, 2018

About sign: I tried to be conservative, but if others agree with my humble opinion, then worst to change.

About precision, note that "MONEY" (I hate that name!) works in much higher precision, so $-562949953421312.75 mod $1= $0.25, and you add may other nine 1: $-562949953421312111111111.75 mod $1 = $0.25

@hostilefork
Copy link
Member

hostilefork commented Oct 16, 2018

I think this rounding-to-zero behaviour is better than returning a random result, due to approximation.

Looks like you took it out--but I'm in support of the feature if you think the code was better that way. I just need to have the tests working, because when the tests fail I don't know what I broke! So changing the tests is acceptable too.

I put the MODULO tests into the %mod.test.reb file:

79eb19b

They have some failures now:

>> modulo 0.1 + 0.1 + 0.1 0.3
== 5.551115123125783e-17

Was 0.0 before, which I agree is likely a better answer. But this shows Clojure not rounding:

(mod (+ (double 0.1) (double 0.1) (double 0.1)) (double 0.3))
=> 5.551115123125783E-17

And for whatever it's worth, Haskell doesn't either. (Their default mod is integers only, so the mod' operation is something you have to load in from a package Data.Fixed that isn't included by default. Backticks are how you make a function invocation infix in Haskell.)

Prelude> import Data.Fixed
Prelude> x = (0.1 + 0.1 + 0.1) `mod'` 0.3
Prelude> x
 => 5.551115123125783e-17

Still your call. But I feel like if the code was written and has a point, we probably don't want to throw it out--just didn't want two separate operations. So either it should be the default behavior of MOD with /UNCHECKED (or a better name) as the refinement to ask for not doing the rounding, or the rounding should be optional and asked for as /CHECKED.

@giuliolunati
Copy link
Author

giuliolunati commented Oct 17, 2018

After some experiment I'm skeptical about code trying to detect when remainder is 'almost zero' w.r.t. operands, because there's always some arbitrariness in that.
Better let the user decide.
@hostilefork: if you agree I will keep the current MODULO and MOD implementation and will adapt tests. Let me know.

@hostilefork
Copy link
Member

hostilefork commented Oct 17, 2018

Better let the user decide.

Sounds good. For the sake of arguing that we didn't throw anything out that was there before...can the "almost zero" detection be put under a refinement? MOD/ADJUSTED might be better than MOD/CHECKED... I dunno, whatever name is fine.

@giuliolunati
Copy link
Author

giuliolunati commented Oct 17, 2018

Well, if so maybe worth to set MOD: enfix tighten specialize :MODULO [adjusted: true]. If the user don't trusts the 'almost zero' detection can use MODULO

@giuliolunati
Copy link
Author

Or better other way around, in line with old behaviour: MOD: enfix tighten specialize :MODULO [STRICT: true], and if the user trusts the 'almost zero' detection can use MODULO

@hostilefork
Copy link
Member

Note: Was resolved with mod: enfix tighten :modulo and near-zero behavior isolated to /ADJUSTED refinement...

700c646

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type.wish a.k.a. new feature request
Projects
None yet
Development

No branches or pull requests

2 participants