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
)
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;
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;
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;
{$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.
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}
精彩评论