www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Why there is too many uneccessary casts?

reply "Temtaime" <temtaime gmail.com> writes:
ubyte k = 10;
ubyte c = k + 1;

This code fails to compile because of: Error: cannot implicitly 
convert expression (cast(int)k + 1) of type int to ubyte

Why? It's pain in the ass, i think. My code contains only casts 
then.
Jun 11 2013
next sibling parent reply "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Tue, 11 Jun 2013 12:12:25 +0200, Temtaime <temtaime gmail.com> wrote:

 ubyte k = 10;
 ubyte c = k + 1;

 This code fails to compile because of: Error: cannot implicitly convert  
 expression (cast(int)k + 1) of type int to ubyte

 Why? It's pain in the ass, i think. My code contains only casts then.
Because it's unsafe. The compiler does not know if k+1 fits in a ubyte. (True, in this case it could know it, but it does not, and in the general case it can't know) -- Simen
Jun 11 2013
parent reply "Temtaime" <temtaime gmail.com> writes:
There is overflow and it can be with int too.
It's standard behavior.
Jun 11 2013
next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Tue, 11 Jun 2013 12:39:47 +0200, Temtaime <temtaime gmail.com> wrote:

 There is overflow and it can be with int too.
 It's standard behavior.
Indeed. And a class is a void* is an int is a char is a double? That's perfectly possible - it's all just memory anyway. D has chosen to do it like this to prevent common errors. If you think the cast stands out like a sore thumb, use: ubyte k = 10; ubyte c = (k + 1) & 0xFF; That way, value range propagation ensures the result fits in a ubyte, and the code compiles happily. -- Simen
Jun 11 2013
prev sibling parent reply "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Tue, 11 Jun 2013 13:15:11 +0200, Simen Kjaeraas  
<simen.kjaras gmail.com> wrote:

 On Tue, 11 Jun 2013 12:39:47 +0200, Temtaime <temtaime gmail.com> wrote:

 There is overflow and it can be with int too.
 It's standard behavior.
Indeed. And a class is a void* is an int is a char is a double? That's perfectly possible - it's all just memory anyway. D has chosen to do it like this to prevent common errors. If you think the cast stands out like a sore thumb, use: ubyte k = 10; ubyte c = (k + 1) & 0xFF; That way, value range propagation ensures the result fits in a ubyte, and the code compiles happily.
Also worth noting: both & 0xFF and cast(ubyte) shows that the programmer has considered the possibility of overflow (or just programmed blindly, but let's assume a rational programmer), something your original code did not. Looking at this code: ubyte a = foo(); ubyte b = a + 1; doSomethingWith(b); It's not possible for me to know if that code works correctly if foo() returns 255 - perhaps it should actually be saturated (255+1 == 255), perhaps it should even throw an exception. With this code: ubyte a = foo(); ubyte b = cast(ubyte)(a + 1); // or (a + 1) & 0xFF; doSomethingWith(b); The programmer has documented something: If this causes an overflow, the value in b is still correct. Explicit is better than implicit. -- Simen
Jun 11 2013
parent reply "Temtaime" <temtaime gmail.com> writes:
No. I means, that

uint a = uint.max;
uint b = a + 1;
writeln(b);

Works OK.
Why? Compiler doesn't know if a + b fits in uint, right?
Then why overflow with ints are accepted?

So your example is meaningless.
Jun 11 2013
next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Tue, 11 Jun 2013 13:46:11 +0200, Temtaime <temtaime gmail.com> wrote:

 No. I means, that

 uint a = uint.max;
 uint b = a + 1;
 writeln(b);

 Works OK.
 Why? Compiler doesn't know if a + b fits in uint, right?
 Then why overflow with ints are accepted?
Because there's a limit to how far this goes without introducing arbitrary-precision numbers. There is indeed an argument for uint+uint giving a ulong answer, and I'm confused myself at why this is not the case.
 So your example is meaningless.
No. -- Simen
Jun 11 2013
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Temtaime:

 Why? Compiler doesn't know if a + b fits in uint, right?
 Then why overflow with ints are accepted?
It's an inconstancy based on practical considerations. Walter decided that applying the same rule to uint/int/long causes too many casts in normal problems. And the range of a 32 bit int is much larger than the range of a ubyte/byte/ushort/short, so in normal programs the probability of overflowing an int by mistake is much lower than overflowing a ubyte.
 So your example is meaningless.
You should be more gentle if you want people to keep giving you answers :-) Bye, bearophile
Jun 11 2013
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 11 Jun 2013 07:46:11 -0400, Temtaime <temtaime gmail.com> wrote:

 No. I means, that

 uint a = uint.max;
 uint b = a + 1;
 writeln(b);

 Works OK.
 Why? Compiler doesn't know if a + b fits in uint, right?
 Then why overflow with ints are accepted?
