Как стать автором
Обновить

Комментарии 104

И код Хэмминга. Хотя в википедии в алгоритмах кодирования/декодирования что-то с матрицами наворочено, на самом деле идея простая — xor номеров единичных битов.
Да это проще Рида-Соломона, но более расточительно. Вот пример кодирования 8бит в 12бит способное восстановить один ошибочный бит.
/*
Hamming encode/decode
                         MP3 0  0  0  0  0  0  0  1  1  1  1  0
                         MP2 0  0  0  1  1  1  1  0  0  0  0  1
                         MP1 0  1  1  0  0  1  1  0  0  1  1  0
                         MP0 1  0  1  0  1  0  1  0  1  0  1  0
D0 D1 D2 D3 D4 D5 D6 D7      C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB
B0 B1 B2 B3 B4 B5 B6 B7 <==> P0 P1 B0 P2 B1 B2 B3 P3 B4 B5 B6 B7 
                                   **    ** ** **    ** ** ** **
data_byte                    hamming_code (L=C0..C7, H=C8..CB)

P0=C0 xor C2 xor C4 xor C6 xor C8 xor C10
P1=(C1 xor C2) xor (C5 xor C6) xor (C9 xor C10)
P2=(C3 xor C4 xor C5 xor C6) xor (CB)
P3=(C7 xor C8 xor C9 xor CA)

P3_0 -> 0-no_error, 1..12 invalid bit number

Hamming encode D7_0  ==> C11_0
Hamming decode C11_0 ==> D7_0
*/
int hamming_parity(int x) {
  int i,p;
  for(p=0, i=1;i<=12;++i) { if (x&1) p^=i; x>>=1; }
  return p;
}

int hamming_encode(int x) {
  int p,r;
  r=((x<<4)&0x0F00)|((x<<3)&0x0070); if (x&1) r|=4; // encode
  p=hamming_parity(r); r|=p&3; if (p&4) r|=0x0008; if (p&8) r|=0x0080; // set parity
  return r;
}

int hamming_decode(int x) {
  int p,r;
  p=hamming_parity(x); if (p) x^=1<<(p-1); // correcting error
  r=((x>>4)&0xF0)|((x>>3)&0x0E); if (x&4) r|=1; // decode
  return r;
}
Использовать XOR для перемены значений местами на практике не стоит: минус — код работает в 3-4 раза медленнее чем с использованием доп.переменной, минус — трудности с читабельностью кода, минус — работает только базовыми числовыми типами, минус — доп. память все-таки используется, правда неявно — регистры или стек
Медленнее? Да ладно.

xor eax, ebx
xor ebx, eax
xor eax, ebx

vs
mov ecx, eax
mov eax, ebx
mov ebx, ecx

А там какого-нибудь XCHG разве нет?
блин.
я и забыл про него.
вот что значит долгое отсутствие практики :(
но мне по старой памяти всё равно кажется, что арифметика на х86 в разы шустрее передачи значений. могу и ошибаться.
Как по мне, такие трюки нужно применять только в очень специальных ситуациях, когда ресурсы ну совсем кончились. Типа бутлоадера в микроконтроллере, когда каждый байт на вес золота. А в нормальной жизни за такое нужно бить по голове на кодревью.
Ну и вообще, я бы данный пост назвал «питонисты познают то, что очевидно для сишников» )
А я так тихо пробурчу в тапок, что если человека бьют по голове
за то, что он считает каждый байт на вес золота (и каждый такт заодно),
то человек должен тихо уйти оттуда — и пусть они там поубивают
друг друга.
Время программиста уже давно дороже, чем время процессора.
Зависит от процессора. Есть такие, где иначе не будет работать.

Это справедливо если код будет выполнятся немного и у себя. А если это миллиарды повторений на всех компьютерах мира?

