1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7

# теория · шаг 1 из 7

Основание −2: знак из чередующихся весов

Коротко

В позиционной записи основание задаёт веса разрядов. При основании −2 они идут как 1, −2, 4, −8, …, поэтому цифры 0 и 1 представляют и положительные, и отрицательные целые без отдельного знакового бита. Чтобы получить младшую цифру, выбирай остаток r из {0,1}, затем переходи к частному (n−r)/(−2). Чтение обратно выполняется по схеме Горнера. Фиксированная ширина задаёт свой диапазон: четыре бита здесь обозначают целые от −10 до 5, а не от −8 до 7.

Если переводишь −3 в основание −2 и можешь вывести диапазон четырёхбитного слова, переходи к «Теперь сам».

Как думаешь, что означает строка 1101

Пусть программа получила четыре бита 1101. В обычной двоичной записи это 13. В четырёхбитном дополнительном коде это −3: из 13 вычитаем 16. Теперь прочтём те же цифры с основанием −2: 1·(−8)+1·4+0·(−2)+1=−3. Значения случайно совпали для этой строки, но совпадение не является правилом формата.

У строки 1010 обычное двоичное значение 10, дополнительный код даёт −6, а новое основание даёт −8−2=−10. До вычисления нужно узнать, какой формат задан. Биты сами не содержат пометки «это такое-то число». Эту договорённость хранит описание файла, команды или типа данных.

На что это похоже

Представь ряд коробок с жетонами. На первой написано «добавить 1», на второй «вычесть 2», на третьей «добавить 4», на четвёртой «вычесть 8». Единица в разряде означает взять соответствующий жетон, ноль - пропустить. Отрицательное значение получается суммой жетонов с отрицательными весами, хотя отрицательных цифр в записи нет.

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

Механизм: деление выбирает очередную цифру

Для обычного основания 2 мы пишем n=2q+r. Для нового основания равенство становится n=(−2)q+r. Цифра r должна равняться 0 или 1. Поэтому она определяется чётностью n: чётному соответствует 0, нечётному 1. После выбора цифры число n−r делится на −2 без остатка, и частное снова целое.

В Python удобно брать r=n%2, то есть остаток от положительного делителя 2. Выражение n%(-2) имеет другую договорённость о знаке остатка и может дать −1, который не является допустимой цифрой. Например, для n=−3 нужный шаг такой: r=1, q=(−3−1)/(−2)=2. Записываем цифру 1 и продолжаем с 2.

Выписываем остатки от младшего разряда к старшему. Чтение готовой строки идёт в обратном направлении: при очередной цифре d накопленное значение заменяется на −2·value+d. Это та же схема Горнера, но множитель отрицателен. Не надо отдельно проверять знак или считать степени для каждой цифры.

Правило

Любое целое имеет конечную запись цифрами 0 и 1 по основанию −2. Для кодирования повторяй r=n%2, n=(n−r)//(-2) до нулевого частного и переверни остатки. Ноль записывается отдельной цифрой 0. Для декодирования умножай накопленное значение на −2 и прибавляй следующую цифру. Ведущие нули значения не меняют.

Почему процесс завершается? При |n|>1 новое частное по модулю меньше прежнего. Остаются три малых состояния: 0 завершает цикл, 1 переходит в 0, а −1 - в 1, затем 0. Почему запись единственна? Младшая цифра однозначно задана чётностью; после её удаления тот же довод применяется к частному. Речь о записи без лишних ведущих нулей.

Разбор: отрицательное число и обратная проверка

Для −3 получаем цепочку −3 → 2 → −1 → 1 → 0. Остатки по пути равны 1,0,1,1; читаем назад и получаем 1101. Теперь проверим значение: −8+4+1=−3. Каждая стрелка сохраняла равенство n=(−2)q+r, поэтому обратная подстановка обязана вернуть исходное число.

Для 2 шаги другие: 2 → −1 → 1 → 0, остатки 0,1,1, запись 110. Уже пример 1+1=110 показывает, почему стандартный двоичный сумматор не подходит: цифры похожи, но переносы должны учитывать знак основания.

