Здравствуйте, MasterZiv, Вы писали:
MZ>Да блин очень просто.
MZ>Результат сложения должен быть БОЛЬШЕ каждого из слагаемых.
MZ>Результат вычитания должен быть меньше уменьшаемого.
Это неполная логика, пригодная только для чисел без знака. (Я уж молчу, что тут везде надо "больше или равно".)
Логика большинства современных процессоров выглядит следующим образом:
1. При сложении чисел со знаком, переполнение диагностируется, если у обоих слагаемых совпадают старшие биты, но отличаются от старшего (после переноса) бита результата.
Например, в 8 битах:
0x30 + 0x30 == 0x60 — нет переполнения (48 + 48 == 96);
0x60 + 0x60 == 0xC0 — есть переполнение (у слагаемых старший бит был равен 0, у суммы — 1);
0xD0 + 0xD0 == 0x1A0 — нет переполнения (-48 + (-48) == -96);
0xA0 + 0xA0 == 0x140 — есть переполнение (у слагаемых старший бит был равен 1, у суммы — 0);
0x10 + 0xF0 == 0x100 — нет переполнения (16 + (-16) == 0).
Если слагаемые имели разные знаки, переполнения не может быть в принципе.
Признак переполнения ставится во флаге условия V (в x86 он зовётся OF).
Некоторые процессоры также знают флаг "истинного знака результата" (обозначается S; не путать с N — видимый знак, он же SF в x86). При этом выполняется по определению, что N xor V == S.
Проверки результата сравнения со знаком — такие, как jge, jlt в x86 — проверяют истинный знак, поэтому их условия выглядят как (N xor V) == 0 или 1 (в x86, (SF xor OF) == 0 или 1).
2. При вычитании чисел со знаком, повторяется логика сложения, но знак вычитаемого инвертируется.
Таким образом, при вычитании чисел с одним и тем же знаком переполнение невозможно.
Но при разных — возможно (0x50 — 0xB0 == 0xA0, например).
Флаг V (OF) ставится в соответствии с исправленной таким образом логикой.
3. Для чисел без знака, признаком переполнения служит C==1 (CF==1) одинаково для сложения и вычитания (на процессорах, где вычитание с заёмом, как в x86).
Для процессоров, где вычитание с переносом (как в Mostek 6502), при сложении надо проверять на C==1, а при вычитании — на C==0.
Поэтому простейший вариант анализа на переполнение без поддержки аппаратуры выглядит примерно так:
int checked_add(int a, int b) {
int sum = a + b;
int ssd = a ^ b; // на самом деле нам нужен только старший бит
if ((ssd >= 0) && ((ssd ^ sum) < 0))
throw integer_overflow();
return sum;
}
Разумеется, при поддержке ассемблера это будет проще:
int checked_add(int a, int b) {
__asm__((
" add %0,%1\n"
" jo overflowed"
: "=0" (a): "r"(a), "r"(b): "flags"
));
return a;
overflowed:
throw integer_overflow();
}
(тут надо тщательно вычистить реализацию, но идея должна быть понятна)