【问题标题】:Implementing efficient 512 bit integer type实现高效的 512 位整数类型
【发布时间】:2014-01-13 19:09:00
【问题描述】:

我被要求为我的 RSA 加密算法创建 512 位整数类型。我从最简单的按位运算OR, AND, XOR, NOT 开始,然后使用提到的操作我实现了加法、减法等。 不幸的是,经过多次测试,它在超过 128 位时非常慢。

当我发现使用 RSA 生成的站点时,我很惊讶,它显然在几毫秒内使用 javascript 生成了密钥。如何改进我的算法以获得更好的效率?或者更好的问题是:专业人士如何实现这么大的类型 这样的表现。

这是我的代码:

    public class BigInt {

      public static final int    BYTE = 8;
      public static final int    DEFAULT_SIZE = 128;
      public final static BigInt ZERO = new BigInt(0);
      public final static BigInt ONE = new BigInt(1);
      public final static BigInt TEN = new BigInt(10);
      private boolean[] number;
      private Endianness endianness = Endianness.BIG_ENDIAN;

    public BigInt() {
        number = new boolean[DEFAULT_SIZE];
    }

    public BigInt(boolean[] number) {
        this.number = number;
    }

    public BigInt(int integerNumber) {
        this();
        boolean isNegative = false;
        if (integerNumber < 0) {
            isNegative = true;
            integerNumber = Math.abs(integerNumber);
        }
        for (int i = number.length - 1; i >= 1 && integerNumber >= 1; i--) {
            number[i] = integerNumber % 2 == 1;
            integerNumber >>= 1;
        }
        if (isNegative) {
            number = new BigInt(number).not().add(ONE).number;
        }
    }

    public BigInt(String binaryString) throws InvalidBinaryStringException {
        this();
        for (int i = binaryString.length() - 1, j = number.length - 1;
             i >= 0 && j >= 0; i--, j--) {
            if (binaryString.charAt(i) != '1' && binaryString.charAt(i) != '0') {
                throw new InvalidBinaryStringException(binaryString);
            }
            number[j] = (binaryString.charAt(i) - '0') == 1;
        }
    }

    public BigInt(BigInt copy) {
        this();
        System.arraycopy(copy.number, 0, number, 0, copy.number.length);
    }

    public BigInt add(BigInt component) {
        BigInt a, b;
        BigInt x = new BigInt(this);
        BigInt y = new BigInt(component);
        do {
            a = x.and(y);
            b = x.xor(y);
            x = a.shiftLeft(1);
            y = b;
        } while (!a.equals(ZERO));
        return b;
    }

    public BigInt sub(BigInt subtrahend) {
        return add(subtrahend.not().add(new BigInt(ONE)));
    }

    public BigInt mul(BigInt multiplier) {
        BigInt m = new BigInt(ONE), z = new BigInt(ZERO);
        BigInt x = new BigInt(this), y = new BigInt(multiplier);

        if (x.lessThen(ZERO)) {
            x = x.not().add(ONE);
            y = y.not().add(ONE);
        }

        while (x.greaterThenEqual(m) && !y.equals(ZERO)) {
            if (!x.and(m).equals(ZERO)) {
                z = y.add(z);
            }
            y = y.shiftLeft(1);
            m = m.shiftLeft(1);
        }
        return z;
    }

    public BigInt div(BigInt divisor) {
        BigInt mask = new BigInt(ONE);
        BigInt quotient = new BigInt(ZERO);

        BigInt numerator = new BigInt(this), denominator = new BigInt(divisor);

        if (numerator.lessThen(ZERO)) {
            numerator = numerator.not().add(ONE);
        }

        if (denominator.lessThen(ZERO)) {
            denominator = denominator.not().add(ONE);
        }

        while (denominator.lessThenEqual(numerator)) { // PROBLEM
            denominator = denominator.shiftLeft(1);
            mask = mask.shiftLeft(1);
        }

        while (mask.greaterThen(ONE)) {
            denominator = denominator.shiftRight(1);
            mask = mask.shiftRight(1);
            if (numerator.greaterThenEqual(denominator)) {
                numerator = numerator.sub(denominator);
                quotient = quotient.or(mask);
            }
        }

        if (number[0] != divisor.number[0]) {
            return quotient.not().add(ONE);
        }
        return quotient;
    }


    public BigInt mod(BigInt y) {
        // (x - y*(x/y))
        BigInt x = new BigInt(this);
        BigInt right = x.div(y);
        BigInt mid = y.mul(right);
        return x.sub(mid);
    }

    //completly inefficient for numbers larger than 32 bit
    @Deprecated
    public BigInt div2(BigInt divisor) {
        BigInt c = new BigInt(ZERO), sign = new BigInt(ZERO);
        BigInt x = new BigInt(this), y = new BigInt(divisor);
        if (x.lessThen(ZERO)) {
            x = x.not().add(ONE);
            sign = sign.xor(ONE);
        }

        if (y.lessThen(ZERO)) {
            y = y.not().add(ONE);
            sign = sign.xor(ONE);
        }

        if (!y.equals(ZERO)) {
            while (x.greaterThenEqual(y)) {
                x = x.sub(y);
                c = c.add(ONE);
            }
        }

        if (!sign.equals(ZERO)) {
            c = c.not().add(ONE);
        }
        return c;
    }

    //doesn't work for big numbers close to maximum bit
    @Deprecated
    public BigInt mod2(BigInt mod) {
        BigInt y = new BigInt(this);
        BigInt x = new BigInt(mod);

        BigInt p = new BigInt(x);

        if (y.lessThen(ZERO)) {
            y = y.not().add(ONE);
        }

        if (p.lessThen(ZERO)) {
            p = p.not().add(ONE);
            x = x.not().add(ONE);
        }

        while (p.lessThen(y)) {         //forever loop
            p = p.shiftLeft(1);
        }

        while (p.greaterThenEqual(x)) {
            if (y.greaterThenEqual(p)) {
                y = y.sub(p);
            }
            p = p.shiftRight(1);
        }

        if (number[0]) {
            y = y.not().add(ONE);
        }
        return y;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null)
            return false;
        if (obj == this)
            return true;
        if (!(obj instanceof BigInt))
            return false;

        BigInt bigInt = (BigInt) obj;
        for (int i = 0; i < bigInt.number.length; i++) {
            if (number[i] != bigInt.number[i]) {
                return false;
            }
        }
        if (!endianness.equals(bigInt.endianness)) {
            return false;
        }
        return true;
    }

    public boolean lessThen(BigInt num) {
        if (equals(num)) {
            return false;
        }
        if (number[0] && !num.number[0]) {
            return true;
        } else if (!number[0] && num.number[0]) {
            return false;
        }
        BigInt left = null, right = null;
        if (number[0]) {
            left = not().add(ONE);
            right = num.not().add(ONE);
        } else {
            left = this;
            right = num;
        }
        for (int i = 1; i < number.length; i++) {
            if (left.number[i] != right.number[i]) {
                if (number[0]) {
                    return !(!left.number[i] && right.number[i]);
                } else {
                    return !left.number[i] && right.number[i];
                }
            }
        }
        return false;
    }

    public boolean lessThenEqual(BigInt num) {
        if (equals(num)) {
            return true;
        }
        return lessThen(num);
    }

    public boolean greaterThen(BigInt num) {
        return !lessThen(num);
    }

    public boolean greaterThenEqual(BigInt num) {
        if (equals(num)) {
            return true;
        }
        return greaterThen(num);
    }

    /**
     * BITWISE OPERATORS*
     */
    //logical bitwise shift lefts
    public BigInt shiftLeft(int n) {
        //IT WORKS BECAUSE NEW OBJECT IS SET TO 0;
        BigInt shifted = new BigInt();
        for (int i = 0; i < number.length - n; i++) {
            shifted.number[i] = number[i + n];
        }
        return shifted;
    }

    //logical bitwise shift right
    public BigInt shiftRight(int n) {
        BigInt shifted = new BigInt();
        for (int i = number.length - 1; i >= n; i--) {
            shifted.number[i] = number[i - n];
        }
        boolean sign = number[0];

        for (int i = 0; i < n; i++) {
            shifted.number[i] = sign;
        }
        return shifted;
    }

    //bitwise or |
    public BigInt or(BigInt num) {
        BigInt newInt = new BigInt();
        for (int i = 0; i < number.length; i++) {
            newInt.number[i] = number[i] | num.number[i];
        }
        return newInt;
    }

    //bitwise and &
    public BigInt and(BigInt num) {
        BigInt newInt = new BigInt();
        for (int i = 0; i < number.length; i++) {
            newInt.number[i] = number[i] & num.number[i];
        }
        return newInt;
    }

    //bitwise exclusive or ^
    public BigInt xor(BigInt num) {
        BigInt newInt = new BigInt();
        for (int i = 0; i < number.length; i++) {
            newInt.number[i] = number[i] ^ num.number[i];
        }
        return newInt;
    }

    public BigInt not() {
        BigInt negate = new BigInt();
        for (int i = 0; i < number.length; i++) {
           negate.number[i] = !number[i];
        }
        return negate;
    }

    @Override
    public String toString() {
      /* StringBuilder binaryRepr = new StringBuilder();
        for (byte b : number) {
            binaryRepr.append(b);
        }*/

       String decRepr = "";
        BigInt copy = new BigInt(this);

        if (copy.lessThen(ZERO)) {
            copy = copy.not().add(ONE);
        }

        while (copy.greaterThenEqual(ONE)) {
            BigInt rem = copy.mod(TEN);
            copy = copy.div(TEN);
            decRepr = String.valueOf(Integer.parseInt(getDecimalRemainder(rem), 2)) + decRepr;
        }

        if (number[0]) {
            return "-" + decRepr;// + binaryRepr.toString();
        }
        return decRepr;// + binaryRepr.toString();
        //return binaryRepr.toString();
    }

    private String getDecimalRemainder(BigInt copy) {
        String decimalString = "";

        for (int i = copy.number.length - 1; i >= copy.number.length - 4; i--) {
            decimalString = (copy.number[i] ? "1" : "0") + decimalString;
        }
        return decimalString;
    }

    public String toBinaryString() {
        StringBuilder binaryString = new StringBuilder();
        boolean isFirstBit = false;
        for (boolean b: number) {
            if (b) {
                isFirstBit = true;
            }
            if (isFirstBit) {
                binaryString.append(b);
            }
        }
        return binaryString.toString();
    }

    public static BigInt nextBigInt(BigInt min, BigInt max) {
        BigInt pseudo = new BigInt();
        Random rnd = new Random();
        for (int i = 2; i < pseudo.number.length; i++) {
            pseudo.number[i] = rnd.nextBoolean();
        }
        return pseudo.mod(max).add(min);
    }


    public static void main(String[] args) {
        String big = "";
        for(int i = 0; i < 126; i++) {
            big += "1";
        }
        StopWatch stopWatch = new StopWatch();
        System.out.println(new BigInt(big));
        System.out.println(stopWatch.elapsedTime());
    }
}

