Базовые и битовые операции в компьютерах

переводчик

Почему компьютеры считают в двоичном формате:

Компьютеры состоят из схем, а схемы имеют только два состояния: 0 и 1.

Преобразование между различными базами:

В десятичной дроби 1 в разряде единиц означает 10⁰=1, 1 в разряде десятков означает 10¹=10, а 1 в разряде сотен означает 10²=100, поэтому: 123=1×10²+2×10¹+3×10⁰

Точно так же в двоичном формате 1 в одном разряде представляет 2⁰=1, 1 в разряде десятков представляет 2¹=2, а 1 в разряде сотен представляет 2²=4, поэтому: (A3A2A1A0)₂=A3 ×2³+A2×2²+A1 ×2¹+A0×2⁰

Если двоичные и десятичные числа встречаются в одном и том же уравнении, чтобы различать их, мы используем форму (A3A2A1A0)₂, чтобы указать, что A3A2A1A0 является двоичным числом, и каждое число может быть только 0 или 1. Другие числа без скобок и нижних индексов по-прежнему представлять десятичное число. Для двоичного числа, такого как (A3A2A1A0)₂, крайний левый бит A3 называется старшим значащим битом (MSB, старший значащий бит), а крайний правый бит A0 называется младшим значащим битом (LSB, младший значащий бит). Отныне мы следуем этому соглашению:LSB называется 0-м битом, а не 1-м битом, поэтому, если число состоит из 32 бит, MSB является 31-м битом.. Приведенная выше формула является формулой преобразования из двоичного в десятичное число.

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

13=1×2³+1×2²+0×2¹+1×2⁰ Таким образом, 13, преобразованное в двоичное, должно быть (1101)₂. Вопрос в том, как разложить 13 в виде справа от знака равенства? Обратите внимание, что правая часть знака равенства может быть записана как

13=(((0×2+1₃)×2+1₂)×2+0₁)×2+1₀

Мы можем извлечь четыре числа 1101 из приведенной выше формулы, многократно разделив 13 на 2 и взяв остаток. подписываются следующие формулы:

13÷2=6...1₀ 6÷2=3...0₁ 3÷2=1...1₂ 1÷2=0...1₃

Расположение остатков, полученных в этих четырех шагах, в обратном порядке является двоичным представлением числа 13, поэтому этот метод называется остатком от деления на два в обратном порядке.

Компьютеры используют двоичный код для представления чисел, и программисты также должны привыкнуть к использованию двоичного кода, но двоичный код слишком многословен для записи, поэтому двоичные числа обычно делятся на группы из трех или четырех цифр, и каждая группа представлена ​​числом. Например, разделите (10110010)₂ на группы по три цифры, начиная с самой младшей цифры, 10, 110, 010, а затем запишите каждую группу в виде десятичного числа, то есть (262)₈, чтобы диапазон значений каждой цифры это 0~7, каждые восемь и один, называемый восьмеричным (Octal). Аналогично разделим (10110010)₂ на группы по четыре цифры, 1011, 0010, а затем каждую группу запишем в виде числа Младшая цифра этого числа 2, а старшая цифра уже больше 9. Оговариваем, что буквы A ~ F представляют 10 ~ 15. Это число может быть записано как (B2)₁₆, диапазон значений каждой цифры 0 ~ F, каждое шестнадцатеричное число равно единице, называемой шестнадцатеричной (шестнадцатеричной). Поэтому восьмеричный и шестнадцатеричный — это удобные способы записи, придуманные программистами для удобства написания двоичного, точно так же, как отношения между курсивом и печатными буквами.

Битовая операция:

Целые числа представлены в компьютере двоичными битами.В языке C есть несколько операторов, которые могут напрямую манипулировать битами целых чисел, которые называются битовыми операциями.

