I solve the problem by never using mod or division for that matter with negative numbers.
I solve the problem by never using mod or division for that matter with negative numbers.
There are only 2 cases either you truncate to zero or you floor to negative infinity.
The zero behavior breaks the symmetry to help remember.
n * truncate(a/n) + rem(a,n) = a
n * floor(a/n) + mod(a,n) = a
Now suppose that "a" is any real, a/n shared by both. however truncate and floor will do different things to the negatives. truncate "moves" numbers to zero
..............................................................
>>.>>.>>.>>.>>.>>.>>.>>.>>.>>0<<.<<.<<.<<.<<.<<.<<.<<.<<.<<.<<
while floor "moves" number to -inf
..............................................................
<<.<<.<<.<<.<<.<<.<<.<<.<<.<<0<<.<<.<<.<<.<<.<<.<<.<<.<<.<<.<<
when we then multiply by n its clear the positive "a" is the same however for negative a floor < truncate and so mod is positive while rem is negative for a < 0 : floor (a/n) < truncate(a/n) unless n divides a then they are =
The other case is for negative n, but that just flips the division and multiplication twice which has the effect of changing floor to ceiling and truncate to zero be truncate away from zero which flips the mod sign, but leaves rem alone, because its symmetric about zero.Mod is "fun". I agree, I simply avoid negative numbers entirely, and handle them more explicitly when needed. That way it's language independent.
- RoundNearest
- RoundNearestTiesAway
- RoundNearestTiesUp
- RoundToZero — rem, div
- RoundFromZero
- RoundUp
- RoundDown — mod, fld (i.e. floor(x/y) but without incorrect corner cases)
Most languages have no way of doing most of these, but then again, they're mostly pretty useless. They're really only useful when you're pairing division with a specific kind of rounding with a remainder that needs to match. Example of whacky remainder behavior in the "familiar" RoundNearest mode (default rounding mode for floating point):
julia> [k => rem(k, 4, RoundNearest) for k=-6:6]
13-element Vector{Pair{Int64, Int64}}:
-6 => 2
-5 => -1
-4 => 0
-3 => 1
-2 => -2
-1 => -1
0 => 0
1 => 1
2 => 2
3 => -1
4 => 0
5 => 1
6 => -2
Wild, huh? Output range for modulus 4 is -2:2 and whether you get -2 or 2 alternates with each cycle around the ring. (Of course, Julia has comprehensive support for all of these because we're a bunch of nerds for this kind of thing.)Dear God no why?! I thought my confusion around modulus could not get any worse :)