Поясніть цей фрагмент, який знаходить максимум два цілих числа, не використовуючи if-else або будь-який інший оператор порівняння?


78

Знайдіть максимум два числа. Ви не повинні використовувати if-else або будь-який інший оператор порівняння. Я знайшов це запитання на дошці оголошень в Інтернеті, тому я подумав, що повинен поставити в StackOverflow

ПРИКЛАД Вхід: 5, 10 Вихід: 10

Я знайшов це рішення, хтось може допомогти мені зрозуміти ці рядки коду


1
Я хотів би поглянути на обман знакових бітів, оскільки це в основному те саме, що робить процесор <.
starblue

7
Що стосується C ++, принаймні, з 5.8.3, обговорюючи E1 >> E2: "Якщо E1 має підписаний тип і від'ємне значення, отримане значення визначається реалізацією.", Тому "c >> 31" може або не може зміщуватися знаковий біт від найменшого до найменш значущого біта ....
Тоні Делрой

1
і не можна сказати, що біт 31 і так є знаковим бітом.
Antti Haapala

3
У будь-якому випадку, це питання слід закрити, тому що ніхто не читає текст запитання, а також тег C.
Antti Haapala

Просто тому, що ніхто інший цього не відзначав. Це відомий хакер, який тут , у замаскованому вигляді.
kaartic

Відповіді:


121

Давайте розберемо це. Цей перший рядок здається прямим - він зберігає різницю aта b. Це значення є негативним, якщо a < bі в іншому випадку невід’ємним. Тут насправді є помилка - якщо різниця чисел aі bтака велика, що не може поміститися в ціле число, це призведе до невизначеної поведінки - ой! Тож припустимо, що тут цього не відбувається.

У наступному рядку, який є

ідея полягає в тому, щоб перевірити, чи значення cвід’ємне. Практично на всіх сучасних комп'ютерах числа зберігаються у форматі, який називається доповненням двох, в якому найвищий біт числа дорівнює 0, якщо число додатне, і 1, якщо число від'ємне. Більше того, більшість ints мають 32 біти. (c >> 31)зміщує число на 31 біт, залишаючи найвищий біт числа на місці для найнижчого біта. Наступний крок, коли беремо це число та додаємо до нього 1 (двійкове представлення якого дорівнює 0 скрізь, крім останнього біта), стирає всі старші біти і просто дає вам найнижчий біт. Оскільки найнижчий біт c >> 31- це найвищий біт c, це читає найвищий біт cяк 0 або 1. Оскільки найвищий біт дорівнює 1, якщо cдорівнює 1, це спосіб перевірити, чиcє негативним (1) або позитивним (0). Поєднуючи це міркування з вищезазначеним, kце 1, якщо a < bі 0, інакше.

Останній крок - це зробити:

Якщо a < b, тоді k == 1і k * c = c = a - b, і так

Що є правильним максимумом, оскільки a < b. В іншому випадку, якщо a >= b, то k == 0і

Що також є правильним макс.


7
Все це припускає відсутність переповнення.
Пітер Тейлор

4
@templatetypedef, припустимо, a = 0x80000000 (мінімальне значення int) і b = 1. Що таке c?
Пітер Тейлор,

4
@Peter Taylor- Це хороший момент. Зауважте, що я не придумав цієї відповіді; Я просто пояснював код OP. :-) Але ви праві, що це зламається, якщо цифри будуть занадто далекі.
templatetypedef

1
@templatetypedef, я знаю: мені вдалося написати дуже подібну відповідь, коли ви розмістили свою. Я просто вказував на користь OP.
Пітер Тейлор,

3
@Ani, верхній біт зміщується в кожну позицію, через яку він проходить. Альтернативою будеmax = a + (c >> 31) * c
Пітер Тейлор

28

Ось і ми: (a + b) / 2 + |a - b| / 2


1
Чи знаєте ви математичну логіку цього?
Senthil Kumaran

6
@ mike.did: Чи можете ви зробити | a - b | без будь-яких умовних умов?
templatetypedef

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

5
-1 Неправильно на ints, наприклад (3 + 2) / 2 + |3 - 2| / 2 = 2 + 0 = 2 != 3.
starblue

4
@starblue: ((3 + 2) + | 3-2 |) / 2 = 3 Виглядає сюди звідси.
Michael Foukarakis

21

Використовуйте побітові хаки

Якщо ви знаєте, що INT_MIN <= x - y <= INT_MAX,тоді ви можете скористатися наступним, що швидше, оскільки (x - y)потрібно оцінити лише один раз.

Джерело: Bit Twiddling Hacks Шона Ерона Андерсона


14
зауважте, що перша пропозиція порушує обмеження "відсутність оператора порівняння".
Matthieu M.

12

Це базується на тій же техніці, що і рішення mike.dld , але тут менш очевидно, що я роблю. Операція "abs" виглядає так, ніби ви порівнюєте знак чогось, але я тут користуюся тим фактом, що sqrt () завжди повертає вам позитивний квадратний корінь, тому я квадратикую (ab) виписую його повністю, а потім квадрат- вкорінюючи його знову, додаючи a + b і ділячи на 2.

