Модификация и деление по степени двойки для любого числа со знаком

Я знаю формулу положительных чисел. например, учитывая целое положительное число X и любую степень двойки Y = 2^K, мы имеем

X / Y == X >> K
X % K == X & (Y - 1)

но что, если X отрицательно? Я не смог найти это в Интернете, но мне нужно это знать. Я хочу выполнить целочисленное деление/модификацию с помощью SSE, а делители - это степень двойки. (хотя это может быть не известно во время компиляции)

примечание: для модуля я ищу фактическое поведение С++, например -1 % 3 == -1 и -5 % 3 == -2


Глядя на clang x86-64, эта функция

int divide(int x) {
    return x / 8;
}

компилируется в

    lea     eax, [rdi + 7]
    test    edi, edi
    cmovns  eax, edi       // move edi to eax if edi is positive?
    sar     eax, 3         // i can only understand this part!
    ret

и для модуля x % 8,

    mov     eax, edi
    lea     ecx, [rax + 7]
    test    edi, edi
    cmovns  ecx, edi
    and     ecx, -8
    sub     eax, ecx
    ret

может кто-нибудь объяснить формулу или просто дать ссылку на то, откуда это взялось? Заранее спасибо.

Итак, очевидно, что для отрицательного x, положительного y: x % y == -(-x % y) и x / y == -(-x / y).

Joop Eggen 05.09.2024 11:24

@JoopEggen Я тоже об этом думал, но для этого требуется ветка, и когда я смотрю на компиляторы, такие как gcc или clang, они делают что-то более умное и не имеют ветки. (хотя есть один cmov)

M.kazem Akhgary 05.09.2024 11:27

SAR «округляется в сторону отрицательной бесконечности», поэтому для x в [-7,-1] SAR x, 3 будет давать -1 вместо 0. Добавление 7 в случае отрицательного числа приводит к тому, что это округление дает желаемый результат.

Botje 05.09.2024 12:05
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Знаковый модуль/деление С++ (на мой взгляд) определен плохо. Вы получите гораздо более естественную структуру, если модуль всегда положителен, а деление всегда округляется до отрицательной бесконечности. Думаю, можно винить производителей процессоров за выбор подписанных инструкций деления.

Одним из случаев, когда это проявляется, является арифметический сдвиг со знаком. Естественно, это всегда округляется до отрицательной бесконечности. Итак, чтобы эмулировать деление C++, которое всегда округляется до нуля, вам нужно разделить оператор деления на два случая:

  1. Если положительный, мы переключаемся как обычно.
  2. Если отрицательный результат, мы вычисляем floor((x + d - 1) / d) для эмуляции ceil(x / d), округляя его до нуля.

Итак, это объясняет следующее:

    lea     eax, [rdi + 7]  // t = x + 7
    test    edi, edi        // if x < 0
    cmovns  eax, edi        //   x = t
    sar     eax, 3          // ret = floor(x / 8)
    ret

Теперь, при вычислении по модулю, мы имеем точно такой же сценарий. В дополнении до двух взятие младших k бит при выполнении по модулю 2^k уже дает всегда положительный модуль. Однако в C++ (из-за неудачного выбора округления деления до нуля) есть странный модуль, знак которого всегда совпадает с числителем.

Чтобы смоделировать это, мы вместо этого вычисляем остаток как x - round_to_zero(x / d) * d. Итак, вот что делает сборка:

    mov     eax, edi        // orig = x
    lea     ecx, [rax + 7]  // Same as before, see above.
    test    edi, edi        
    cmovns  ecx, edi
    // sar  ecx, 3          // As an optimization we
    // shl  ecx, 3          // replace (x >> 3) << 3
    and     ecx, -8         // with x & ~((1 << 3) - 1).
    sub     eax, ecx        // ret = orig - round_to_zero(x / 8)
    ret

Большое спасибо! теперь это имеет большой смысл <3

M.kazem Akhgary 05.09.2024 12:36

Другие вопросы по теме