August 14, 2020
On Friday, 14 August 2020 at 02:10:20 UTC, mw wrote:
> currently, there is a compile error
> Error: template std.experimental.checkedint.Checked!(long, Abort).Checked.__ctor cannot deduce function from argument types !()(Checked!(ulong, Abort))

Actually this is the compile error I wanted! to force me to re-write.
August 14, 2020
On Friday, 14 August 2020 at 02:10:20 UTC, mw wrote:

The only other extra tedious thing need to do is: manually add checked() on literals in the array:

```
  Long b = 1L;  // OK
//Long[] a = [-5000L, 0L];  // Error: cannot implicitly convert expression [-5000L, 0L] of type long[] to Checked!(long, Abort)[]
  Long[] a = [checked(-5000L), checked(0L)];  // here for each literals
```

August 13, 2020
On Fri, Aug 14, 2020 at 02:10:20AM +0000, mw via Digitalmars-d wrote: [...]
> currently, there is a compile error
> Error: template std.experimental.checkedint.Checked!(long,
> Abort).Checked.__ctor cannot deduce function from argument types
> !()(Checked!(ulong, Abort))
> 
> have to re-write it as:
> ```
>   Long c = sum(a);
>   c /= a.length;
> ```

Try writing it as:

	auto x = Long(sum(a) / a.length);


T

-- 
He who sacrifices functionality for ease of use, loses both and deserves neither. -- Slashdotter
August 14, 2020
On Friday, 14 August 2020 at 02:28:13 UTC, H. S. Teoh wrote:
> On Fri, Aug 14, 2020 at 02:10:20AM +0000, mw via Digitalmars-d wrote: [...]
>> currently, there is a compile error
>> Error: template std.experimental.checkedint.Checked!(long,
>> Abort).Checked.__ctor cannot deduce function from argument types !()(Checked!(ulong, Abort))
                      ^^^^^
                      |||||
We want that error message, maybe you missed that too :-)

> Try writing it as:

same error; but we want it!
August 14, 2020
On Thursday, 13 August 2020 at 23:50:08 UTC, Walter Bright wrote:
> On 8/13/2020 4:33 PM, James Blachly wrote:
>> Especially in cases like this, compiler warnings would be helpful.
>
> Warnings are inherently problematic, as the poor user is always faced with "is this a real problem or not?" I've been very resistant to adding warnings in the past because it balkanizes the language into dialects.
>
> The core language should decide what's an error and what isn't.

First, DMD has -w switch. I know you don't like it (I remember your disapproval from at least 15 years ago), but it's there. :p

It's the logical place to put a warning like this. Having it be a warning means copy-pasted C code will do what it did in C without a hitch, while also providing an easy way to check for something that may not be an error, but can be very surprising.


> To check for overflows, etc., use core.checkedint:

This does not in any way address the problem here, namely that the intuitive way to do things causes issues in possibly-very-rare situations. Telling users to use checkedint for (arr.sum / arr.length) is equivalent to telling them to simply cast arr.length to signed - it's bug-prone in that it's easy to forget, and it's bug-prone in that a new user doesn't know that it's necessary. We want our users to fall into the pit of success, and that is not what's happening here.

--
  Simen
August 14, 2020
On 8/13/2020 10:47 PM, Simen Kjærås wrote:
> This does not in any way address the problem here, namely that the intuitive way to do things causes issues in possibly-very-rare situations.

On a more personal note, I came to C from Pascal. Pascal required explicit casts everywhere one did mixed integer arithmetic. I grew to dislike it, it was ugly and just plain annoying.

