lmi
[Top][All Lists]
Advanced

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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]