что можно сказать по поводу такого алгоритма
a -= b;
b += a;
a = b - a;
против обычной замены?
Здравствуйте, фонарь, Вы писали:
Ф>что можно сказать по поводу такого алгоритма
Ф>Ф>a -= b;
Ф>b += a;
Ф>a = b - a;
Ф>
Ф>против обычной замены?
как насчет переполнения?
Regards,
Vasili
Здравствуйте, фонарь, Вы писали:
Ф>что можно сказать по поводу такого алгоритма
Ф>Ф>a -= b;
Ф>b += a;
Ф>a = b - a;
Ф>
Ф>против обычной замены?
А самому проверить слабо...
У меня, например, твой вариант в 2.5 раза медленнее варианта с промежуточной переменной.
Плюс что за эффективность тебя интересует?
По памяти твой вариант эффективнее, а по скорости явно нет
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, фонарь, Вы писали:
Ф>>что можно сказать по поводу такого алгоритма
Ф>>Ф>>a -= b;
Ф>>b += a;
Ф>>a = b - a;
Ф>>
Ф>>против обычной замены?
А>А самому проверить слабо...
А>У меня, например, твой вариант в 2.5 раза медленнее варианта с промежуточной переменной.
А>Плюс что за эффективность тебя интересует?
А>По памяти твой вариант эффективнее, а по скорости явно нет
Все равно в варианте с промежуточной переменной компилятор применит регистровую оптимизацию, записи в память не будет (что и показывает тест). Так что в таких трюках нет надобности.
Здравствуйте, фонарь, Вы писали:
Ф>что можно сказать по поводу такого алгоритма
Ф>Ф>a -= b;
Ф>b += a;
Ф>a = b - a;
Ф>
Ф>против обычной замены?
Поскольку у Intel x86 нет операций память-память, за исключением строковых (movsb и т.п.), то по-любому возникнет потребность в промежуточном регистре.
extern void swap1(unsigned int& a, unsigned int& b)
{
unsigned int x = a;
a = b;
b = x;
// реконструкция ассемблерного кода (*1*)
unsigned int x = a; // *2*
unsigned int y = b;
a = y;
b = x;
}
extern void swap2(unsigned int& a, unsigned int& b)
{
a -= b;
b += a;
a -= b;
// реконструкция
unsigned int x = a;
unsigned int y = b;
x -= y;
a = x; // *3*
y += x;
b = y;
x -= y;
a = x;
}
extern unsigned int p = 100, q = 200;
int main()
{
swap1(p,q);
swap2(p,q);
return 0;
}
*1* — получить ассемблерный код можно, запустив cl -Ox -Fa test.cpp
(-Ox — оптимизировать все).
Реконструировал вручную. Все-таки С выразительнее ассемблера
*2* — x,y — регистры. В ассемблерном листинге видно, что требуются 4 регистра: адрес a, адрес b (переданные параметры), x, y. Поскольку конвенция предполагает несохранение только трех — eax,ecx,edx, то четвертый — esi — сохраняется в стеке (push esi / pop esi).
*3* — не знаю, зачем пишется промежуточное значение a. Без этой строки вполне можно обойтись. Тем не менее...
Итого
* swap1 имеет 2 чтения и 2 записи в память.
* swap2 — 2 чтения, 3(2) записи и
3 арифметических операции регистр-регистр.
* Если бы swap2 использовал арифметику память-регистр, то ему пришлось бы неоднократно читать значения из памяти (что там получилось?). Естественно, это было бы еще медленнее.
Здравствуйте, kepo4ka, Вы писали:
K>Здравствуйте, Кодт, вы тут много всего написали:
K>Насколько я понимаю применять такой алгоритм в принципе не стоит. Равно как и оценивать его эффективность.
K>Если переменная a и b, не целочисленные. И a намного больше b => a-=b даст нам a. И весь алгоритм посыпался.
Потому что плавающие числа
конечной разрядности не образуют группу ни по сложению, ни по умножению.
А фиксированные — образуют группу (целые — образуют даже кольцо — вычетов по модулю 2^n).
А еще они образуют группу по битовому сложению (xor). Отсюда — такая формула
a = a (-) b;
b = a (+) b;
a = a (-) b;
где (+), (-) это операция над группой и ее противоположность.
Общедоступны
3 варианта:
+|-,
-|+ (sic! обратите внимание на расположение операндов -- здесь нет коммутативности) и
^|^.
Абстрактная алгебра рулит!
Здравствуйте, Кодт, Вы писали:
К>А еще они образуют группу по битовому сложению (xor). Отсюда — такая формула
К>К>a = a (-) b;
К>b = a (+) b;
К>a = a (-) b;
К>
К>Абстрактная алгебра рулит!
фнтастика! как раз только хотел вынести на суд такой вариант
a ^= b;
b ^= a;
a ^= b;
тут уж эффективность так эффективность