1.1 Побитовое И, ИЛИ, XOR, Отрицание

  • Побитовое И (&): оба операнда равны 1, результат равен 1, иначе 0
  • или (|): оба операнда имеют 1, результат равен 1
  • XOR (^): если два операнда одинаковы, результат равен 0, а если два операнда разные, результат равен 1.
  • Отрицание (~): отрицание операнда

1.2. Сменная работа

Операторы сдвига (побитовый сдвиг) включают сдвиг влево >. Сдвиг влево сдвигает все двоичные биты целого числа влево на несколько битов, например, 0xcffffffff3

左移

Платформа Java представляет собой все целые числа со знаком, поэтому приведенная выше операция на рис. 1 изменяет бит знака в Java с (-805306381) на (1073741772).

В определенном диапазоне значений сдвиг целого числа влево на 1 бит эквивалентен умножению на 2.. Например, двоичное число 11 (десятичное число 3) смещается влево и становится равным 110, то есть 6, а затем сдвигается влево, получая 1100, то есть 12. Читатели могут сами убедиться, что это правило выполняется как для чисел со знаком, так и для чисел без знака, а также для отрицательных чисел. Конечно, если сдвиг влево изменяет старший бит (бит знака), то результат не должен умножаться на 2, поэтому я добавил поправку"в пределах определенного диапазона значений". Поскольку компьютер выполняет сдвиг намного быстрее, чем умножение, компилятор может воспользоваться этим для оптимизации, например, увидеть i*8 в исходном коде, который может быть скомпилирован в инструкции сдвига вместо инструкций умножения.

Когда операнд представляет собой число без знака, правила операции сдвига вправо аналогичны правилам сдвига влево, например, 0xcffffff3>>2 получает 0x33fffffc:

Результат выполнения на платформе Java: значение изменяется с -805306381 на -201326596 и по-прежнему сохраняет бит знака отрицательного числа, что эквивалентно делению на 4

11 из двух младших битов сдвигаются наружу, два старших бита заполняются двумя нулями, а остальные биты сдвигаются вправо на два бита по очереди. Как и при сдвиге влево, количество сдвинутых битов должно быть меньше, чем общее количество битов в левом операнде, иначе результат будет неопределенным. В пределах определенного диапазона значений сдвиг целого числа вправо на 1 бит эквивалентен делению на 2 с усечением дробной части.

Когда операнд представляет собой число со знаком, правила операции сдвига вправо усложняются:

  • Если это положительное число, введите 0 в старший бит
  • Если это отрицательное число, не обязательно, является ли старший бит 1 или 0, что определяется реализацией. Для компилятора gcc платформы x86 старший бит вводится в 1, то есть по-прежнему сохраняется знаковый бит отрицательного числа. Этот метод обработки по-прежнему сохраняет свойство «сдвиг на 1 бит вправо эквивалентен делению на 2" для отрицательных чисел.

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

1.3 Маска:

Если вы хотите работать с некоторыми битами в целом числе, как представить положение этих битов в целом числе? Его можно представить маской. такие как маска0x0000ff00Указывает, что операции выполняются с 8~15 битами 32-битного целого числа, например, следующим образом.

1. Выньте 8~15 цифр.

unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = (a & mask) >> 8; /* 0x00000056 */

2. Очистить биты с 8 по 15 до 0.

unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = a & ~mask; /* 0x12340078 */

3. Установите 8~15 на 1.

unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = a | mask; /* 0x1234ff78 */

