February 16, 2010
On Mon, 15 Feb 2010 19:29:27 -0500, Michel Fortin <michel.fortin@michelf.com> wrote:

> On 2010-02-15 18:33:11 -0500, "Steven Schveighoffer" <schveiguy@yahoo.com> said:
>
>> I should clarify, using - on an unsigned value should work, it just should
>> not be assignable to an unsigned type.  I guess I disagree with the
>> original statement for this post (that it should be disabled all
>> together), but I think that the compiler should avoid something that is
>> 99% of the time an error.
>>  i.e.
>>  uint a = -1; // error
>> uint b = 5;
>> uint c = -b; // error
>> int d = -b; // ok
>> auto e = -b; // e is type int
>
> But should this work?
>
> uint a = 0-1;
> uint c = 0-b;
> auto e = 0-b; // e is type int?

Through integer promotion rules, these all work.  This is essentially negation, but it is not a unary operation.  These could also be disallowed, but only after optimization.  Because optimizing cannot change the semantic meaning, they have to be allowed.

That is typeof(uint - uint) is uint, no matter how you do it.

unary negation is a different operator.

>
> uint zero = 0;
> uint a = zero-1;
> uint c = zero-b;
> auto e = zero-b; // e is type int?

No different than your first examples.  e is of type uint, since uint - uint = uint.

> This rule has good intentions, but it brings some strange inconsistencies. The current rules are much easier to predict since they behave always the same whether you have a variable, a literal or a constant expression.

There are plenty of strange inconsistencies in all aspects of computer math.  but unary negation of an unsigned value to get another unsigned value is one of those inconsistencies that is 99% of the time not what the user expected, and easily flagged as an error.

For example, there is no possible way a person unfamiliar with computers (and most programmers who have not run into this) would believe that

b = 5;
a = -b;

would result in a being some large positive number.  It's just totally unexpected, and totally avoidable.

-Steve
February 16, 2010
On Mon, 15 Feb 2010 21:32:21 -0500, Rainer Deyke <rainerd@eldwood.com> wrote:

> Ellery Newcomer wrote:
>> On 02/15/2010 05:33 PM, Steven Schveighoffer wrote:
>>> uint a = -1; // error
>>
>> I can't say I would appreciate having to write
>>
>> uint a = 0xFFFFFFFF;
>>
>> or the equivalent for ulong.
>
> uint a = ~0u;

even ~0 works, no need for the u (although it makes things clearer).

Ellery, you didn't read my original post thoroughly, I said this was the most common case of wanting to use unary negative on an unsigned value, and it's easily rewritten, with the same number of characters no less.

-Steve
February 16, 2010
On Mon, 15 Feb 2010 17:21:09 -0500, Walter Bright <newshound1@digitalmars.com> wrote:

> Steven Schveighoffer wrote:
>> are there any good cases besides this that Walter has?  And even if there are, we are not talking about silently mis-interpreting it.  There is precedent for making valid C code an error because it is error prone.
>
>
> Here's where I'm coming from with this. The problem is that CPU integers are 2's complement and a fixed number of bits. We'd like to pretend they work just like whole numbers we learned about in 2nd grade arithmetic. But they don't, and we can't fix it so they do. I think it's ultimately fruitless to try and make them behave other than what they are: 2's complement fixed arrays of bits.
>
> So, we wind up with oddities like overflow, wrap-around, -int.min==int.min. Heck, we *rely* on these oddities (subtraction depends on wrap-around). Sometimes, we pretend these bit values are signed, sometimes unsigned, and we mix together those notions in the same expression.

One further thing I'll say on this:

signed computer math makes a lot of sense to people because the limits are so large.  For instance, -2 billion to 2 billion.  It seems logical that a computer can't just keep coming up with new bits to represent numbers, but it seems so far off that an integer will wrap at 2 billion.  But unsigned math has a much more familiar boundary -- zero.  Numbers are far more likely to be near zero than they are near 2 billion or -2 billion.  Applying a negation operator to an unsigned value almost *guarantees* wrapping past that boundary.  99% of the time, when I apply a negative sign to a number, I want the negative equivalent of that number.  I don't want some large bizarre value that has no relation to that number, despite what the computer thinks is sane.  I'd look at applying a negation operator to an unsigned int with as much scrutiny as I'd look at multiplying an integer by 1_000_000_000.  It's almost guaranteed to go out of bounds, why are you doing it?

Bringing up -int.min == int.min is exactly my point.  For integers, there is one value that the negation operator doesn't work as expected.  For unsigned integers, there is only one number that *does* work as expected -- zero.  All others either don't work as expected, or rely on the computer behaving strangely (if it is indeed expected).  To make such rare purposeful uses more explicit does not lower the quality of code or the ease of use.