Сколько компаний пишут такой код, который выполняется «на всех компьютерах мира»?
Подход к разработке, целесообразный в этих немногих компаниях, нецелесообразен во всех остальных.
А если программа выполняется на клиенте, то процессорное время и вовсе получается «бесплатным», платит пользователь.
Хотя есть, конечно, и своя выгода в оптимизации — например, охват аудитории, которая сможет пользоваться программой с комфортом на своём железе.
Я специально написал что есть ситуации, когда это можно и нужно делать. И про бутлоадер не зря написал, потому что это буквально из моего опыта. Бутлоадер ограничен 8К, а первый билд получился 13К. После чего я примерно месяц делал так чтоб оно влезло, используя все возможные трюки, некоторые из них ещё на Спектруме использовал.
p.s. Поставить другой контроллер было уже не вариантом, так что пришлось корячиться.
p.p.s. И я там написал много комментариев на тему почему это сделано так и сколько это экономит байт, хоть вроде и не очевидно что экономит.
Вспомнился баянистый рассказ про этот самый последний недостающий байт: habr.com/ru/post/27055
За такие трюки однозначно можно бить по голове только в том случае, если они подробно не расписаны тут же в комментариях.
Я регулярно пользуюсь XOR — например, для проверки, не изменилось ли отслеживаемое значение. Такой вот волшебник XOR, несущий свет XOR в массы :)
В качестве оператора «не равно»?
Да.
передачи может и на случиться. «ок, положим, что регистр R21 теперь называется не EAX, а ECX» — переименование регистров. Более того, может произойти такой фокус, как «ок, положим, что переменная _I0001 до 112й строки называется 'x', а начиная со 113 — 'y'», еще на этапе компиляции.

Оптимизировать руками это можно после того, как удалось доказать, что результат оптимизации положительный и больше погрешности измерений.
If a memory operand is referenced, the processor’s locking protocol is automatically implemented for the duration of the exchange operation, regardless of the presence or absence of the LOCK prefix or of the value of the IOPL.

Может выйти и медленнее, чем через промежуточный регистр.
При этом надо иметь в виду, что xchg reg,mem работает очень медленно.
А я и не предлагаю его использовать. Напишу через обмен через переменную. Пусть компилятор пусть сам думает что из этого сделать, у него голова большая.
Вот эта вот штука с обменом через XOR имеет настолько узкоспециальное применение, что я вот даже не помню использовал ли это хоть где-нибудь за 30 лет написания программ. Очень может быть что использовал в 90-х в демо на Спекки, когда нужно было считать каждый такт и каждый регистр процессора был на вес золота.
Для x86_64 код
x ^= y;
y ^= x;
x ^= y;


компилируется в
        mov     eax, DWORD PTR [rbp-8]
        xor     DWORD PTR [rbp-4], eax
        mov     eax, DWORD PTR [rbp-4]
        xor     DWORD PTR [rbp-8], eax
        mov     eax, DWORD PTR [rbp-8]
        xor     DWORD PTR [rbp-4], eax


А код
    z=x;
    x=y;
    y=z;


соответственно в
        mov     eax, DWORD PTR [rbp-4]
        mov     DWORD PTR [rbp-12], eax
        mov     eax, DWORD PTR [rbp-8]
        mov     DWORD PTR [rbp-4], eax
        mov     eax, DWORD PTR [rbp-12]
        mov     DWORD PTR [rbp-8], eax


Выигрыш от этой конструкции на языках высокого уровня только моральный (экономия 1 переменной).
Точно, забыл ;) С оптимизацией оба варианта (с xor и с временной переменной) компилируются в один xchg.
ЗЫ: лучше использовать std::swap — это коротко, понятно, и результат тот же.
В общем случае медленнее, потому что при этом отключается возможность переупорядочивания последних двух команд — компилятором и/или процессором. Это важное свойство, дающая свои проценты производительности.

Можно же без XOR и без доп переменных:
a=a+b;//a+b
b=a-b;//a+b-b=a
a=a-b;//a+b-a=b

А если a или b равно какому-нибудь MaxLongint?
Да, у любого метода своя область применения, там область применения a,b=<Max_переменной/2, а у метода с XOR нет.В этом случае он реально лучше… но метод с плюсом/минусом интуитивнее, и главное может быть обобщен для объектов классов гораздо проще чем XOR
область применения a,b=<Max_переменной/2, а у метода с XOR нет

Нет. Границы применимости у методов абсолютно одинаковые.

Нет, при возможности signed overflow компилятор C / C++ может выбрасывать код: lwn.net/Articles/511259

Если можете что-то ксорить, то вы можете работать с этими [наборами бит] как с беззнаковыми целыми. С беззнаковыми целыми signed overflow никак не возникнет.

То что? Для беззнаковых целых даже UB нет.

И что? Пока сами a и b влазят в машинное слово (а иначе задача лишена физического смысла) все работает, независимо от переполнений, возникших при промежуточных вычислениях.
Смотрите: пусть, для простоты понимания кожаными мешками, машинное слово — два десятичных разряда.