CPU performs math at int level. So even if you add two ubytes, they are added at integer level. For instance, this is valid, and does not produce a truncated uint: ubyte x = ubyte.max; uint y = x + x; assert(y == 255 + 255); Essentially the compiler is making sure you wanted to throw away that extra precision that it had to create anyways because that's what the CPU supports. It can take some getting used to. Note that += does not have this problem: ubyte c = k; c += 1; // ok This I find extremely inconsistent... -Steve
Jun 11 2013
parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Tuesday, 11 June 2013 at 16:05:30 UTC, Steven Schveighoffer 
wrote:
 CPU performs math at int level.
eh, I wouldn't blame the hardware. You can do asm { mov AL, 10; add AL, 5; } and it is allowed, it also don't spill into AH if you overflow it (it just sets the carry flag). I'm sure it is different on different processors, but x86 is pretty flexible.
 ubyte c = k;
 c += 1; // ok

 This I find extremely inconsistent...
I'd be extremely annoyed if that required a cast. It's bleeding obvious that you want it to assign back there....
Jun 11 2013
next sibling parent "QAston" <qaston gmail.com> writes:
On Tuesday, 11 June 2013 at 16:18:54 UTC, Adam D. Ruppe wrote:
 I'd be extremely annoyed if that required a cast. It's bleeding 
 obvious that you want it to assign back there....
To me u = u + k is as obvious as u += k, but that's probably not a thing anyone would be much concerned about :)
Jun 11 2013
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 11 Jun 2013 12:18:52 -0400, Adam D. Ruppe  
<destructionator gmail.com> wrote:

 On Tuesday, 11 June 2013 at 16:05:30 UTC, Steven Schveighoffer wrote:
 CPU performs math at int level.
eh, I wouldn't blame the hardware. You can do asm { mov AL, 10; add AL, 5; } and it is allowed, it also don't spill into AH if you overflow it (it just sets the carry flag). I'm sure it is different on different processors, but x86 is pretty flexible.
Well, that is not what I knew, so good lesson :) But I'd be surprised if the above was less costly than a full word addition. I think that is the point of C's policy of integer promotion, which D adopts for the most part (although C allows it to go back into whatever).
 ubyte c = k;
 c += 1; // ok

 This I find extremely inconsistent...
I'd be extremely annoyed if that required a cast. It's bleeding obvious that you want it to assign back there....
I argue the consistency is both ways :) I don't want to require a cast there (and in reality, where would the cast go?), but it seems that if you are doing math among bytes, or a byte and an integer that can fit into a byte, it should be quite obvious that you are working in the byte land. The fact that k += 1; and k = k + 1; are treated differently is annoying. -Steve
Jun 11 2013
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Jun 11, 2013 at 01:25:24PM -0400, Steven Schveighoffer wrote:
 On Tue, 11 Jun 2013 12:18:52 -0400, Adam D. Ruppe
 <destructionator gmail.com> wrote:
 
On Tuesday, 11 June 2013 at 16:05:30 UTC, Steven Schveighoffer wrote:
CPU performs math at int level.
eh, I wouldn't blame the hardware. You can do asm { mov AL, 10; add AL, 5; } and it is allowed, it also don't spill into AH if you overflow it (it just sets the carry flag). I'm sure it is different on different processors, but x86 is pretty flexible.
Well, that is not what I knew, so good lesson :) But I'd be surprised if the above was less costly than a full word addition. I think that is the point of C's policy of integer promotion, which D adopts for the most part (although C allows it to go back into whatever).
It may or may not be less costly as full-word addition, but it definitely allows the optimizer more wiggle room, because it allows more byte-sized values to fit into registers before spilling over to memory accesses. But that doesn't really pertain to this discussion. :-P
ubyte c = k;
c += 1; // ok