Then a friend loaned me K+R, and the way its expressions worked was simple and effective, and looked a lot nicer on the page. I never had any trouble with it. (To be fair, at the time I had done a lot of assembler programming and was very well aware of 2's complement arithmetic. I also had done a lot of PDP-11 assembler and recognized the integral promotion semantics from that.) I never wrote another line of Pascal.

The only thing I did like about Pascal was its way of nested functions, which were marvelous and hence D's marvelous nested functions :-)

As for intuitive, what that really means is what you're used to. 2's complement arithmetic is intuitive for systems programmers and assembler programmers, people coming from school arithmetic are going to find it unintuitive. There isn't a way to please everyone, we have to make a choice.

P.S. I should add to my previous list that people doing systems programming need to come to terms with size_t, ptrdiff_t, and their model dependent behavior. At least D doesn't do random sizes for int, and the random signed-ness of char. C people get regularly punished for assuming char is signed or unsigned. Maybe I should expand this into an article.
August 14, 2020
On Friday, 14 August 2020 at 09:12:50 UTC, Walter Bright wrote:
> On 8/13/2020 10:47 PM, Simen Kjærås wrote:
>> This does not in any way address the problem here, namely that the intuitive way to do things causes issues in possibly-very-rare situations.
>
> On a more personal note, I came to C from Pascal. Pascal required explicit casts everywhere one did mixed integer arithmetic. I grew to dislike it, it was ugly and just plain annoying.
>

To be fair, this issue specifically only happens with int/uint division. Addition, subtraction and even multiplication are all fine.

(*Why* is multiplication fine? I have no idea... but it works in spot testing.)
August 14, 2020
On Friday, 14 August 2020 at 09:12:50 UTC, Walter Bright wrote:
> On 8/13/2020 10:47 PM, Simen Kjærås wrote:
>>
> As for intuitive, what that really means is what you're used to.

Everybody is used to -50/2 = -25. I'd argue that intuition is far more fundamental than 2 compliment. I'd also argue a lot of experienced C/C++ programmers would probably get caught out by that bug because it's the kind of thing that's easy to forget or overlook.

That's why it should be an error.

I reckon most C/C++ people would think it's actually a good idea.


August 14, 2020
On Friday, 14 August 2020 at 09:32:58 UTC, FeepingCreature wrote:
> (*Why* is multiplication fine? I have no idea... but it works in spot testing.)

mul 32 bit * 32 bit and imul 32*32 give the same result in the lower 32 bits of the result. Only the upper half shows the difference.

The algorithm* in binary looks kinda like a sign extension followed by a series of shifts and adds. https://en.wikipedia.org/wiki/Multiplication_algorithm#Binary_or_Peasant_multiplication

The signed product is the sum of left-shifted values after sign extension. We know the sum of two's complement is the same regardless of sign after extension, and that left shift is going to naturally only start to become an issue on the high word. Thus same deal for a 32 bit result (int = int * int), but you can expect differences in a 64 bit result (long = int * int).

Let's first demo it with a 4 bit example. Say -2 * 3.

-2 = 1110 (2 = 0010, then flip the bits + 1: 1101 + 1 = 1110)
 3 = 0011

I'll do the long form in 8 bit, so first, we need to sign extend them, which is just duplicating the msb all the way left):

shr     ---     shl
11111110 * 00000011
-----------
01111111 * 00000110
00111111 * 00001100
00011111 * 00011000
00001111 * 00110000
00000111 * 01100000
00000011 * 11000000
00000001 * 10000000 (c)

Now we get the sum of all the rhs values there if the lhs small bit is set... which happens to be all of them here


11111010 (c)

That's obviously signed, flip the bits and add one to get our result back out, -6. Whether we chop off or keep those high bits, no difference.

That was an imul since I sign extended. Now, let's do mul, the unsigned one. Same deal except we just pad left with zero:

00001110 * 00000011 (unsigned would be 14 * 3)
-------------------
00000111 * 00000110
00000011 * 00001100
00000001 * 00011000

Sum:       00101010 (unsigned is 42)


Well, those lower bits look the same... 1010, though here we interpret that as decimal 10 instead of -6, but same bits so if the compiler casted back to signed we'd never know the difference. But those upper bits... oh my, zeroes instead of ones, positive number.

With positive values, sign extension and and zero extension are the same thing - all zeroes. And since 0 * x = 0 for all x, it is all discarded once it shifts into the target bits.

But with negative values, the sign extension gives us a bunch of ones on the lhs to shift in. The rhs doesn't really care - it gets a bunch of zeroes shifted in on the right so it ignores it. But those ones on the left change the high word, it now results in that rhs stuff getting added.


And if you wanna do a test program with 32 bit numbers of course you will see this same result. Same result as int, discarding those upper 32 bits, but different assigned long or ulong since now the initial sign extension led to different values up there. But since C and D will both happily discard those without an explicit cast you might never even know.


sorry if this was a bit wordy, if i had the time, i would edit it down more
August 14, 2020
On Friday, 14 August 2020 at 09:12:50 UTC, Walter Bright wrote:
> 2's complement arithmetic is intuitive for systems programmers and assembler programmers

A lot of us know perfectly well how it works (at least when we stop and think about it for a second), but it is still easy to just unintentionally mix things and screw it up without realizing it since the edge cases may not manifest quickly.

But I do find this a little ironic given how absolutely brutal D is when it comes to narrowing conversions after implicit promotion. You know:

ushort a, b;
ushort c = a + b; // requires explicit cast!

even

ushort c = ushort(a + b);

complains. Gotta explicitly cast the whole parenthetical, and it is pretty annoying. You and I both know the CPU can do this trivially, in x86 asm it is just `add ax, bx`. Of course we both also know C promotes them to ints (ok to uints here) before any arithmetic and the carry bit is discarded when truncated back to ushort and I understand the concern the compiler is trying to communicate... just I don't care, I want 16 bit operations here.


So anyway I bring this up in this thread because when it comes to signed and unsigned, you say we need to know how two's complement works, this is a systems programming language. But when it comes to discarding carry bits on 16 bit operations, now the compiler treats us like this like we're ignorant fools.

Like I said, I sometimes ignorantly and foolishly mix signed and unsigned when I should know better. I just forget. And maybe I sometimes would do that with int and ushort or whatever too if the compiler didn't say something.

So I get why it does this.

Just it irks me and seeing the very different behavior for these two situations doesn't make much sense to me. Either say we need to know how it works and make the compiler accept it or say we are prone to mistakes and make the compiler complain. I find it hard to believe explicit 16 bit arithmetic is more prone to real world bugs than unsigned issues.