Применение битовой операции в алгоритме снежинки:

  1. Максимальное значение 12-битного приращения в миллисекундах: 1 сдвиг вправо 12 бит минус 1, вы можете использовать пропорциональную последовательность для вычисления максимального значения следующих 12 бит, чтобы увидеть, согласуется ли оно с результатом смещения; двоичное представление: (...001111 1111 1111) (0 с после 14 заменяются многоточием)
  2. 10bits Worker Process Id: 1 сдвигается вправо на 10 бит, вот у меня вопрос, 1 не надо вычитать, это максимальное значение?
  3. Основание для принятия решения о получении в следующий раз:0L == (sequence = ++sequence & SEQUENCE_MASK)последовательность и максимальное значениепобитовое И, только когда последовательность больше, чем SEQUENCE_MASK, результат & равен 0, получить следующую метку времени
  4. 41 бит: (currentMillis - EPOCH)
  5. ((currentMillis - EPOCH)

Реализация shrding-jdbc по умолчанию:


/**
 * 默认的主键生成器.
 * 
 * <p>
 * 长度为64bit,从高位到低位依次为
 * </p>
 * 
 * <pre>
 * 1bit   符号位 
 * 41bits 时间偏移量从2016年11月1日零点到现在的毫秒数
 * 10bits 工作进程Id
 * 12bits 同一个毫秒内的自增量
 * </pre>
 * 
 * <p>
 * 工作进程Id获取优先级: 系统变量{@code sharding-jdbc.default.key.generator.worker.id} 大于 环境变量{@code SHARDING_JDBC_DEFAULT_KEY_GENERATOR_WORKER_ID}
 * ,另外可以调用@{@code DefaultKeyGenerator.setWorkerId}进行设置
 * </p>
 * 
 * @author gaohongtao
 */
@Getter
@Slf4j
public final class DefaultKeyGenerator implements KeyGenerator {
    
    public static final long EPOCH;
    
    public static final String WORKER_ID_PROPERTY_KEY = "sharding-jdbc.default.key.generator.worker.id";
    
    public static final String WORKER_ID_ENV_KEY = "SHARDING_JDBC_DEFAULT_KEY_GENERATOR_WORKER_ID";
    
    private static final long SEQUENCE_BITS = 12L;
    
    private static final long WORKER_ID_BITS = 10L;
    
    private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;
    
    private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;
    
    private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;
    
    private static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS;
    
    @Setter
    private static TimeService timeService = new TimeService();
    
    @Getter
    private static long workerId;
    
    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2016, Calendar.NOVEMBER, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        EPOCH = calendar.getTimeInMillis();
        initWorkerId();
    }
    
    private long sequence;
    
    private long lastTime;
    
    public static void initWorkerId() {
        String workerId = System.getProperty(WORKER_ID_PROPERTY_KEY);
        if (!Strings.isNullOrEmpty(workerId)) {
            setWorkerId(Long.valueOf(workerId));
            return;
        }
        workerId = System.getenv(WORKER_ID_ENV_KEY);
        if (Strings.isNullOrEmpty(workerId)) {
            return;
        }
        setWorkerId(Long.valueOf(workerId));
    }
    
    /**
     * 设置工作进程Id.
     * 
     * @param workerId 工作进程Id
     */
    public static void setWorkerId(final long workerId) {
        Preconditions.checkArgument(workerId >= 0L && workerId < WORKER_ID_MAX_VALUE);
        DefaultKeyGenerator.workerId = workerId;
    }
    
    /**
     * 生成Id.
     * 
     * @return 返回@{@link Long}类型的Id
     */
    @Override
    public synchronized Number generateKey() {
        long currentMillis = timeService.getCurrentMillis();
        Preconditions.checkState(lastTime <= currentMillis, "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", lastTime, currentMillis);
        if (lastTime == currentMillis) {
            if (0L == (sequence = ++sequence & SEQUENCE_MASK)) {
                currentMillis = waitUntilNextTime(currentMillis);
            }
        } else {
            sequence = 0;
        }
        lastTime = currentMillis;
        if (log.isDebugEnabled()) {
            log.debug("{}-{}-{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(lastTime)), workerId, sequence);
        }
        return ((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
    }
    
    private long waitUntilNextTime(final long lastTime) {
        long time = timeService.getCurrentMillis();
        while (time <= lastTime) {
            time = timeService.getCurrentMillis();
        }
        return time;
    }
}

наконец:

У Little Tail есть волна, добро пожаловать, чтобы обратить внимание на мой публичный аккаунт и время от времени делиться своими мыслями о программировании, инвестициях и жизни :)