Я наконец нашел решение, это ужасно, но оно работает:
public BigInteger srl(BigInteger l, int width, int shiftBy) {
if (l.signum() >= 0)
return l.shiftRight(shiftBy);
BigInteger opener = BigInteger.ONE.shiftLeft(width + 1);
BigInteger opened = l.subtract(opener);
BigInteger mask = opener.subtract(BigInteger.ONE).shiftRight(shiftBy + 1);
BigInteger res = opened.shiftRight(shiftBy).and(mask);
return res;
}
Случай, когда ваше целое число положительное, тривиален, так как shiftRight в любом случае вернет правильный результат.Но для отрицательных чисел это становится сложно.Упомянутая ранее версия отрицания не работает, так как -1 в BigInteger отрицается как 1. Сдвиньте ее, и вы получите 0. Но вам нужно знать, какова ширина вашего BigInteger.Затем вы в основном заставляете BigInteger иметь по крайней мере ширину + 1 бит, вычитая сошник.Затем вы выполняете сдвиг и маскируете лишний бит, который вы ввели.На самом деле не имеет значения, какой сошник вы используете, если только он не изменяет младшие биты.
Как работает сошник:
Реализация BigInteger хранит только самую высокую позицию 0для отрицательных чисел.-3 представлен как:
1111_1111_1111_1111_1101
Но только некоторые биты сохранены, остальные помечены как X.
XXXX_XXXX_XXXX_XXXX_XX01
Сдвиг вправо ничего не делает, так как всегда есть 1идет слева.Таким образом, идея состоит в том, чтобы вычесть 1, чтобы сгенерировать 0 за пределами ширины, которая вас интересует. Предполагая, что вы заботитесь о младшем двенадцатом бите:
XXXX_XXXX_XXXX_XXXX_XX01
- 0001_0000_0000_0000
========================
XXXX_XXX0_1111_1111_1101
Это заставило генерировать реальные 1.Затем вы сдвигаетесь вправо, скажем, 5.
XXXX_XXX0_1111_1111_1101
>>5 XXXX_XXX0_1111_111
И затем маскируете это:
XXXX_XXX0_1111_111
0000_0000_1111_111
И тем самым получаете правильный результат:
0000_0000_1111_111
Так чтовведение нуля заставило реализацию BigInteger обновить сохраненную позицию 0 до ширины, превышающей ту, которая вас интересует, и принудительно создало сохраненные единицы.