Який ваш улюблений розрядний прийом? [зачинено]


14

Кілька днів тому член StackExchange Anto поцікавився, чи дійсно використовує для операторів біт- розум . Я заявив, що переміщення було швидше, ніж множення та ділення цілих чисел на два рівні. Член StackExchange Daemin протидіяв, заявивши, що зсув правої форми представляє проблеми з негативними числами.

На той момент я ніколи не задумувався над використанням операторів зсуву з підписаними цілими числами. Я в основному використовував цю техніку в розробці програмного забезпечення низького рівня; тому я завжди використовував непідписані цілі числа. C виконує логічні зрушення на непідписані цілі числа. При виконанні логічного зсуву справа не звертає уваги на бітовий знак. Відпущені біти заповнюються нулями. Однак C виконує операцію з арифметичним зсувом при зміщенні підписаного цілого числа справа. Випущені біти заповнюються бітом знака. Ця різниця призводить до того, що негативне значення округлюється до нескінченності, а не до усікання до нуля, що є іншою поведінкою, ніж підписане ціле ділення.

Кілька хвилин думки призвели до рішення першого порядку. Рішення умовно перетворює негативні значення в позитивні значення перед зміщенням. Значення умовно перетворюється назад в його негативну форму після виконання операції зсуву.

int a = -5;
int n = 1;

int negative = q < 0; 

a = negative ? -a : a; 
a >>= n; 
a = negative ? -a : a; 

Проблема цього рішення полягає в тому, що твердження умовного призначення зазвичай переводяться щонайменше на одну інструкцію стрибків, а інструкції стрибків можуть бути дорогими для процесорів, які не декодують обидва контури інструкцій. Повторне завантаження інструментального конвеєра вдвічі дає хорошу вм’ятину в будь-якому прирості продуктивності, отриманому при переході на ділення.

Зі сказаним вище я прокинувся в суботу з відповіддю на проблему умовного призначення. Проблема округлення, яку ми відчуваємо при виконанні операції арифметичного зсуву, виникає лише при роботі з представленням комплементу двох. Це не відбувається при представленні доповнення. Рішення проблеми передбачає перетворення значення доповнення двох у значення доповнення, перш ніж виконувати операцію зсуву. Тоді нам доведеться перетворити значення додатка назад у значення доповнення двох. Дивно, але ми можемо виконувати цей набір операцій без умовного перетворення негативних значень перед виконанням операції зсуву.

int a = -5;
int n = 1;

register int sign = (a >> INT_SIZE_MINUS_1) & 1

a = (a - sign) >> n + sign;   

Від'ємне значення доповнення двох перетворюється на негативне значення доповнення, яке вичиняється. Зі зворотного боку негативне значення доповнення одного перетворюється на негативне значення доповнення двох, додаючи одне. Код, перерахований вище, працює тому, що біт знаків використовується для перетворення з доповнення двох у доповнення і навпаки . Лише негативні значення встановлюватимуть біти знаків; отже, знак змінної буде дорівнює нулю, коли a додатний.

З урахуванням сказаного, чи можете ви придумати інші хитрі хакерства, як той, що був вище, які зробили це у вашій сумці хитрощів? Який ваш улюблений біт-мудрий хак? Я завжди шукаю нові біт-розумні хаки, орієнтовані на ефективність.


3
Це питання та назва вашого облікового запису - світ знову має сенс ...
JK

+1 Цікаве запитання як моє продовження, а також інакше;)
Анто

Я також зробив кілька швидких розрахунків паритету один раз. Паритет - це біль, тому що традиційно він включає петлі та підрахунок, якщо встановлено трохи, для чого потрібно багато стрибків. Паритет можна обчислити за допомогою shift та XOR, тоді купа тих, що робляться одна за одною, уникає циклів і стрибків.
quick_now

2
Чи знаєте ви, що ціла книга про ці методи? - Hackers Delight amazon.com/Hackers-Delight-Henry-S-Warren/dp/0201914654
nikie

Так, є веб-сайт, присвячений також операціям біт. Я забуду URL-адресу, але Google досить швидко вимкне її.
quick_now

Відповіді:


23

Я люблю хакерство Госпера (HAKMEM # 175), дуже хитрий спосіб взяти номер і отримати наступне число з такою ж кількістю бітів. Наприклад, корисно, наприклад, для створення комбінацій kелементів n:

int set = (1 << k) - 1;
int limit = (1 << n);
while (set < limit) {
    doStuff(set);

    // Gosper's hack:
    int c = set & -set;
    int r = set + c;
    set = (((r^set) >>> 2) / c) | r;
}

7
+1. Але відтепер у мене будуть кошмари щодо пошуку цього під час сесії налагодження без коментарів.
nikie

@nikie, muahahahaha! (Я схильний використовувати це для таких речей, як проблеми Project Euler - моя щоденна робота не передбачає особливої ​​комбінаторики).
Пітер Тейлор

7

Метод швидкого зворотного квадратного кореня використовує найбільш химерні методи бітового рівня для обчислення оберненого квадратного кореня, який я коли-небудь бачив:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking [sic]
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck? [sic]
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //    y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

Швидкий sqrt також дивовижний. Carmack виглядає одним з найбільших кодерів.
БенджамінB

У Вікіпедії є ще старіші джерела, наприклад, понад3d.com
content/

0

Поділ на 3 - не вдаючись до виклику бібліотеки, що працює.

Виявляється, поділ на 3 (завдяки натяку на Stack Overflow) можна приблизно оцінити як:

X / 3 = [(x / 4) + (x / 12)]

І X / 12 є (x / 4) / 3. Тут раптом з’являється елемент рекурсії.

Також виявляється, що якщо ви обмежите діапазон чисел, в які граєте, ви можете обмежити кількість необхідних ітерацій.

Таким чином, для непідписаних цілих чисел <2000, наступним є швидкий і простий / 3 алгоритм. (Для більшої кількості просто додайте більше кроків). Компілятори оптимізують хек з цього, щоб він був швидким і малим:

статичний неподписаний короткий FastDivide3 (const неподписаний короткий аргумент)
{
  непідписаний короткий RunningSum;
  неподписаний короткий FractionalTwelth;

  RunningSum = arg >> 2;

  FractionalTwelth = RunningSum >> 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  // Більше повторень вищевказаних 2 рядків для більшої точності

  повернути RunningSum;
}

1
Звичайно, це актуально лише для дуже незрозумілих мікроконтролерів. Будь-який реальний процесор, створений за останні два десятиліття, не потребує бібліотеки часу для цілого поділу.
MSalters

1
Ну звичайно, але невеликі мікрофони без апаратного множника насправді дуже поширені. А якщо ви працюєте на вбудованій землі і хочете зекономити 0,10 долара на кожному проданому мільйоні продуктів, то вам краще знати деякі брудні хитрощі. Ці заощаджені гроші = додатковий прибуток, що робить вашого шефа дуже щасливим.
quick_now

Ну, брудно ... це просто множення на .0101010101(приблизно 1/3). Підказка: ви також можете помножити на .000100010001і 101(що займає всього 3-х розрядні зміни, але має кращий наближення.010101010101
MSalters

Як я можу це зробити лише з цілими числами і без плаваючої точки?
quick_now

1
Побіт, x * 101 = x + x << 2. Аналогічно x * 0.000100010001 є x >> 4 + x >> 8 + x >> 12.
MSalters

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.