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