Ви побачите, що це завжди працює: наприклад, приклад користувача 10 і 5 ви отримуєте sqrt (100 + 25 - 100) = 5, потім додайте 10 і 5, ви отримаєте 20, а ділення на 2 - 10.

Якщо ми використовуємо 9 і 11 як наші числа, ми отримаємо (sqrt (121 + 81 - 198) + 11 + 9) / 2 = (sqrt (4) + 20) / 2 = 22/2 = 11


a * a легко
переллється

8

Найпростіша відповідь - нижче.


6

Це рішення дозволяє уникнути множення. m буде або 0x00000000, або 0xffffffff


4

Використовуючи ідею переміщення для вилучення знаку, розміщеного іншими, ось ще один спосіб:

Це виштовхує два числа в масив з максимальним числом, заданим елементом масиву, індекс якого є знаковим бітом різниці між двома числами.

Зверніть увагу, що:

  1. Різниця (a - b) може переповнитися.
  2. Якщо цифри без знака, а >>оператор посилається на логічний зсув вправо, & 1це непотрібно.

4

Ось як я думаю, що я б зробив цю роботу. Це не так читабельно, як вам може сподобатися, але коли ви починаєте з "як зробити X, не використовуючи очевидний спосіб X, ви повинні начебто цього очікувати. Теоретично це також відмовляється від певної мобільності, але ви" мені доведеться знайти досить незвичну систему, щоб побачити проблему.

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


2

Ось що роблять ці рядки:

c ab. якщо c від’ємне, a <b.

k - це 32-й біт c, який є знаковим бітом c (припускаючи 32-бітові цілі числа. Якщо це зробити на платформі з 64-бітними цілими числами, цей код не буде працювати). Він зміщується на 31 біт вправо, щоб видалити крайній правий 31 біт, залишаючи знаковий біт у самому правому місці, а потім, поєднуючи його з 1, видаляє всі біти ліворуч (які заповнюються 1, якщо c негативне). Отже, k буде 1, якщо c від’ємне та 0, якщо c додатне.

Тоді max = a - k * c. Якщо c дорівнює 0, це означає a> = b, тому max дорівнює a - 0 * c = a. Якщо c дорівнює 1, це означає, що a <b, а потім a - 1 * c = a - (a - b) = a - a + b = b.

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

Слід також додати, що всі ці рішення припускають, що ці два числа є цілими числами. Якщо вони плаваючі, подвійні або щось більш складне (BigInts, Rational numbers тощо), тоді вам дійсно доведеться використовувати оператор порівняння. Біт-трюки зазвичай не підходять для тих.


1

функція getMax () без будь-якої логічної операції-


Пояснення:

Давайте розіб'ємо `` макс '' на шматки,

Тож функція повинна виглядати так -

Зараз,

У цілочисловому додатному числі перший біт (знаковий біт) дорівнює 0 ; у мінусі - 1 . Зміщуючи біти вправо (>>), перший біт може бути захоплений.

Під час зрушення вправо порожній простір заповнюється знаковим бітом. Отже, 01110001 >> 2 = 00011100 , тоді як 10110001 >> 2 = 11101100 .

У результаті для 8-бітового зсуву числа 7 біт або дасть - 1 1 1 1 1 1 1 [0 або 1] для негативного, або 0 0 0 0 0 0 0 [0 або 1] для позитивного.

Тепер, якщо операція АБО виконується з 00000001 (= 1) , негативне число дає - 11111111 (= -1) , а додатне - 00000001 (= 1) .

Так,

Нарешті,


Інший спосіб -


0

статичний int mymax (int a, int b)

Якщо b> a тоді (ab) буде від'ємним, знак поверне -1, додавши 1, ми отримаємо індекс 0, який є b, якщо b = a, тоді ab буде 0, +1 дасть 1 індекс, так що це не має значення якщо ми повертаємо a або b, коли a> b, тоді ab буде додатним, а знак поверне 1, додаючи 1, ми отримуємо індекс 2, де зберігається a.


0

0

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

Опис

  • Перше, що функція приймає аргументи як double і має тип return як double. Причиною цього є створення єдиної функції, яка може знайти максимум для всіх типів. Коли наводяться цілі числа, або одне є цілим, а інше - з плаваючою комою, тоді також завдяки неявному перетворенню функція може бути використана для знаходження максимуму і для цілих чисел.
  • Основна логіка проста, припустимо, у нас є два числа a & b, якщо ab> 0 (тобто різниця додатна), тоді a максимум, якщо ab == 0, то обидва рівні, а якщо ab <0 (тобто diff - ve) b максимальна.
  • Біт знаку зберігається у пам'яті як найзначніший біт (MSB). Якщо MSB дорівнює 1 і навпаки. Щоб перевірити, чи MSB дорівнює 1 або 0, ми переміщуємо MSB в положення LSB і побітовим & з 1, якщо результат 1, то число -ve ще ні. становить + ve. Цей результат отримується за твердженням:

    int_diff >> (sizeof (int) * 8 - 1) & 1