This I find extremely inconsistent...
I'd be extremely annoyed if that required a cast. It's bleeding obvious that you want it to assign back there....
I argue the consistency is both ways :) I don't want to require a cast there (and in reality, where would the cast go?), but it seems that if you are doing math among bytes, or a byte and an integer that can fit into a byte, it should be quite obvious that you are working in the byte land. The fact that k += 1; and k = k + 1; are treated differently is annoying.
Yeah, I agree that is annoyingly inconsistent. I'm sure there are good reasons for it -- avoid common overflow bugs, for example, while not making regular int arithmetic a pain to work with by requiring casts everywhere. But still, it *is* an inconsistency, and it's still annoying. T -- Ruby is essentially Perl minus Wall.
Jun 11 2013
prev sibling parent reply Timothee Cour <thelastmammoth gmail.com> writes:
On Tue, Jun 11, 2013 at 11:26 AM, H. S. Teoh <hsteoh quickfur.ath.cx> wrote:

 On Tue, Jun 11, 2013 at 01:25:24PM -0400, Steven Schveighoffer wrote:
 On Tue, 11 Jun 2013 12:18:52 -0400, Adam D. Ruppe
 <destructionator gmail.com> wrote:

On Tuesday, 11 June 2013 at 16:05:30 UTC, Steven Schveighoffer wrote:
CPU performs math at int level.
eh, I wouldn't blame the hardware. You can do asm { mov AL, 10; add AL, 5; } and it is allowed, it also don't spill into AH if you overflow it (it just sets the carry flag). I'm sure it is different on different processors, but x86 is pretty flexible.
Well, that is not what I knew, so good lesson :) But I'd be surprised if the above was less costly than a full word addition. I think that is the point of C's policy of integer promotion, which D adopts for the most part (although C allows it to go back into whatever).
It may or may not be less costly as full-word addition, but it definitely allows the optimizer more wiggle room, because it allows more byte-sized values to fit into registers before spilling over to memory accesses. But that doesn't really pertain to this discussion. :-P
ubyte c = k;
c += 1; // ok

This I find extremely inconsistent...
I'd be extremely annoyed if that required a cast. It's bleeding obvious that you want it to assign back there....
I argue the consistency is both ways :) I don't want to require a cast there (and in reality, where would the cast go?), but it seems that if you are doing math among bytes, or a byte and an integer that can fit into a byte, it should be quite obvious that you are working in the byte land. The fact that k += 1; and k = k + 1; are treated differently is annoying.
Yeah, I agree that is annoyingly inconsistent. I'm sure there are good reasons for it -- avoid common overflow bugs, for example, while not making regular int arithmetic a pain to work with by requiring casts everywhere. But still, it *is* an inconsistency, and it's still annoying. T -- Ruby is essentially Perl minus Wall.
wouldn't it be a better and more consistent idea to implement bearophile's 'Compiler support to implement efficient safe integrals' http://d.puremagic.com/issues/show_bug.cgi?id=9850 so uint/int etc would require no cast
Jun 11 2013
parent "Adam D. Ruppe" <destructionator gmail.com> writes:
On Tuesday, 11 June 2013 at 19:09:11 UTC, Timothee Cour wrote:
 wouldn't it be a better and more consistent idea to implement 
 bearophile's 'Compiler support to implement efficient safe 
 integrals'
 http://d.puremagic.com/issues/show_bug.cgi?id=9850 so uint/int 
 etc would require no cast
This isn't really about overflow though, it is about cutting off bits on assignment. int a = 0; byte b = a; test11.d(3): Error: cannot implicitly convert expression (a) of type int to byte The only reason this comes up in the OP's case is because of the int promotion for arithmetic.
Jun 11 2013
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Temtaime:

 ubyte k = 10;
 ubyte c = k + 1;

 This code fails to compile because of: Error: cannot implicitly 
 convert expression (cast(int)k + 1) of type int to ubyte

 Why? It's pain in the ass, i think. My code contains only casts 
 then.
I agree that sometimes that's a pain. Currently D performs a range analysis only inside one expression, and not across different ones. One way to avoid the cast is to perform a masking: void main() { immutable ubyte UB = ubyte.max; ubyte k = 10; ubyte c = (k + 1) & UB; } The solution is to improve D. Beside fixing this bug: http://d.puremagic.com/issues/show_bug.cgi?id=9107 A first step is to keep the range value for immutable variables, but this is not enough to solve your problem because your k is mutable: http://d.puremagic.com/issues/show_bug.cgi?id=10018 Your case is very simple. It's just a sequence of instructions, with no gotos, no jumps, no control instructions, no function calls. Introducing some simple conservative rules for such simple situation is possible. But the advantage of the current situation is that it make usually easy to know where to put a cast and where it's not needed. The more complex the rules become, the harder is to know where the casts are not needed. Bye, bearophile
Jun 11 2013
prev sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Tuesday, 11 June 2013 at 10:12:27 UTC, Temtaime wrote:
 ubyte k = 10;
 ubyte c = k + 1;

 This code fails to compile because of: Error: cannot implicitly 
 convert expression (cast(int)k + 1) of type int to ubyte
