Delphi read overflow flag
Asked Answered
C

5

8

If I do this

var
  a,b,c:cardinal;
begin
  a:=$80000000;
  b:=$80000000;
  c:=a+b;
end;

c will equal 0, since the addition overflowed. What's the best way to catch this overflowed boolean? (a+b<a) or (a+b<b)? a really nice way would be with inline assembler, but I'm not that prolific in assembler (though my guess would be it would envolve something like JO)

Cosher answered 20/6, 2011 at 22:45 Comment(2)
Is there a reason you don't just turn on overflow checking and range checking in the compiler options?Riva
My best guess would be that he wants to know if there was an overflow, but not raise an exception.Hunnicutt
I
8

In assembly the term Overflow usually refers to signed arithmetic and means that the sign of the sum is different from the signs of both operands; for unsigned arithmetic the term Carry is preferable.

You can implement addition with Overflow (Carry) check in pure pascal:

// signed add - returns True if no overflow produced
function SAdd(A, B: integer; out C: integer): Boolean;
begin
  C:= A + B;
  Result:= (A xor B < 0)   // operands have different signs
        or (C xor A >= 0); // sum has the same sign as operands
end;

// unsigned add - returns True if no carry produced
function UAdd(A, B: Cardinal; out C: Cardinal): Boolean;
begin
  C:= A + B;
  Result:= (C >= A);
end;

The same functions in assembly - optimized variant of Andreas' solution:

// Signed Add
function SAdd(A, B: Integer; out C: Integer): Boolean;
asm
        ADD   EAX,EDX
        MOV   [ECX],EAX
        SETNO AL
end;

// Unsigned Add
function UAdd(A, B: Cardinal; out C: Cardinal): Boolean;
asm
        ADD   EAX,EDX
        MOV   [ECX],EAX
        SETNC AL
end;
Iain answered 21/6, 2011 at 3:10 Comment(3)
will SETNC AL and SETNO AL clear AH and the higher half if EAX?Cosher
@Stijn - No, it sets a byte only. To zero-extend the value to 32 bits you need one more instruction - movzx eax, alIain
@Stijn .... and higher bits of EAX are not used by Delphi. Setting AL is enough for returning a boolean, since only AL value is checked. Use Alt-F2 over Delphi-generated boolean code, and you'll see that.Ferrick
F
5

I am not an expert on assembly either, but I think this works:

Signed version:

function TryAdd(a, b: integer; out c: integer): boolean;
asm
  ADD EAX, EDX             // EAX := a + b;
  MOV [c], EAX             // c := EAX;
  JO @@END                 // if overflow goto end;
  MOV EAX, true            // result := true
  RET                      // Exit;
@@END:
  XOR EAX, EAX             // result := false;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  c: integer;
begin
  if TryAdd(MaxInt - 5, 6, c) then
    ShowMessage(IntToHex(c, 8))
  else
    ShowMessage('Overflowed!');
end;

Unsigned version:

function TryAdd(a, b: cardinal; out c: cardinal): boolean;
asm
  ADD EAX, EDX             // EAX := a + b;
  MOV [c], EAX             // c := EAX;
  JC @@END                 // if overflow goto end;
  MOV EAX, true            // result := true
  RET                      // Exit;
@@END:
  XOR EAX, EAX             // result := false;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  c: cardinal;
begin
  if TryAdd($A0000000, $C0000000, c) then
    ShowMessage(IntToHex(c, 8))
  else
    ShowMessage('Overflowed!');
end;
Fye answered 20/6, 2011 at 23:6 Comment(0)
M
5

Andreas' solution in pure pascal (with fixed TryAdd as suggested in the comments).

function TryAdd(a, b: integer; out c: integer): boolean; overload;
var
  sum: int64;
begin
  sum := int64(a) + int64(b);
  Result := (Low(integer) <= sum) and (sum <= High(integer));
  c := integer(Int64Rec(sum).Lo);
end;

function TryAdd(a, b: cardinal; out c: cardinal): boolean; overload;
var
  sum: int64;
begin
  sum := int64(a) + int64(b);
  Result := sum <= High(cardinal);
  c := Int64Rec(sum).Lo;