a=98
b=99


a = a+b = 98+99 = 197 97 // переполнение при сложении
b = a-b = 97-99 = -2 98 // переполнение при вычитании
a = a-b = 97-98 = -1 99 // переполнение при вычитании


У нас во всех трех операциях возникало переполнение, и тем не менее мы получили правильный результат. Модульная арифметика она такая

Переполнения, в большинстве случаев, лучше избегать. На мой взгляд, такой фокус допустим в случаях типа лишнего байта.
Чем лучше?
Тем, что между моим кодом и физическим процессором есть N слоёв прочего программного обеспечения (включая операционную систему). Соответственно, я не хочу гадать, как будет выполняться мой код, я хочу знать это наверняка. А вдруг на каком-то из слоёв переполнение будет трактоваться, как ошибка?
Каким образом на результат арифметического действия может повлиять ОС или любой другой слой ПО, кроме непосредственно используемого компилятора/интерпретатора?
Я этот пример объясняю так. Допустим, у нас есть по сумке в каждой руке и мы хотим их поменять. Дополнительная переменная — это если мы ставим сумку на пол. Если мы хотим перехватить сумки прямо в руках, не опуская на пол, то, в определённый момент, у нас окажется две сумки в одной руке. Значит, мы ограничены грузоподъёмностью одной руки, обе сумки в сумме не должны её превышать. Если мы ставим сумку на пол, то этого ограничения нет.
Как один из вариантов, мне понравился вариант ваш с арифметикой.
Это свойство позволяет создавать файлы с наперёд заданной контрольной суммой добавлением всего 4х байт к данным
Можете посоветовать, где подробнее про подобные интересные свойства CRC и их полиномов почитать можно?
Тут. Но можете и поэкспериментировать ideone.com/yQHn2c
пример
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

typedef unsigned uint;
uint crc32(const char* data,int size,uint crc=0) {
	crc=~crc;
	for(int i=0;i<size;i++) { 
		crc^=data[i]&255;
		for(int j=0;j<8;j++) crc=crc>>1^0xEDB88320&-(crc&1); 
	}
	return ~crc;
}

int main(int argc, char const *argv[]) {
	enum { n=10 };
	char payload[n+4];
	srandom(time(0)); for(int i=0;i<n;i++) payload[i]=random();
	
	uint payload_crc=crc32(payload,n);
	printf("crc(payload)=%08X\n",payload_crc);

	uint x=0xBEDA; 
	char xd[4]={x,x>>8,x>>16,x>>24};
	uint xd_crc=crc32(xd,4);
	uint suffix=payload_crc^x;
	payload[n  ]=suffix;
	payload[n+1]=suffix>>8;
	payload[n+2]=suffix>>16;
	payload[n+3]=suffix>>24;
	uint crc=crc32(payload,n+4);
	printf("crc(data   )=%08X\n",crc);
	printf("crc(x      )=%08X\n",xd_crc);

	return 0;
}

crc(payload)=116441E8
crc(data   )=A76D55FB
crc(x      )=A76D55FB
Благодарю.
Это представляет академический интерес. В коммерческой разработке я бы не стал так делать. Эти сэкономленные такты и биты явно не стоят времени, которое новый человек в проекте потратит на понимание, что делает это кусок кода.

Это другой человек, увидев такое краткое, быстрое и при этом непонятное ему решение, почувствует свою неполноценность. А это ведь это и есть высшая цель программиста-автора. %))

В чём разница между джуниором и сениором?
Джуниор пишет код, который способен понять только сениор.
Сениор пишет код, который способен понять даже джуниор.
Есть проекты, где сэкономленные такты имеют значение. Взять тот же крипто майнинг или какое-нибудь потоковое шифрование/кодирование.
Но если есть возможность писать с первого взгляда понятный код — надо именно так и делать.
Ну криптографические алгоритмы, в основном, и разрабатываются в академической среде. Там новому человеку в проекте не нужно рассказывать про бинарную логику — он ей думает)
Мы складываем все потенциальные целые числа, а затем вычитаем те, которые встречаются на самом деле. Это решение не такое красивое, потому что придётся обрабатывать переполнения, а также потому что тип элементов должен поддерживать +, — с определёнными свойствами.

Для указанных в условии задачи целых чисел такой тип элементов называется int.
И нет, как ни странно, обрабатывать переполнения никакой необходимости нет.

