Treating the abusive unsigned syndrome

Denis Koroskin 2korden at gmail.com
Wed Nov 26 11:53:02 PST 2008


On Wed, 26 Nov 2008 21:45:30 +0300, Andrei Alexandrescu  
<SeeWebsiteForEmail at erdani.org> wrote:

> Denis Koroskin wrote:
>> On Wed, 26 Nov 2008 18:24:17 +0300, Andrei Alexandrescu  
>> <SeeWebsiteForEmail at erdani.org> wrote:
>>
>>> Also consider:
>>>
>>> auto delta = a1.length - a2.length;
>>>
>>> What should the type of delta be? Well, it depends. In my scheme that  
>>> wouldn't even compile, which I think is a good thing; you must decide  
>>> whether prior information makes it an unsigned or a signed integral.
>>>
>>  Sure, it shouldn't compile. But explicit casting to either type won't  
>> help. Let's say you expect that a1.length > a2.length and thus expect a  
>> strictly positive result. Putting an explicit cast will not detect (but  
>> suppress) an error and give you an erroneous result silently.
>
> But "silently" and "putting a cast" don't go together. It's the cast  
> that makes the erroneous result non-silent.
>
> Besides, you don't need to cast. You can always use a function that does  
> the requisite checks. std.conv will have some of those, should any  
> change in the rules make it necessary.
>
> By this I'm essentially replying Don's message in the bugs newsgroup:  
> nobody puts a gun to your head to cast.
>
>> Putting an assert(a1.length > a2.length) might help, but the check will  
>> be unavailable unless code is compiled with asserts enabled.
>
> Put an enforce(a1.length > a2.length) then.
>

Right, it is better. Problem is, you don't want to put checks like  
"a1.length > a2.length" into your code (I don't, at least). All you want  
is to be sure that "auto result = a1.length - a2.length" is positive. You  
*then* decide and solve the "a1.length - a2.length >= 0" equation that  
leads to the check. Moreover, why evaluate both a1.length and a2.length  
twice? And you should update all your checks everytime you change your  
code.

>> A better solution would be to write code as follows:
>>  auto delta = unsigned(a1.length - a2.length); // returns an unsigned  
>> value, throws on overflow (i.e., "2 - 4")
>> auto delta = signed(a1.length - a2.length); // returns result as a  
>> signed value. Throws on overflow (i.e., "int.min - 1")
>> auto delta = a1.length - a2.length; // won't compile
>
> Amazingly this solution was discussed with these exact names! The signed  
> and unsigned functions can be implemented as libraries, but  
> unfortunately (or fortunately I guess) that means the bits32 and bits64  
> are available to all code.
>
> One fear of mine is the reaction of throwing of hands in the air "how  
> many integral types are enough???". However, if we're to judge by the  
> addition of long long and a slew of typedefs to C99 and C++0x, the  
> answer is "plenty". I'd be interested in gaging how people feel about  
> adding two (bits64, bits32) or even four (bits64, bits32, bits16, and  
> bits8) types as basic types. They'd be bitbags with undecided sign ready  
> to be converted to their counterparts of decided sign.
>
>> // this one is also handy:
>> auto newLength = checked(a1.length - 1); // preserves type of  
>> a1.length, be it int or uint, throws on overflow
>
> This could be rather tricky. How can overflow be checked? By inspecting  
> the status bits in the processor only; at the language/typesystem level  
> there's little to do.

It is an implementation detail. Expression can be calculated with higher  
bit precision and result compared to needed range.

>
>> I have previously shown an implementation of unsigned/signed:
>>  import std.stdio;
>>  int signed(lazy int dg)
>> {
>>     auto result = dg();
>>     asm {
>>        jo overflow;
>>     }
>>     return result;
>>      overflow:
>>     throw new Exception("Integer overflow occured");
>> }
>>  int main()
>> {
>>    int t = int.max;
>>    try
>>    {
>>        int s = signed(t + 1);
>>        writefln("Result is %d", s);
>>    }
>>    catch(Exception e)
>>    {
>>        writefln("Whoops! %s", e.toString());
>>    }
>>    return 0;
>> }
>
> Ah, there we go! Thanks for pasting this code.
>
>> But Andrei has correctly pointed out that it has a problem - it may  
>> throw without a reason:
>> int i = int.max + 1; // sets an overflow flag
>> auto result = expectSigned(1); // raises an exception
>>  Overflow flag may also be cleared in a complex expression:
>> auto result = expectUnsigned(1 + (uint.max + 1)); // first add will  
>> overflow and second one clears the flag -> no exception as a result
>>  A possible solution is to make the compiler aware of this construct  
>> and disallow passing none (case 2) or more that one operation (case 1)  
>> to the method.
>
> Can't you clear the overflow flag prior to invoking the operation?
>

No need for this, it adds one more instruction for no gain, flag is  
automatically set/reset at any of add/sub/mul operations. It can only save  
you from "auto result = signed(1)" error, that's why I said it should be  
disallowed in first place.

> I'll also mention that making it a delegate reduces appeal quite a bit;  
> expressions under the check tend to be simple which makes the relative  
> overhead huge.
>

Such simple instructions are usually inlined, aren't they?



More information about the Digitalmars-d mailing list