-Steve
February 16, 2010
On 02/15/2010 09:17 PM, Steven Schveighoffer wrote:
> On Mon, 15 Feb 2010 21:32:21 -0500, Rainer Deyke <rainerd@eldwood.com>
> wrote:
>
>> Ellery Newcomer wrote:
>>> On 02/15/2010 05:33 PM, Steven Schveighoffer wrote:
>>>> uint a = -1; // error
>>>
>>> I can't say I would appreciate having to write
>>>
>>> uint a = 0xFFFFFFFF;
>>>
>>> or the equivalent for ulong.
>>
>> uint a = ~0u;
>
> even ~0 works, no need for the u (although it makes things clearer).
>
> Ellery, you didn't read my original post thoroughly, I said this was the
> most common case of wanting to use unary negative on an unsigned value,
> and it's easily rewritten, with the same number of characters no less.
>
> -Steve

Ohhh! that post! You're right; I missed that part.

Alright, here's something I found myself writing just today or yesterday:

//x,r are long, n is ulong

if(x < 0){
  ulong ux = -x;
  ...
}

I also have

if(r < 0){
  return n - (-r) % n;
}

emphasis on ensuring dividend is positive before it gets promoted to ulong, etc etc, and I do guard that r is not remotely close to ulong.max/min.

assuming that the return type is long (it isn't, but it might as well be, since n is always within [2,long.max]) or gets assigned to long or whatever.

-The bottom one obeys your rules.
-The top one doesn't.
-The bottom one is much less clear than the top.
-Whatever I was trying to prove, I think I just inadvertently strengthened your argument tenfold.

and no, I expect this doesn't fall within the 99% use case of unary -
February 16, 2010
On 02/15/2010 11:35 PM, Ellery Newcomer wrote:
> On 02/15/2010 09:17 PM, Steven Schveighoffer wrote:
>> On Mon, 15 Feb 2010 21:32:21 -0500, Rainer Deyke <rainerd@eldwood.com>
>> wrote:
>>
>>> Ellery Newcomer wrote:
>>>> On 02/15/2010 05:33 PM, Steven Schveighoffer wrote:
>>>>> uint a = -1; // error
>>>>
>>>> I can't say I would appreciate having to write
>>>>
>>>> uint a = 0xFFFFFFFF;
>>>>
>>>> or the equivalent for ulong.
>>>
>>> uint a = ~0u;
>>
>> even ~0 works, no need for the u (although it makes things clearer).
>>
>> Ellery, you didn't read my original post thoroughly, I said this was the
>> most common case of wanting to use unary negative on an unsigned value,
>> and it's easily rewritten, with the same number of characters no less.
>>
>> -Steve
>
> Ohhh! that post! You're right; I missed that part.
>
> Alright, here's something I found myself writing just today or yesterday:
>
> //x,r are long, n is ulong
>
> if(x < 0){
> ulong ux = -x;
> ...
> }
>
> I also have
>
> if(r < 0){
> return n - (-r) % n;
> }
>
> emphasis on ensuring dividend is positive before it gets promoted to
> ulong, etc etc, and I do guard that r is not remotely close to
> ulong.max/min.
>
> assuming that the return type is long (it isn't, but it might as well
> be, since n is always within [2,long.max]) or gets assigned to long or
> whatever.
>
> -The bottom one obeys your rules.
> -The top one doesn't.
> -The bottom one is much less clear than the top.
> -Whatever I was trying to prove, I think I just inadvertently
> strengthened your argument tenfold.
>
> and no, I expect this doesn't fall within the 99% use case of unary -

Oh, darn it. nvm
February 16, 2010
Steven Schveighoffer wrote:
> For example, there is no possible way a person unfamiliar with computers 

That's a valid argument if you're writing a spreadsheet program. But programmers should be familiar with computers, and most definitely should be familiar with 2's complement arithmetic.

Similarly, if you do much with floating point, you should be familiar with "What Every Computer Scientist Should Know About Floating-Point Arithmetic"

http://docs.sun.com/source/806-3568/ncg_goldberg.html
February 16, 2010
Walter Bright wrote:

> Steven Schveighoffer wrote:
>> For example, there is no possible way a person unfamiliar with computers
> 
> That's a valid argument if you're writing a spreadsheet program. But programmers should be familiar with computers, and most definitely should be familiar with 2's complement arithmetic.
> 
> Similarly, if you do much with floating point, you should be familiar with "What Every Computer Scientist Should Know About Floating-Point Arithmetic"
> 
> http://docs.sun.com/source/806-3568/ncg_goldberg.html

It's a valid viewpoint, but it is a 'should'. I believe many programmers have only passing familiarity if at all with the semantics of unsigned types and floating point operations. At least when coding, they don't have these semantics in mind. Why do you think Java doesn't have unsigned types?

As the language designer you can say that your target users must have this knowledge, that's fine. Paraphrasing Alexandrescu: this is one of those fundamental coordinates that put D on the landscape of programming languages. I'm quite sure though that when you go look at the empirical side of the story, 'should' does not equate with 'is'.

However D does seem to target C#/Java and even python programmers. It is often suggested D's 'system programming' features are not actually *needed* and it offers enough high-level and safe features for programmers not comfortable with C / C++ to program effectively. This reasoning does not hold for unsigned integers and floating point vagaries.



February 16, 2010
Steven Schveighoffer wrote:
> On Mon, 15 Feb 2010 17:21:09 -0500, Walter Bright <newshound1@digitalmars.com> wrote:
> 
>> Steven Schveighoffer wrote:
>>> are there any good cases besides this that Walter has?  And even if there are, we are not talking about silently mis-interpreting it.  There is precedent for making valid C code an error because it is error prone.
>>
>>
>> Here's where I'm coming from with this. The problem is that CPU integers are 2's complement and a fixed number of bits. We'd like to pretend they work just like whole numbers we learned about in 2nd grade arithmetic. But they don't, and we can't fix it so they do. I think it's ultimately fruitless to try and make them behave other than what they are: 2's complement fixed arrays of bits.
>>
>> So, we wind up with oddities like overflow, wrap-around, -int.min==int.min. Heck, we *rely* on these oddities (subtraction depends on wrap-around). Sometimes, we pretend these bit values are signed, sometimes unsigned, and we mix together those notions in the same expression.
> 
> One further thing I'll say on this:
> 
> signed computer math makes a lot of sense to people because the limits are so large.  For instance, -2 billion to 2 billion.  It seems logical that a computer can't just keep coming up with new bits to represent numbers, but it seems so far off that an integer will wrap at 2 billion.  But unsigned math has a much more familiar boundary -- zero.  Numbers are far more likely to be near zero than they are near 2 billion or -2 billion.  Applying a negation operator to an unsigned value almost *guarantees* wrapping past that boundary.  99% of the time, when I apply a negative sign to a number, I want the negative equivalent of that number.  I don't want some large bizarre value that has no relation to that number, despite what the computer thinks is sane.  I'd look at applying a negation operator to an unsigned int with as much scrutiny as I'd look at multiplying an integer by 1_000_000_000.  It's almost guaranteed to go out of bounds, why are you doing it?
> 
> Bringing up -int.min == int.min is exactly my point.  For integers, there is one value that the negation operator doesn't work as expected.  For unsigned integers, there is only one number that *does* work as expected -- zero.  All others either don't work as expected, or rely on the computer behaving strangely (if it is indeed expected).  To make such rare purposeful uses more explicit does not lower the quality of code or the ease of use.

Well said.

-Lars
February 16, 2010
Lutger wrote:
> It's a valid viewpoint, but it is a 'should'. I believe many programmers have only passing familiarity if at all with the semantics of unsigned types and floating point operations. At least when coding, they don't have these semantics in mind. Why do you think Java doesn't have unsigned types? 

Naive programmers have trouble with Java floating point as well:

    http://www.eecs.berkeley.edu/~wkahan/JAVAhurt.pdf

There's just no getting around it. Should Java just remove floating point types as well?

Heck, I knew a degree'd mechanical engineer who could not understand why his calculator kept giving him answers off by a factor of 2 (he refused to understand roundoff error, no matter how many times I tried to explain it to him - he believed that calculators had mathematically perfect arithmetic). We could ban calculators, but misuse of slide rules is far worse.


> However D does seem to target C#/Java and even python programmers. It is often suggested D's 'system programming' features are not actually *needed* and it offers enough high-level and safe features for programmers not comfortable with C / C++ to program effectively. This reasoning does not hold for unsigned integers and floating point vagaries. 

Pointers are far more troublesome than negating an unsigned.

In my experience with beginning programming courses, the very first thing they explained was 2's complement arithmetic. I do not think it unreasonable at all that someone using a powerful systems programming language ought to understand it.
February 16, 2010
Walter Bright wrote:
> Lutger wrote:
>> It's a valid viewpoint, but it is a 'should'. I believe many programmers have only passing familiarity if at all with the semantics of unsigned types and floating point operations. At least when coding, they don't have these semantics in mind. Why do you think Java doesn't have unsigned types? 
> 
> Naive programmers have trouble with Java floating point as well:
> 
>     http://www.eecs.berkeley.edu/~wkahan/JAVAhurt.pdf
> 
> There's just no getting around it. Should Java just remove floating point types as well?
> 
> Heck, I knew a degree'd mechanical engineer who could not understand why his calculator kept giving him answers off by a factor of 2 (he refused to understand roundoff error, no matter how many times I tried to explain it to him - he believed that calculators had mathematically perfect arithmetic).

How could he refuse? One of my favorite games with calculators was to successively extract square root of 2 until I got 1. The better the calculator, the more steps it takes. That's kind of difficult to refuse to acknowledge :o).

Andrei