А мне больше понравилась реализацыя Switch...Case на xor`ах, в каком-то С-компиляторе (не х86) код подглядел. Примерно так, если на «высоком» уровне:
Switch eax
Case A:

Case B:

Case C:
....

То получится следующее:
xor eax, A
jz A_label
xor eax, A^B
jz B_label
xor eax, B^C
jz C_label
xor eax, C^D
jz D_label
jmp Default_Label

Многие компиляторы для x86 делают похожий каскад из sub
А если А, В и С не по порядку, а произвольные константы?
То переупорядочивают: Duff's device встречается на много порядков реже, чем switch, в котором каждый case завершается break, return или throw.

Так приходится делать в кривеньких dsp, с убогой системой команд

Дан массив A из n — 2 целых чисел, находящихся в интервале от 1 до n. Все числа встречаются ровно один раз, за исключением двух отсутствующих чисел. Найти эти два отсутствующих числа.

После применения предыдущего алгоритма у нас останется u ^ v. Что можно сделать с этим значением? Нам каким-то образом нужно извлечь из него значения u и v, но не совсем понятно, как это сделать.

А можно по рабоче-крестьянски пояснить, на пальцах:


n = 5
u^v = 1


какие именно два числа из исходного набора 1,2,3,4,5 были пропущены ?

Был массив A, размером n-2, с возможными числами от 1 до n. И массив потенциальных значений N с числами от 1 до n
Нашли z = u ^ v
Теперь z смотрим побитово, например он (0,0,0,0,1,0,1,0)
Понимаем что в числах u и v есть отличие в пятом бите и в седьмом, берем только первое отличие в пятом бите.

Теперь исходные массивы A и N делим на 2 части, в массивы A1,N1 заносим все числа из A и N в которых пятый бит равен 0, в массивы A2 и N2 заносим все числа из A и N в которых пятый бит равен 1.

Теперь мы точно знаем что u и v находятся/отсутствуют в разных массивах, которые мы создали. Остается прогнать алгоритм для нахождения одного пропущенного/избыточного числа для A1,N1 и A2,N2 по отдельности, и мы найдем u и v.

Ну так найдите. Пример-то простой.

Информации не достаточно, после разбиения будет 2 массива, как ниже написал xi-tauw
[1, 3, 5], [2, 4]
И нужно ещё 2 результата xor после применения алгоритма на них, тогда найдётся точная пара чисел.
На основании u^v=1 строится два множества, отличающиеся тем самым битом. В одном: числа 1,3 и 5, в другом: 2 и 4 (кроме пропущенных). Причем, не умаляя общности, можно считать, что среди 1, 3 и 5 отсутствует u, а среди 2 и 4 отсутствует v.
Задача, фактически, сведена к предыдущей — есть множество известных элементов, из которого один отсутствует. Снова xor'им, чтобы найти u (или v), далее оставшееся.
N = 1, 2, 3, 4, 5
A = 2, 3, 4, 1, 2, 5, 3
XOR(A,N) = 3 ^ 2 = 1 (1 0 0 0 0 0 0 0), отличие в первом бите
Делим
N1 = 2, 4
A1 = 2, 4, 2

N2 = 1, 3, 5
A2 = 3, 1, 5, 3

Считаем
XOR (N1, A1) = 2
XOR (N2, A2) = 3

Нашли искомое

Это все, конечно здорово. Но загадал я 4 и 5 :)

Для повторов 4 и 5 всё то же самое:

N = 1, 2, 3, 4, 5
A = 2, 5, 4, 1, 4, 5, 3
XOR(A,N) = 4 ^ 5 = 1 (1 0 0 0 0 0 0 0), отличие в первом бите
Делим
N1 = 2, 4
A1 = 2, 4, 4

N2 = 1, 3, 5
A2 = 5, 1, 5, 3

Считаем
XOR (N1, A1) = 4
XOR (N2, A2) = 5
Возможно вы неправильно поняли постановку задачи. Требуется найти в уже существующем массиве, а не найти массив в для которого u ^ v дадут какое то число.
Проекция u ^ v необратима однозначно, и даст одинаковое значение для бесконечного числа пар различных u и v.

Не в первый раз вижу комменты с текстом «del». Что это означает на сленге хабра?

«Ждём не дождёмся, когда ТМ реализуют возможность удалять комментарии, запощенные по ошибке.»
Промазал местом ответа, и чтобы не было дубля и замусоривания отредактировал с указанием что текст был удален.

Поиск 2 недостающих чисел с помощью xor уже слишком искусственный и вряд ли имеет смысл. Он требует 2 N доп. памяти и O(N) операций xor и сравнений. Имея столько памяти за столько сравнений, можно более эффективный алгоритм поискать. Навскидку.


N = new int[n];
for (int a: A) N[a]++;

Получили в массиве N количество вхождений каждого элемента множества.

Нет, дополнительной памяти там по-прежнему O(log N).

Каким образом можно создать 4 множества из 2N элементов, не потратив память?

Так нам не нужны сами множества.

Алгоритм такой:
1. Посчитайте u^v (как xor всего множества A xor range).
2. Выберите бит, в котором есть расхождение между u и v (пусть это бит i).
3. Посчитайте xor {x \in A, x_i = 1} xor {x \in range, x_i = 1} — это u
4. Посчитайте xor {x \in A, x_i = 0} xor {x \in range, x_i = 0} — это v

Понял, спасибо

Пройтись по числам 1..n и по массиву A[n] и у каждого элемента проверять включен-ли i-й бит. Если да, то сделать ^ c переменной агрегатором. В конце процедуры в переменной будет содержаться u. Сделав ^ c u^v, найдем v. Никакой дополнительной памяти, множества строятся "на лету" — просто проверяется входит-ли в него данный элемент.

Дан массив A из n — 1 целых чисел, находящихся в интервале от 1 до n. Все числа встречаются в нём ровно один раз, за исключением одного отсутствующего числа. Найти это отсутствующее число.


Сумма ряда от 1 до N считается моментально. Сумма чисел в массиве — за 1 проход. Разница == пропущенное число. И никакой магии, кроме школьной математики ;)
Это если в размерность уместитесь, иначе здравствуй, длинная арифметика. Плюс xor'а как раз в том, что все операции гарантированно не приведут к переполнению.
Не нужно длинной арифметики: для сложений и умножений достаточно модульной, т.е. просто игнорировать переполнения, и результат получится верный.
Помню, давным давно тесты 1С: Профессионал были такими: Всем выдавали Excel файлы, в которых были листы с заданиями, а на последней странице зашифрованные правильные ответы. Шифр — номер вопроса 5 раз и правильный ответ 5 раз, например, первый ответ в шестом вопросе давал число 11111^66666 = 77581
Некоторые хорошо готовились к тесту, а именно: заранее находили подобные файлы, вскрывали пароль на макросы и смотрели алгоритм. Далее дело техники: XOR числа нужной колонки с пятикратным номером вопроса давал правильный ответ. Делалось с помощью виндового калькулятора даже без перехода в режим Программист горячей клавишей.
Потом прикрыли лавочку, стали собирать файлы с ответами и отправлять их в 1С.
Сейчас всё онлайн, конечно.
Хороший код — это код, не содержащий трюков.

Хороший код, это понятный и быстрый.
Если элементарные операции вам не понятны, значит вы не туда свернули)

А если трюк не особо очевидный, и не самый быстрый (требуется два цикла, хотя достаточно и одного)?

Да, трюки, хаки это все для выпендрежа, возможно для собесов, где проверяют не ваши умения, а на насколько глубоко вы копнули в мануалы, те тестируют глубину ваших знаний. А в работе подобное точно использовать нельзя, так как команда может состоять из специалистов разного уровня и код должен быть простой понятный для всех, а не только для гиков

С поиском пропущенного числа лучше сумма справится с учётом того, что сумма арифметической прогрессии 1+2+...+n = n(n+1)/2. Притом алгоритм будет толерантен и к переполнениям.

Хз почему минусуют но именно такое решение приходит в голову первым делом.

Минусуют видимо те, у кого есть более элегантное решение

Либо те, кто заметили, что это решение уже было запощено на две страницы выше.

Ещё можно заметить, что 2n^(2n+1)=1
Это поможет упростить первый цикл. Если n четное, то result после него будет 0, если нечётное, то n+1
Пните, если ошибся

1^2^3^4^5 = 1
Тут не 0 и не n+1
Идея в первом комментарии совершенно правильная, надо просто немного хитрее делать.

Для нечётного n последовательность 0...n разбивается на парные блоки вида [2x, 2x+1], где x — целое и неотрицательное. Число таких блоков составляет (n + 1) / 2.
Каждый блок сокращается в 1 при взятии операции xor внутри блока. Соответственно вся последовательность операций xor сокращается в xor от (n + 1) / 2 единиц. Если (n + 1) / 2 — чётное, то конечный результат 0, в противном случае 1.

Для чётного n можно просто посчитать результат xor последовательности для n-1, и сделать xor с n. Получится либо n (при xor с 0), либо n + 1 (при xor с 1).
> Если алгоритм по-прежнему кажется вам непостижимым и магическим (надеюсь, это не так), то может быть полезным подумать, как достичь того же результата при помощи арифметических операторов. На самом деле всё довольно просто
Это не только просто, но и быстрее, потому что первый цикл («сложить все числа диапазона») можно сократить до одной строчки — вычисление суммы арифметической прогрессии.
Как заметили чуть выше, с XOR тоже можно сократить первый цикл до одной строчки.
Придумался еще один вариант решения с XOR. Потенциально меньше XOR-ов, но добавляются сравнения.
Применяем XOR ко всем элементам, запоминаем результат:
P = A[0] ^ A[1] ^… ^ A[n — 1]
После этого, поочередно выполняя XOR со всеми числами от 1 до n, находим такое значение, что XOR этого значения с P даст результат, отличный от P. Это и будет искомое число.
Единственное значение, XOR которого с P не даст результат, отличный от P — это 0.
Просто по определению XOR.
Да, это я глупость какую-то придумал.
Помню, в Фидо в RU.HACKER смеялись над, кажется, разработчиками ключей HASP для LPT порта, которыми была защищена 1С бухгалтерия. Разработчики коммерческого продукта для шифрования не знали этого трюка с xor, шифровали A xor B xor C xor… и считали, что множество xor сильно запутывает и это трудно будет расшифровать. А хакеры знали этот трюк. :-)

Еще если сделать XOR и затем посчитать единичные биты(popcount) можно найти расстояние Хэмминга.

На самом деле, только коммутативности и "х^х=0" недостаточно. Необходимо, чтобы операция была ещё и ассоциативна — когда мы пишем а^b^c, мы подразумеваем (а^b)^c, и по-хорошему нужно доказать, что это выражение равно а^(b^c). Можно так же с помощью таблиц.

"Достигнуть предела" и поиметь XOR для большего числа состояний чем 2 можно опустившись глубже в терминологии. XOR это побитовая четность значений (ну или поразрядные суммы по модулю 2) Тут возникает даже 2 подхода к увеличению показателя. Можно все пересчитать в троичной системе, а можно двоичные разряды складывать в троичный накопитель. Но подумав еще немного станет понятно, что эффективнее подниматься сразу до 4-ичной системы. И так как нам не важен порядок разрядов (т.к. XOR его игнорирует по определению) можно все входящие значения "преобразовывать рассческой"

toQuaternit=(x)=> x&0x5555 | (x&~0x5555)<<15 //получая исходные 16 бит в позициях 32битного 0x55555555

затем с пред коррекцией можно складывать по модулю 3 (по сути это троичный XOR)

addBitMod3=(sum, x){
    let quaternit = x&0x5555 | (x&~0x5555)<<15;
    let msk = ~3*(sum>>1 & quaternit) //4-чные разряды ожидающие сложения и уже >=2
    return (sum&msk) //коррекция переполнения
      + (quaternit&msk); //сложение остальных
    // после которого 4ичные разряды 0<=2 (что эквивалентно вычислениям в 3ичной)
}

Кстати говоря если вы параллельно с этим сделаете обычный 2-чный XOR по элементам, то из них обоих можно будет однозначно получить 6-чный XOR. И вообще пройдя так несколько-ичными XOR'ами вы получите XOR'ы всех их комбинаций (так что интереснее всего брать простые числа)

4-чная система конечно с другими коррекциями тоже считается, и даже пребывает в нормальной для компьютера форме. Т.е. в нее не надо переводить значения потому что 2чка ей кратна.

Upd: И конечно XOR далеко не только для экономии память на хаках... высокоуровневые программисты об этом узнают часто поздно. Он активно используется в алгоритмах избыточных данных с кодами коррекции. В прикладных нуждах к нему часто можно свести флаги хитро индексирующие БД и там это может заменять крайне тормознутые развернутые формы запросов.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий