Здравствуйте, Marty, Вы писали:
W>>Аналогично и с сигнализацией переполнения — везде всё настолько по-разному реализовано, что какого-то единого интерфейса предложить нельзя.
M>Есть процессоры без флаговых регистров? А язык C для них есть?
M>Intel, ARM, MIPS — есть флаги;
Ты явно что-то путаешь. У MIPS флагового регистра в привычном виде нет. У него есть команды перехода по условию и установки целевого регистра по условию (в 0 или 1), а условие выглядит как сравнение src1 и src2, при этом src1 — регистр, а src2 — регистр или константа. Условия — стандартный набор типа "равно/больше/меньше" с вариантами со знаком или без. Такая же ситуация на Alpha. При разработке обоих тут следовали идее, что флаговый регистр (PSW) — узкое место и не надо завязываться на него для всех операций.
Для примера возьмём MIPS код в libgmp (mpn/mips32/add_n.asm, сложение длинных натуральных), комментирую:
C INPUT PARAMETERS
C res_ptr $4
C s1_ptr $5
C s2_ptr $6
C size $7
ASM_START()
PROLOGUE(mpn_add_n)
lw $10,0($5); младшее слово первого аргумента
lw $11,0($6); младшее слово второго аргумента
addiu $7,$7,-1
and $9,$7,4-1 C number of limbs in first loop
beq $9,$0,.L0 C if multiple of 4 limbs, skip first loop
move $2,$0; инициализация переноса в 0
subu $7,$7,$9
; входим со значениями в текущей позиции, уже загруженными в $10 и $11
.Loop0: addiu $9,$9,-1; декремент переменной цикла - позиции в массиве
lw $12,4($5); прочли следующее слово из 1-го аргумента
addu $11,$11,$2; прибавили перенос к текущему значению из 2-го аргумента
lw $13,4($6); прочли следующее слово из 2-го аргумента
sltu $8,$11,$2; запись результата сравнения на меньше без знака,
; то есть $8 получит 1 только если перенос с предыдущего цикла
; дал беззнаковое переполнение, иначе ставится в 0
addu $11,$10,$11; складываем два входных значения текущей позиции
sltu $2,$11,$10; запись результата сравнения на меньше без знака, аналогично,
; запись факта переполнения
sw $11,0($4); выгрузка суммы на текущем шаге в массив результата
or $2,$2,$8; объединение двух признаков переноса в один логическим "или"
addiu $5,$5,4; инкремент указателя 1-го аргумента на 1 слово
addiu $6,$6,4; инкремент указателя 2-го аргумента на 1 слово
move $10,$12; следующее прочтённое слово из 1-го аргумента сделать текущим
move $11,$13; следующее прочтённое слово из 2-го аргумента сделать текущим
bne $9,$0,.Loop0
addiu $4,$4,4
Дальнейший код (ещё один цикл, более крупными прыжками) пропустил. Я надеюсь, по комментариям видно, как получается сумма и вычисление переноса. На C один шаг такого суммирования внутри цикла можно было бы записать так:
unsigned /* out_carry */
sum_step(unsigned *result_word,
unsigned in_carry, unsigned input1, unsigned input2)
{
unsigned tmp, ovf1, ovf2;
tmp = input2 + in_carry;
ovf2 = (tmp < in_carry);
tmp += input1;
ovf1 = (tmp < input1);
*result_word = tmp;
return ovf1 | ovf2;
}
Громоздко? Да. Дольше варианта, когда явно ставится и анализируется флаг переноса?
Да, если считать на количество команд.
Но: работает? Да, работает, корректно. И в описанном сишном варианте гарантированно работает независимо от архитектуры и без ассемблерных вставок (которые, конечно, на каком-нибудь x86 ускорят итерацию в несколько раз).
Аналогичные проблемы, но несколько более облегчённые, были на S/360 (до введения в S/390 команд типа ALC), несмотря на то, что это CISC. Там надо было точно так же сделать два сложения, от обоих сохранить выходной бит 1 поля CC и сделать логический OR для получения признака переноса на следующий цикл. По сравнению с этим привычный тебе формат (появившийся впервые, насколько я смог раскопать, в PDP-11), это уже позднее сверхудачное нововведение.
M> контроллеры всякие — все, что видел, тоже с флаговым регистром в том или ином виде. Поведение несколько отличается, но это как раз повод просто упрятать конкретную реализацию в стд библиотеку. Просто стандарт обязывал бы производителя платформы поддерживать еще пару-тройку функций, вот и все.
Как видишь, нет, не с флаговым регистром. С другой стороны, я полностью согласен с тем, что функцию, идентичную по поведению такой sum_step, имеет смысл подумать внести в стандарт, хотя бы в необязательные, но в расширения со стандартизованным интерфейсом. Ну а на каком-нибудь x86 она превратится в банальную цепочку из shr + adc + setc, с возможностью устранения крайних элементов в ней, если между вызовами CF не меняется.
Операции с числами со знаком тоже легко делаются через такое же, просто старший бит старшего слова значения определяет знак

Но обрати внимание, что тот же GMP сделал mpn нижним уровнем, а знаки чисел уже над ним. При "реально" длинной арифметике так эффективнее.
M>>>А что, в Java такие вещи можно отслеживать? Или там исключениями будут кидаться?
W>>А там виртуальная машина жёстко зафиксирована. Неважно какой у тебя процессор, архитектура, интерпретатор или jit-компилятор, — изволь эмулировать то что написано. Например, тот же long в java с точки зрения программы всегда 64-битный и в two's complement представлении — это гарантируется. Если процессор так не умеет, то реализация обязана, например, программной эмуляцией добиться соответствующего поведения.
M>Ну, это да. Но как там с переполнениями?
Не проверяются, там арифметика гарантированно кольцевая по модулю (2**32 или 2**64).
Зато ты можешь проверить результаты как минимум сложения или вычитания практически таким же способом, как я показал: если c = a+b, то проверка на переполнение делается через:
((b >= 0) ? (c < a) : (c > a))
Для GCC аналогичное можно получить, скомпилировав с -fwrapv (для целого входного файла для версий до 4.4, с грануляцией до функции, начиная с 4.4). Clang знает тот же -fwrapv для входного файла.
Вот с проверкой умножения, конечно, сложнее всего. Обратное деление слишком дорого. Если нет ассемблерной поддержки, лучше брать входные значения размером в половину доступного слова. Будет дольше, зато надёжно.