Тут, щоб отримати знаковий біт від MSB до LSB, ми вправо переносимо його на біти k-1 (де k - кількість бітів, необхідна для збереження цілого числа в пам'яті, що залежить від типу системи). Тут k = sizeof (int) * 8, оскільки sizeof () дає кількість байтів, необхідну для збереження цілого числа, щоб отримати no. бітів, ми множимо його на 8. Після правильного зсуву застосовуємо побітове & з 1, щоб отримати результат.

  • Тепер після отримання результату (припустимо його як r) як 1 (для -ve diff) і 0 (для + ve diff) ми множимо результат на різницю двох чисел, логіка подається наступним чином:

    1. якщо a> b, то ab> 0, тобто, є + ve, то результат дорівнює 0 (тобто r = 0). Отже, a- (ab) * r => a- (ab) * 0, що дає "a" як максимум.
    2. якщо a <b, то ab <0, тобто, є -ve, то результат дорівнює 1 (тобто r = 1). Отже, a- (ab) * r => a- (ab) * 1 => a-a + b => b, що дає 'b' як максимум.
  • Тепер залишилось два пункти: 1. використання циклу while і 2. чому я використав змінну 'int_diff' як ціле число. Щоб правильно відповісти на них, ми повинні зрозуміти деякі моменти:

    1. Значення плаваючого типу не можна використовувати як операнд для побітових операторів.
    2. Через вищезазначену причину нам потрібно отримати значення у цілочисельному значенні, щоб отримати знак різниці за допомогою побітових операторів. Ці два пункти описують потребу змінної 'int_diff' як цілочисельний тип.
    3. Тепер, скажімо, ми знаходимо різницю у змінній 'diff', тепер є 3 можливості для значень 'diff' незалежно від знака цих значень. (а). | різниця |> = 1, (b). 0 <| різниця | <1, (c). | різниця | == 0.
    4. Коли ми призначаємо подвійне значення цілочисельній змінній, десяткова частина втрачається.
    5. Для випадку (а) значення 'int_diff'> 0 (тобто 1,2, ...). Для інших двох випадків int_diff = 0.
    6. Умова (temp_diff-int_diff) || 0.0 перевіряє, якщо diff == 0, тож обидва числа рівні.
    7. Якщо diff! = 0, то ми перевіряємо, чи int_diff | 0 відповідає дійсності, тобто case (b) відповідає дійсності
    8. У циклі while ми намагаємось отримати значення int_diff як ненульове, щоб значення int_diff також отримувало знак diff.

0

Ось пара дрібниць методів щоб отримати максимум двох інтегральних значень:

Спосіб 1

Пояснення:

  • (a - b) >> SIGN_BIT_SHIFT - Якщо a > bтоді a - bпозитивне, то знаковий біт є 0, а маска є 0x00.00. В іншому випадку, a < bтак a - bнегативно, знаковий біт1 і після зсуву, ми отримуємо маску0xFF..FF
  • (a & ~ mask) - Якщо маска є 0xFF..FF, тоді ~maskє 0x00..00і тоді це значення є 0. В іншому випадку ~maskє0xFF..FF і значення єa
  • (b & mask) - Якщо маска є 0xFF..FF, то це значення є b. В іншому випадку maskє 0x00..00і значення є 0.

Нарешті:

  • Якщо a >= bтоді a - bпозитивне, ми отримуємоmax = a | 0 = a
  • Якщо a < bтоді a - bнегативне, ми отримуємоmax = 0 | b = b

Спосіб 2

Пояснення:

  • Пояснення маски те саме, що і для методу 1 . Якщо a > bмаска є 0x00..00, інакше маска є0xFF..FF
  • Якщо маска є 0x00..00, то (a ^ b) & maskє0x00..00
  • Якщо маска є 0xFF..FF, то (a ^ b) & maskєa ^ b

Нарешті:

  • Якщо a >= bми отримуємоa ^ 0x00..00 = a
  • Якщо a < bми отримуємоa ^ a ^ b = b

0

// У C # ви можете використовувати математичну бібліотеку для виконання функції min або max

за допомогою системи;

клас NumberComparator {

}


Будь уважний: запитання OP позначено тегом [c], а не [c #]
Алекс Ю

0

Немає логічних операторів, немає libs (JS)


-2

Логіку, описану в задачі, можна пояснити, як якщо 1-е число менше, то 0 буде віднято, інакше різниця буде віднята з 1-го числа, щоб отримати 2-е число. Я знайшов ще одне математичне рішення, яке, на мою думку, простіше зрозуміти це поняття.

Розглядаючи a та b як задані числа

Знову ж таки, ідея полягає в тому, щоб знайти k, яке в'яне 0 або 1, і помножити його на різницю двох чисел. І нарешті це число слід відняти від 1-го числа, щоб отримати менше з двох чисел. PS це рішення не вдасться, якщо друге число дорівнює нулю


-3

Є один шлях

і один


-3

-4

Думаю, ми можемо просто помножити числа на їх побітове порівняння, наприклад:

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