【问题讨论】:

  • 你为什么不用java.math.BigInteger
  • 如果您正在构建新的数据类型来进行加密,那么您可能会绕错轴。为什么不使用像 BouncyCastle 这样的东西——他们已经发现并修复了前 10 年你可能犯的错误。
  • @gus 听起来更像是有人要求他们重新发明轮子以进行学习。在分配的情况下,任何事情都会发生。
  • @MxyL 是的,它用于学习目的。因此我不使用任何内置类型。

标签: java performance optimization integer


【解决方案1】:

使用int[] 代替boolean[] 来保存您的号码位。通过这种方式,您可以更快地实现大多数操作,例如一次操作 30 位而不是 1 位。

您可以将数组元素 0 中的最低 30 位保留在数组元素 1 中的接下来的 30 位,依此类推。用这种结构实现逻辑运算(&、|、^)很简单。

对于算术运算,您必须跟踪进位。例如两个数组元素相加后0,如果结果大于30位,则清除溢出位,数组元素1之和加1。

可以使用long 进行乘法运算:两个int 值的乘积总是适合long。使用一种方法,例如手动将两个十进制数相乘。只需使用 30 位数字而不是数字 0-9。

更复杂的乘法方法(如 karatsuba)对于只有 512 位长的数字是无效的。

虽然实现和优化这些操作很有趣,但在实现中也很容易出现细微的错误。因此,如果您为生产代码这样做,请务必编写大量单元测试。最好使用经过良好测试的第三方实现,而不是自己做。

【讨论】:

  • 好吧,为什么不long[]? 8 longs 将是 512 位。
  • @在开始时它是byte[] 数组,但后来我将其更改为布尔值 - 我认为这会有所改进,但事实并非如此。无论如何,根据你的回答,你能更准确吗?我不知道我怎么能做这样的事情。
  • @dcsohl 但是我将如何管理所有操作?和以前一样吗?我不确定我是否理解整数或长数组的概念。
  • @dcsohl 乘法使用int 代替long 会更容易一些。
  • @Henry 谢谢,我开始明白你的意思了。然而,让我好奇的是为什么只使用 30 位 int 而不是 32 ?正如您所说,我可以将此结构视为整数数组。 int[] number;,我可以做所有的按位运算,比如number[i] &amp;= number2[i],但是这 30 位让我有点困惑。你能解释一下你的理由吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-21
  • 2020-02-06
  • 2012-09-05
  • 2020-02-25
  • 2015-12-20
  • 2019-03-25
相关资源
最近更新 更多