end;

procedure TForm32.Button1Click(Sender: TObject);
var
  c: integer;
begin
  if TryAdd(MaxInt - 5, 6, c) then
    ShowMessage(IntToHex(c, 8))
  else
    ShowMessage('Overflowed!');
end;
Misuse answered 21/6, 2011 at 7:27 Comment(5)
The signed code is buggy. Test: TryAdd( -(MaxInt - 1), -(MaxInt - 1), c) - overflow (sum of two negatives is positive) is not detected. The unsigned code is inefficient. I doubt this is the best solution.Iain
What Serg says is all true. But then it all depends on which overflows your are actually trying to catch and how often are you calling this Add ...Misuse
@Misuse - Andreas code (which you are emulating) is correct, though not optimal.Iain
@Serg Good point. It's trivially easy to fix though isn't it. I'd test InRange(sum, low(c), high(c)). My idea of best is the easiest to understand.Thermae
@David , @Misuse - Yes, it works, though unit tests are not excessive to be sure. Sometimes I find assembly instructions easier to understand and use.Iain
S
0
{$OPTIMIZATION OFF}
procedure TForm1.FormCreate(Sender: TObject);

  function Overflow(): WordBool;
  const
    fOverflow   = $0800;
  asm
      PUSHF
      POP   AX
      AND   AX, fOverflow;
  end;

var
  I, J, K: Integer;
begin
  I := $80000000;
  J := $80000000;

  { method A - read FLAGS register }
  {$OVERFLOWCHECKS OFF}
  K := I + J;
  if Overflow() then Windows.Beep(5000, 50);

  { method B - have compiler to generate check and catch an exception }
  {$OVERFLOWCHECKS ON}
  try
    K := I + J;
  except on E: EIntOverflow do
    ShowMessage('OH SHI-');
  end;

end;

Naturally, reading FLAGS using Method A must immediately follow an operator in question, thus it makes this method not very practical. In contrast, method B employs High Level Language structured error handling and thus - recommended.

Scandent answered 21/6, 2011 at 1:12 Comment(4)
I would never use the first method. Flags may have been overriden for any non obvious reason by the compiler (if you overloaded operators, and such). It could lead into issues very difficult to track. And the 2nd solution is a bit too complex for the task: if you define $O+, you'll have to reset the compilation state to the previous $O? state after use, so it's not convenient at all. There are other solutions around, which are much safer, and also faster.Ferrick
@A.Bouchez, well, OP wanted to read OF and since Method A perfectly shows how it is problematic - then comes a Method B which tells codegenerator to insert check automatically (essentially the same but with guaranteed proper placement). What's wrong with $O+, BTW?Scandent
If you change the $optimization or $overflochecks status, you'll better have to save its state, then restore it when it's done. At least if you want clean code able to compile with all status. Saving then restoring the state of a conditional is a verbose task - see Misha answer. And setting globaly {$optimization off} is not a good idea.Ferrick
@A.Bouchez, mind you, both $O and $Q have local scope. I disagree what being over-verbose on that is very good idea. Anyway, i was merely demonstrating the concept w/o any intents to "infect" some project or even library.Scandent
S
0

I detest assembler (completey non-portable) so I use overflow checking like:

{$IFOPT Q-}
{$DEFINE CSI_OVERFLOWCHECKS_OFF}
{$ENDIF}
{$OVERFLOWCHECKS ON}

a:=$80000000;
b:=$80000000;
c:=a+b;

{$IFDEF CSI_OVERFLOWCHECKS_OFF}
{$UNDEF CSI_OVERFLOWCHECKS_OFF}
{$OVERFLOWCHECKS OFF}
{$ENDIF}
Statuette answered 21/6, 2011 at 7:47 Comment(2)
Um... $IOCHECKS isn't overflow checking. It's I/O checking; maybe that's why it's named IOCHECKS? :) You might want to change it to $Q or $OVERFLOWCHECKS.Riva
Saving then restoring the state of a conditional is a verbose task... If you hate asm, you have pascal version in Serg answer. Cleaner approach than with all this conditional trick.Ferrick

© 2022 - 2024 — McMap. All rights reserved.