[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi] Remainder of integer division [Was: logic vs runtime errors]
From: |
Greg Chicares |
Subject: |
[lmi] Remainder of integer division [Was: logic vs runtime errors] |
Date: |
Mon, 10 Sep 2018 21:18:45 +0000 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.9.1 |
On 2018-09-07 13:52, Vadim Zeitlin wrote:
[...]
> I think dividing by a negative integer
> is such a rare operation
And I suppose taking the remainder of such an operation is even rarer, but,
incidentally, I happened to try that something like a day or two ago. In
commit 177f78f2, this is one of the two statements that I wanted to rewrite
so that it would work even with a page count of zero:
- int const rows_on_last_page = total_rows_ - (page_count_ - 1) *
rows_per_page_;
+ int const pages_before_last = (0 == page_count_) ? 0 : page_count_ - 1;
+ int const rows_on_last_page = total_rows_ - rows_per_page_ *
pages_before_last;
It offended my aesthetic sense to use the page count there at all, because
the number of rows on the last page can be calculated without it. And I
know how to do that in APL, where it's so simple: 'N-N|N-X' is the natural
answer for X rows in groups of N, and to handle the special case of zero
you just multiply by signum(X), so it's '(×X)×N-N|N-X'.
For example, with [1..9] rows in groups of 3:
index origin 0
N ← 3
X ← ⍳10
0 1 2 3 1 2 3 1 2 3 desired answer
----------------------------
0 1 2 3 4 5 6 7 8 9 X
3 2 1 0 ¯1 ¯2 ¯3 ¯4 ¯5 ¯6 N-X
0 2 1 0 2 1 0 2 1 0 N|N-X
3 1 2 3 1 2 3 1 2 3 N-N|N-X
0 1 1 1 1 1 1 1 1 1 ×X
0 1 2 3 1 2 3 1 2 3 (×X)×N-N|N-X
where
← is assignment
⍳ is like std::iota()
| is "residue", like C's '%' for positive integers
× with no left argument is signum
× with left and right arguments is multiplication
and everything is read from right to left (all operators--same precedence).
I wondered whether I could do the same thing in C++ (of course I can)
and make it seem natural (I couldn't):
void show(std::vector<int> v)
{
for(auto const& i : v) {std::cout << std::setw(2) << i << " ";}
std::cout << std::endl;
}
int N = 3;
std::vector<int> X(10);
std::iota(X.begin(), X.end(), 0); show(X);
std::vector<int> Z {X}; // save a copy of the original
for(auto& i : X) {i = N - i;} show(X);
for(auto& i : X) {i = i % N;} show(X); // remainder, not residue
for(auto& i : X) {i = (N + i) % N;} show(X); // residue
for(auto& i : X) {i = N - i;} show(X);
Output:
0 1 2 3 4 5 6 7 8 9
3 2 1 0 -1 -2 -3 -4 -5 -6
0 2 1 0 -1 -2 0 -1 -2 0
0 2 1 0 2 1 0 2 1 0
3 1 2 3 1 2 3 1 2 3
So that's 'N-N|N-X', which only needs to be multiplied by signum();
adding that, and condensing everything into a single expression:
X = Z;
for(auto& i : X) {i = (0!=i)*(N-(N+(N-i)%N)%N);} show(X);
gives the right answer for [0..9] pages in groups of 3:
0 1 2 3 1 2 3 1 2 3
but the expression is unreadable. In part that's because of the way '%'
is defined for negative arguments, and I can write a function to do that
(with the modulus on the left as in APL):
inline constexpr int residue(int modulus, int datum)
{return (modulus + datum % modulus) % modulus;}
and it gives the right answer:
X = Z;
for(auto& i : X) {i = (0 != i) * (N - residue(N, N - i));} show(X);
0 1 2 3 1 2 3 1 2 3
but that doesn't necessarily improve readability.
I guess some one-line idioms in higher-level languages are just
untranslatable to anything based on C.