Keith said:
I don't see how you come to that conclusion regarding / % < >?
Given that for unsigned p and q division and modulus, p / d and
p % d, are both defined by p = d * q + r ie in terms of * and +,
and that you claim my view is appropriate for both * and +, it
seems odd to claim it "doesn't work" for / and %.
As for < and > are you saying that a finite modular group
cannot be ordered? Or that /sign/ is necessary for ordering?
I don't think so. Please demonstrate these claims.
Anyhow, it seems to me that, in the words of Leigh, / % < > all
work "just fine" for an /un/signed interpretation that is "having
no sign". Not only that, but the defining equation for / and % is
even simpler for unsigned than it is for integer viz:
unsigned
--------
Let p and d be unsigned
Let p = d * q + r for unsigned * and + and unsigned q and r
such that r < d
Then p = d * q + r has a unique solution with q called the
"quotient" and r the "remainder" and
Define p / d = q
Define p % d = r
integer
-------
Let p and d be integers
Let p = d * q + r for integer * and + and integers q and r
such that |r| < |d| and sgn(r) = sgn(p)
Then p = d * q + r has a unique solution with q called the
"quotient" and r the "remainder" and
Define p / d = q
Define p % d = r
note that constraint on r is simpler for unsigned than it is for
integer, otherwise the defining equations are the same and they
are "just fine" for unsigned as "having no sign" ie there is not
a single reference to sign (nor subtraction) in them (as there
must be for integer).
So can you please demonstrate your claim that /un/signed "does
not work for the operators /, %, <, and >"? Thanks!
Ok, I'll try.
First of all, the claim "unsigned means no-sign" is different from the claim
I was commenting on:
And I have trouble more with the first part of the claim than the second.
The second can be interpreted as saying that operations on unsigned types
can be explained without reference to signs. This is true (as far as I see)
but not very strong a claim since with those explanations we tend to
implicitly assume "positive" values. Nonetheless, please allow me to focus
in my discussion on the "finite modular group" aspect of the unsigned types.
What I shall argue is that this point of view does not do justice to the
operators /, %, and <.
First, let me discuss the issue of order. I agree that < _can_ be defined
for unsigned values (after all it is
. However, the notion of < does not
interact nicely with the additive structure of the modular arithmetic. One
very basic and important property of < for integers (or real numbers for
that matter) is _translation invariance_:
(*) for integers a, b, and c: a < b if and only if a+c < b+c
This does not hold _mod N_. In fact, a finite group cannot be totally
ordered as to satisfy (*). Taking N = 32 as a small example, we have
8 < 15
but
8 + 20 = 28
15 + 20 = 35 = 3 mod 32
thus
15+20 < 8+20
So, even though comparing unsigned values in C++ is meaningful, the meaning
is not really well-aligned with the interpretation of unsigned values as
representing values in a modular group.
Now, let us turn to division and mod. As you point out, / and % can be
defined in terms of *, + and < by
(-) p = d * q + r
subject to the requirement 0 <= r < d. At least, this is what we would
_want_ to obtain: the value for q should be p/d and r should be p%d.
First, note that this definition hinges upon <, which in turn is not really
a concept of modular arithmetic. This triggers some suspicion that / and %
may also not be all that cool with regard to modular arithmetic.
Nonetheless, let us just use the C++ meaning of *, +, < for unsigned types
and see whether (-) can serve as a good definition of / and %.
The crucial property of (-) is that it should determine q and r _uniquely_.
Otherwise, we would not know, which of the possible values to pick. Now, in
modular arithmetic as realized by the unsigned types, this uniqueness fails.
E.g., let us consider arithmetic mod 32 (so that we can do the computations
easily):
p = 19
d = 6
19 = 6*3 + 1 (mod 32) // this is the _desired_ solution since 1 < 6
19 = 6*8 + 3 (mod 32) // oops, this is a second solution since 3 < 6
So, we could have 19/6 = 8 and 19%6 = 3 and still satisfy (-) _mod 32_. In
other words, when you _interpret_ (-) as a statement in modular arithmetic,
then (-) fails to determine / and %.
That is why I'd say that / and % realize operations that are not best viewed
as modular arithmetic.
Which is irrelevant to the /arithmetic/ context. But it is what
I had in mind, of course, when I wrote "That is why we use them
for raw bit manipulation where we don't want /any sign/".
See above. Please show how a non-negative integer interpretation
is "best" compared to non-signed finite modular group.
Well, this is precisely the matter of (-). Once you interpret the symbols of
(-) over non-negative integers, the condition indeed does determine unique
values for q and r.
It's written in black-and-white in the standard. I don't know how
much more firmly rooted it could be. What would convince you?
I don't deny what is in the standard. What I deny is that the catch phrase
"unsigned means modular" captures the essence of unsigned types. It
overemphasizes a particular aspect and does not do justice to _other_
provisions in the standard that are hard to reconcile with modular
arithmetic.
I can only speak from personal experience as, frankly, this issue
just hasn't come up within any of the groups I've worked. (I mean
to say we've never debated the concepts.)
So I can only say that once I agreed with Leigh's position that
it was "just divine fine" to use unsigned when the semantic was
"always positive" that is "having a sign that happens to always
be positive". However, in anything but the simplest scenarios
(like the toy for loops Leigh keeps regurgitating) I ended up
regretting the decision because invariably I needed to perform
/arithmetic/ on those "always positive" types. And that in turn
often required annoying casts that ugglified the code.
Once I changed my perspective to view unsigned as a modular type
literally as /un/signed ie "having no sign", such nastiness just
evaporated (expect where we are forced to interact with unsigned
types in /arithmetic/ scenarios).
Now, I cannot dispute personal experience. So, I am prepared to accept that
once programmers adopt the view of unsigned types as (mainly) realizing
modular arithmetic, they might change their use of unsigned types and run
into fewer problems because of that shift.
What I do agree on is that "unsigned means non-negative" is a misguided
mantra, which has (also) no foundation in the standard. I also can see that
following this slogan does lead to problems. I would suspect (although I am
open to correction from experience) that the main benefit of the phrase
"unsigned means modular" is to act as an antidote to "unsigned means non-
negative".
[...]
PS. One scenario I specifically recall where my initial unsigned
index choice (because indexes into vectors are "always positive"
right ;-) later became annoying was in implementing a convolution
function optimized for small kernels operating on large vectors.
Hm, that sounds very interesting. I only did a toy implementation of DFT
once (more or less to see what it is) and implemented convolution on top of
that. I don't recall heavy index arithmetic, rather the algorithms would
just work on ranges given by iterators. But again, I did not optimize in any
way or form; Here, it appears, the devil is hiding in the details ready to
jump at you any moment.
Best
Kai-Uwe Bux