negabase.py
def zapis(n):
    if n == 0:
        return "0"
    digits = []
    while n:
        r = n % 2
        digits.append(str(r))
        n = (n - r) // (-2)
    return "".join(reversed(digits))


def znachenie(s):
    n = 0
    for c in s:
        n = -2 * n + int(c)
    return n


for n in [-10, -3, -1, 0, 2, 5]:
    s = zapis(n)
    print(n, s, znachenie(s))

width = 4
mask = sum(1 << i for i in range(1, width, 2))
values = [((w ^ mask) - mask) for w in range(1 << width)]
assert len(set(values)) == 1 << width
print("четыре бита:", min(values), max(values))
for n in range(min(values), max(values) + 1):
    word = (n + mask) ^ mask
    assert znachenie(f"{word:04b}") == n
print("фиксированная ширина: все 16 значений совпали")
↳  выводpython3 negabase.py
-10 1010 -10
-3 1101 -3
-1 11 -1
0 0 0
2 110 2
5 101 5
четыре бита: -10 5
фиксированная ширина: все 16 значений совпали

Первые три столбца вывода - исходное целое, его новая запись и результат обратного чтения. Последние две строки относятся к проверке фиксированной ширины, которую сейчас выведем отдельно. Перебор подтверждает формулу на четырёх битах; доказательство через веса работает для любой конечной ширины.

Разбор: диапазон и перевод одной маской

Нумеруем позиции с нуля от младшей. Пусть A - обычное положительное целое, у которого единицы стоят во всех нечётных позициях выбранного слова. Для четырёх битов A=1010₂=10. Минимальное значение нового формата равно −A: включены все отрицательные веса и выключены положительные. Максимальное равно (2^w−1)−A, где w - число битов. При w=4 это 5.

Для битового слова u его значение равно u−2·(u&A): разряды с отрицательным весом сначала были учтены положительно, поэтому вычитаем их дважды. Тождество u XOR A = u+A−2·(u&A) даёт более короткую формулу value=(u XOR A)−A. Отсюда обратный перевод: u=(value+A) XOR A, когда значение лежит в диапазоне.

Добавление A переводит весь допустимый диапазон в числа 0…2^w−1, а XOR с фиксированной маской переставляет эти числа без повторов. Значит диапазон непрерывен и каждое целое в нём представляется ровно одним словом. Для восьми битов A=170, диапазон от −170 до 85. Это не диапазон восьмибитного дополнительного кода.

Что ломается без этого

Код берёт модуль числа, переводит его как обычное двоичное и добавляет слева знак. Получается другой формат. Или вычитает число из 2^w, рассчитывая на дополнительный код. Проверка на 0 и 1 этого не заметит; отрицательные значения и разряд с весом −2 сразу обнаруживают ошибку. Поэтому проверяй обратное чтение, крайние значения и числа по обе стороны нуля.

Зачем это в работе

Такой формат редко выбирают для обычного прикладного хранения. Он полезен как проверка понимания машинного представления: знак, диапазон и операции выводятся из весов, а не из привычного вида строки. Тот же навык нужен, когда читаешь нестандартное поле протокола или сравниваешь арифметическую модель с реализацией. Необычное основание само по себе не обещает ускорения процессора.

Теперь сам

Переведи 6 в основание −2. Затем найди минимальное и максимальное значения шестибитного слова. Объясни, меняет ли добавленный слева ноль значение −3.

Ответ. Шаги дают 6 → −3 → 2 → −1 → 1 → 0, поэтому запись 11010: 16−8−2=6. В шести битах отрицательные веса дают A=2+8+32=42, минимум −42, максимум 63−42=21. Ведущий ноль добавляет слагаемое 0, поэтому значение сохраняется.

Главное

Число задаётся цифрами вместе с весами позиций. При основании −2 знак получается из чередования весов; деление с остатком, диапазон и обратный перевод нужно выводить из этого определения, а не переносить из дополнительного кода.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.