The reason is arithmetic operations transform the operands into ints, that's why the error says cast(int)k. Then it thinks int is too big for ubyte. It really isn't about overflow, it is about truncation. That's why uint + 1 is fine. The result there is still 32 bits so assigning it to a 32 bit number is no problem, even if it does overflow. But k + 1 is promoted to int first, so it is a 32 bit number and now the compiler complains that you are trying to shove it into an 8 bit variable. Unless it can prove the result still fits in 8 bits, it complains, and it doesn't look outside the immediate line of code to try to prove it. So it thinks k can be 255, and 255 + 1 = 256, which doesn't fit in 8 bits. The promotion to int is something D inherited from C and probably isn't going anywhere.
Jun 11 2013
parent reply captaindet <2krnk gmx.net> writes:
On 2013-06-11 07:35, Adam D. Ruppe wrote:
 On Tuesday, 11 June 2013 at 10:12:27 UTC, Temtaime wrote:
 ubyte k = 10;
ubyte c = k + 1;

 This code fails to compile because of: Error: cannot implicitly
 convert expression (cast(int)k + 1) of type int to ubyte
The reason is arithmetic operations transform the operands into ints, that's why the error says cast(int)k. Then it thinks int is too big for ubyte. It really isn't about overflow, it is about truncation. That's why uint + 1 is fine. The result there is still 32 bits so assigning it to a 32 bit number is no problem, even if it does overflow. But k + 1 is promoted to int first, so it is a 32 bit number and now the compiler complains that you are trying to shove it into an 8 bit variable. Unless it can prove the result still fits in 8 bits, it complains, and it doesn't look outside the immediate line of code to try to prove it. So it thinks k can be 255, and 255 + 1 = 256, which doesn't fit in 8 bits. The promotion to int is something D inherited from C and probably isn't going anywhere.
i think part of the problem is that '1' is an int. so the calculation must be promoted to integer. if we had byte and ubyte integer literals (suffix b/B and ub/UB?), then if all RHS arguments are (unsigned) bytes the compiler could infer that we are serious with sticking to bytes... ubyte k = 10; // or optional 10ub ubyte c = k + 1ub;
Jun 11 2013
next sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 06/11/2013 05:48 PM, captaindet wrote:

 i think part of the problem is that '1' is an int. so the calculation
 must be promoted to integer.
According to "Integer Promotions" and "Usual Arithmetic Conversions" there is no arithmetic operation that is executed in any type narrower than int: http://dlang.org/type.html
 if we had byte and ubyte integer literals (suffix b/B and ub/UB?), then
 if all RHS arguments are (unsigned) bytes the compiler could infer that
 we are serious with sticking to bytes...
The CPU would have to have special registers to do those operations in. (I am under the impression that x86 does not have any arithmetic registers for types narrower than int.)
 ubyte k = 10;    // or optional 10ub
 ubyte c = k + 1ub;
