Флаг переполнения чтения Delphi

Если я сделаю это

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

c будет равно 0, так как сложение переполнилось. Каков наилучший способ поймать это переполненное логическое значение? (a+b<a) or (a+b<b)? действительно хороший способ был бы со встроенным ассемблером, но я не настолько плодовит в ассемблере (хотя я предполагаю, что это будет включать что-то вроде JO)


person Stijn Sanders    schedule 20.06.2011    source источник
comment
Есть ли причина, по которой вы просто не включаете проверку переполнения и проверку диапазона в параметрах компилятора?   -  person Ken White    schedule 21.06.2011
comment
Мое лучшее предположение было бы в том, что он хочет знать, было ли переполнение, но не создавать исключение.   -  person Ken Bourassa    schedule 21.06.2011


Ответы (5)


В ассемблере термин Overflow обычно относится к арифметике со знаком и означает, что знак суммы отличается от знаков обоих операндов; для беззнаковой арифметики предпочтительнее термин Carry.

Вы можете реализовать добавление с проверкой Overflow (Carry) в чистом паскале:

// 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;

Те же функции в сборке - оптимизированный вариант решения Андреаса:

// 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;
person kludg    schedule 21.06.2011
comment
будут ли SETNC AL и SETNO AL очищать AH и старшую половину, если EAX? - person Stijn Sanders; 21.06.2011
comment
@Stijn - Нет, он устанавливает только байт. Чтобы расширить значение нулями до 32 бит, вам нужна еще одна инструкция — movzx eax, al - person kludg; 22.06.2011
comment
@Stijn .... и более высокие биты EAX не используются Delphi. Установки AL достаточно для возврата логического значения, так как проверяется только значение AL. Используйте Alt-F2 поверх логического кода, сгенерированного Delphi, и вы это увидите. - person Arnaud Bouchez; 22.06.2011

Я тоже не эксперт по ассемблеру, но я думаю, что это работает:

Подписанная версия:

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;

Неподписанная версия:

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;
person Andreas Rejbrand    schedule 20.06.2011

Решение Андреаса на чистом паскале (с фиксированным TryAdd, как предлагается в комментариях).

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;
person gabr    schedule 21.06.2011
comment
Подписанный код глючит. Тест: TryAdd( -(MaxInt - 1), -(MaxInt - 1), c) - переполнение (сумма двух отрицаний положительна) не обнаружено. Беззнаковый код неэффективен. Сомневаюсь, что это лучшее решение. - person kludg; 21.06.2011
comment
Все, что говорит Сергей, правда. Но тогда все зависит от того, какие переполнения вы на самом деле пытаетесь поймать и как часто вы вызываете этот Add... - person gabr; 21.06.2011
comment
@gabr - код Андреаса (который вы эмулируете) верен, хотя и не оптимален. - person kludg; 21.06.2011
comment
@Serg Хороший вопрос. Это тривиально легко исправить, не так ли. Я бы проверил InRange(sum, low(c), high(c)). Мое представление о лучшем проще всего понять. - person David Heffernan; 21.06.2011
comment
@David, @gabr - Да, это работает, хотя модульные тесты не являются чрезмерными, чтобы быть уверенным. Иногда я нахожу инструкции по сборке более простыми для понимания и использования. - person kludg; 21.06.2011

{$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;

Естественно, чтение FLAGS с использованием метода A должно следовать сразу за рассматриваемым оператором, что делает этот метод не очень практичным. Напротив, метод B использует структурированную обработку ошибок языка высокого уровня и, следовательно, рекомендуется.

person Premature Optimization    schedule 21.06.2011
comment
Я бы никогда не использовал первый метод. Флаги могли быть переопределены компилятором по любой неочевидной причине (если вы перегрузили операторы и т.п.). Это может привести к проблемам, которые очень трудно отследить. И второе решение слишком сложное для задачи: если вы определите $O+, вам придется сбросить состояние компиляции до предыдущего $O? состояние после использования, так что это совсем не удобно. Есть и другие решения, которые намного безопаснее, а также быстрее. - person Arnaud Bouchez; 21.06.2011
comment
@A.Bouchez, ну, OP хотел прочитать OF, и поскольку метод A прекрасно показывает, насколько это проблематично, тогда появляется метод B, который сообщает генератору кода автоматически вставлять проверку (по сути то же самое, но с гарантированным правильным размещением). Что не так с $O+, кстати? - person Premature Optimization; 21.06.2011
comment
Если вы измените статус $optimization или $overflochecks, вам лучше сохранить его состояние, а затем восстановить его, когда это будет сделано. По крайней мере, если вы хотите, чтобы чистый код мог компилироваться со всеми статусами. Сохранение, а затем восстановление состояния условного оператора — сложная задача — см. ответ Миши. А глобальная настройка {$optimization off} не очень хорошая идея. - person Arnaud Bouchez; 22.06.2011
comment
@A.Bouchez, имейте в виду, что $O и $Q имеют локальную область видимости. Я не согласен с тем, что быть слишком многословным по этому поводу - очень хорошая идея. В любом случае, я просто демонстрировал концепцию без каких-либо намерений заразить какой-то проект или даже библиотеку. - person Premature Optimization; 22.06.2011

Я ненавижу ассемблер (полностью не переносимый), поэтому использую проверку переполнения, например:

{$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}
person Misha    schedule 21.06.2011
comment
Гм... $IOCHECKS не проверяет переполнение. Это проверка ввода/вывода; может поэтому он и называется IOCHECKS? :) Возможно, вы захотите изменить его на $Q или $OVERFLOWCHECKS. - person Ken White; 21.06.2011
comment
Сохранение, а затем восстановление состояния условного выражения - многословная задача... Если вы ненавидите asm, у вас есть паскаль-версия в ответе Serg. Более чистый подход, чем со всем этим условным приемом. - person Arnaud Bouchez; 22.06.2011