lmi
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [lmi] Remainder of integer division


From: Vadim Zeitlin
Subject: Re: [lmi] Remainder of integer division
Date: Mon, 10 Sep 2018 23:49:28 +0200

On Mon, 10 Sep 2018 21:18:45 +0000 Greg Chicares <address@hidden> wrote:

GC> On 2018-09-07 13:52, Vadim Zeitlin wrote:
GC> [...]
GC> > I think dividing by a negative integer
GC> > is such a rare operation
GC> And I suppose taking the remainder of such an operation is even rarer, but,
GC> incidentally, I happened to try that something like a day or two ago. In
GC> commit 177f78f2, this is one of the two statements that I wanted to rewrite
GC> so that it would work even with a page count of zero:
GC> 
GC> -    int const rows_on_last_page = total_rows_ - (page_count_ - 1) * 
rows_per_page_;
GC> +    int const pages_before_last = (0 == page_count_) ? 0 : page_count_ - 1;
GC> +    int const rows_on_last_page = total_rows_ - rows_per_page_ * 
pages_before_last;
GC> 
GC> It offended my aesthetic sense to use the page count there at all, because
GC> the number of rows on the last page can be calculated without it.

 Yes, but sometimes aesthetic and simplicity are not collinear and in this
case I optimized for the latter. Because at least for me the most natural
answer to the question of "how many rows are there on the last page?" is
"all the remaining rows after filling all the previous page".

 But if we really wanted to get rid of the page count, I'd write this as

        rows_on_last_page = total_rows_ % rows_per_page_;
        if ( !rows_on_last_page )
                rows_on_last_page = rows_per_page_;

which is only slightly less simple to understand but gives the same result
for any strictly positive number of rows, which should be the case here
(and if it isn't, a trivial addition of "&& total_rows_" would fix this).

 I definitely wouldn't write the above as a single expression although I
could consider writing it as

        int const rows_on_last_page = [=]()
                {
                int const rem = total_rows_ % rows_per_page_;
                return rem ? rem : rows_per_page_;
                }();

just to keep the variable const.


GC> And I know how to do that in APL, where it's so simple: 'N-N|N-X' is
GC> the natural answer for X rows in groups of N, and to handle the special
GC> case of zero you just multiply by signum(X), so it's '(×X)×N-N|N-X'.
GC> 
GC> For example, with [1..9] rows in groups of 3:
GC> 
GC>     index origin 0
GC>     N ← 3
GC>     X ← ⍳10
GC>     0  1  2  3  1  2  3  1  2  3   desired answer
GC>     ----------------------------
GC>     0  1  2  3  4  5  6  7  8  9              X
GC>     3  2  1  0 ¯1 ¯2 ¯3 ¯4 ¯5 ¯6            N-X
GC>     0  2  1  0  2  1  0  2  1  0          N|N-X
GC>     3  1  2  3  1  2  3  1  2  3        N-N|N-X
GC>     0  1  1  1  1  1  1  1  1  1    ×X
GC>     0  1  2  3  1  2  3  1  2  3   (×X)×N-N|N-X
GC> 
GC> where
GC>  ← is assignment
GC>  ⍳ is like std::iota()
GC>  | is "residue", like C's '%' for positive integers
GC>  × with no left argument is signum
GC>  × with left and right arguments is multiplication
GC> and everything is read from right to left (all operators--same precedence).

 I admit APL is pretty clear with your explanations _and_ the table above.
I also admit I'd have some trouble unpacking this simple expression without
either of them.


GC> I wondered whether I could do the same thing in C++ (of course I can)
GC> and make it seem natural (I couldn't):

 FWIW the version with the immediately called after declaring it lambda
above is seen as natural in modern C++, whether you like it or not (I
rather do, although mostly in more complicated cases than this one).

[...]
GC> adding that, and condensing everything into a single expression:
GC> 
GC>     X = Z;
GC>     for(auto& i : X) {i = (0!=i)*(N-(N+(N-i)%N)%N);}  show(X);
GC> 
GC> gives the right answer for [0..9] pages in groups of 3:
GC> 
GC>  0  1  2  3  1  2  3  1  2  3
GC> 
GC> but the expression is unreadable.

 It's definitely less readable than any version above.

GC> In part that's because of the way '%'
GC> is defined for negative arguments, and I can write a function to do that
GC> (with the modulus on the left as in APL):
GC> 
GC>     inline constexpr int residue(int modulus, int datum)
GC>     {return (modulus + datum % modulus) % modulus;}
GC> 
GC> and it gives the right answer:
GC> 
GC>     X = Z;
GC>     for(auto& i : X) {i = (0 != i) * (N - residue(N, N - i));} show(X);
GC> 
GC>  0  1  2  3  1  2  3  1  2  3
GC> 
GC> but that doesn't necessarily improve readability.

 Again I can but agree.

GC> I guess some one-line idioms in higher-level languages are just
GC> untranslatable to anything based on C.

 I also think that the simpler C version is more readable than the APL one
but it might be a question of habit. And to my regret, I don't know of
idiomatic Perl 6 version of the above.

 Regards,
VZ


reply via email to

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