Things could be that way but I think these rules are inherited from C. Here is another surprising effect: import std.stdio; void foo(int) { writeln("int"); } void foo(byte) { writeln("byte"); } void main() { byte a, b; foo(a + b); // prints "int" foo(a); // prints "byte" } Ali
Jun 11 2013
next sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Wednesday, 12 June 2013 at 00:58:17 UTC, Ali Çehreli wrote:
 The CPU would have to have special registers to do those 
 operations in. (I am under the impression that x86 does not 
 have any arithmetic registers for types narrower than int.)
It does, for 8, 16, 32, and 64 bits. Here's how 8 and 16 bit arithmetic looks: import std.stdio; void main() { ubyte a; ubyte h; // byte addition and subtraction // demoing cast(ubyte) 255 + cast(ubyte) 10 - cast(ubyte) 5 asm { mov AL, 255; add AL, 10; sub AL, 5; mov [a], AL; mov [h], AH; } writefln("AL: %d AH: %d", a, h); // byte multiplication, answer goes into a 16 bit register, but we could truncate it too // demoing cast(ubyte) 50 * cast(ubyte) 10 ushort response; asm { mov AL, 50; mov BL, 10; imul BL; mov [response], AX; // the full answer mov [a], AL; // explicit cast to ubyte } writeln("50 * 10 = ", response, " casted to ubyte gives ", a); // division works the other way // demoing cast(ubyte) 252 / cast(ubyte) 5 ubyte quotient, remainder; asm { mov AH, 0; // clear the high byte because divide uses both, but we're explicitly talking bytes here so we can just zero it mov AL, 252; // our byte value mov BL, 5; // another byte idiv BL; mov [quotient], AL; mov [remainder], AH; } writeln("252 / 50 = ", quotient, " remainder ", remainder); // the x86 can do 16 bits as well ushort s1, s2; asm { mov AX, 65534; add AX, 4; mov [s1], AX; } writeln("16 bit add, expecting overflow: ", s1); asm { mov AX, 1000; mov BX, 1000; mul BX; // AX * BX // the answer is 32 bits here, just like 8 bit multiply put the answer in 16 bits mov [s1], DX; // the high word mov [s2], AX; // the low word } writeln("16 bit mul casted to ushort: ", s2, " full answer: ", s1 << 16 | s2); // you get the idea by now..... } 32 bit and 64 bit follow the same pattern, with ever growing registers. This code runs with both -m32 and -m64 (and it would work as 16 bit if D had that option... dmc does if you actually want to try it!)
Jun 11 2013
parent "Adam D. Ruppe" <destructionator gmail.com> writes:
On Wednesday, 12 June 2013 at 01:35:47 UTC, Adam D. Ruppe wrote:
 	// byte multiplication, answer goes into a 16 bit register, 
 but we could truncate it too
BTW just as a personal note you might be amused by, this multiply instruction beat the crap out of me when I was learning x86 assembly back in the day. Figuring out what was multiplied by what and where the answer went took me an embarrassingly long time, so I avoided mul and div for quite a while when coding. I'd say mul is slow anyway so that's why I was avoiding it, but the truth was I just didn't know how it worked! The instruction sheet saying the product is stored in DX:AX didn't help much, I remember thinking it meant a memory address and getting all kinds of nonsense trying to read from it.... So I'm talking now partially because I'm a bit proud to know all this stuff after my early failures all that time ago.
Jun 11 2013
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 11 Jun 2013 20:58:17 -0400, Ali =C3=87ehreli <acehreli yahoo.com=
 wrote:
 On 06/11/2013 05:48 PM, captaindet wrote:

  > i think part of the problem is that '1' is an int. so the calculati=
on
  > must be promoted to integer.
No, the compiler knows that 1 could fit in a ubyte. It has = value-range-propagation. Changing 1 to a ubyte type would not fix the = problem. The issue is the integer promotion when doing arithmetic.
 According to "Integer Promotions" and "Usual Arithmetic Conversions"  =
 there is no arithmetic operation that is executed in any type narrower=
=
 than int:

    http://dlang.org/type.html
This is exactly right, and the rule is: "4. Else the integer promotions are done on each operand..." which means both are upgraded to int before anything else occurs. -Steve
Jun 11 2013
prev sibling parent captaindet <2krnk gmx.net> writes:
On 2013-06-11 19:48, captaindet wrote:
 On 2013-06-11 07:35, Adam D. Ruppe wrote:
 On Tuesday, 11 June 2013 at 10:12:27 UTC, Temtaime wrote:
 ubyte k = 10;
 ubyte c = k + 1;

 This code fails to compile because of: Error: cannot implicitly
 convert expression (cast(int)k + 1) of type int to ubyte
The reason is arithmetic operations transform the operands into ints, that's why the error says cast(int)k. Then it thinks int is too big for ubyte. It really isn't about overflow, it is about truncation. That's why uint + 1 is fine. The result there is still 32 bits so assigning it to a 32 bit number is no problem, even if it does overflow. But k + 1 is promoted to int first, so it is a 32 bit number and now the compiler complains that you are trying to shove it into an 8 bit variable. Unless it can prove the result still fits in 8 bits, it complains, and it doesn't look outside the immediate line of code to try to prove it. So it thinks k can be 255, and 255 + 1 = 256, which doesn't fit in 8 bits. The promotion to int is something D inherited from C and probably isn't going anywhere.
i think part of the problem is that '1' is an int. so the calculation must be promoted to integer. if we had byte and ubyte integer literals (suffix b/B and ub/UB?), then if all RHS arguments are (unsigned) bytes the compiler could infer that we are serious with sticking to bytes... ubyte k = 10; // or optional 10ub ubyte c = k + 1ub;
clarification: i was only half serious. i understand that indicating a byte literal is not enough and that the promotion rules would have to be altered too. however, i think it would be backwards compatible. then the question is if enough ppl want this feature badly enough to push the issue. i don't think this is the case meaning we will have to accept this little quirk.
Jun 12 2013