在JavaScript中没有外部库可以处理整数溢出吗?

时间:2016-07-11 00:07:04

标签: javascript node.js algorithm ieee-754

在Javascript中(在Chrome devtools控制台面板和Node.js v0.12.5中),我得到的这两个大数字的产品答案不正确:

输入:41962049 * 1827116622

输出:76669557221078480

在C ++和C#中,在将表达式转换为64位int时,我得到76669557221078478的正确答案。

我假设这是一个整数溢出问题,但我当然可能是错的。

有没有办法在不使用像BigInteger这样的外部库的情况下在Javascript中为大数字获取准确的算术产品?这适用于不允许使用其他库的在线课程。

感谢您的帮助。

编辑:感谢解释这实际上不是整数溢出的解释,Patrick Roberts!很有用。

编辑2:jfriend00,我认为这个问题与你链接的问题不同,因为我试图弄清楚是否有办法解决JS的局限性而不依赖于外部库。您在评论中提供的答案有助于回答我的问题,谢谢!

1 个答案:

答案 0 :(得分:6)

这不是整数溢出,这是由于double precision floating point的限制。 JavaScript中最高的可准确表示的整数是2 ^ 53,因为2 ^ 52到2 ^ 53范围内的epsilon正好是1.在此之上,整数精度会中断,但乘法的结果不是由于整数溢出。

以下是维基百科关于IEEE 754标准的相关引用:

  

在2 52 = 4,503,599,627,370,496和2 53 = 9,007,199,254,740,992之间,可表示的数字正好是整数。对于下一个范围,从2 53 到2 54 ,一切都乘以2,因此可表示的数字是偶数,等等。相反,对于前一个范围, 2 51 至2 52 ,间距为0.5等。

     

作为2 n 至2 n + 1 范围内数字的一部分的间距是2 n-52 。将数字舍入到最接近的可表示的数字(机器epsilon)时的最大相对舍入误差因此为2 -53

要回答你的问题,这很可能。这是我刚刚在几个小时内编写的一个小型库,用于无符号整数加法和乘法,它能够显示基数为10的值:



class BigUint {
  static get WORD() {
    return 100000000000000;
  }

  static get HALF() {
    return 10000000;
  }

  static map(word, index) {
    if (index === 0) {
      return word.toString();
    }

    return `000000${word}`.slice(-7);
  }

  static from(array) {
    return Object.create(BigUint.prototype, {
      words: {
        configurable: true,
        enumerable: true,
        value: new Uint32Array(array),
        writable: true
      }
    });
  }

  constructor(number) {
    if (/\D/.test(`${number}`)) {
    	throw new TypeError('seed must be non-negative integer as number or string');
    }
    
    this.words = new Uint32Array(`${number}`.split(/(?=(?:.{7})+$)/).map(s => +s));
  }

  [Symbol.toPrimitive](hint) {
    let string = this.toString();

    switch (hint) {
    case 'number':
      return +string;
    default:
      return string;
    }
  }

  get length() {
    return this.words.length;
  }

  toString() {
    return Array.from(this.words).map(BigUint.map).join('');
  }

  add(that) {
    const thisLength = this.length;
    const thatLength = that.length;
    const maxLength = Math.max(thisLength, thatLength);
    const minLength = Math.min(thisLength, thatLength);
    const max = maxLength === thisLength ? this : that;

    const words = [];

    let augend, addend, sum, keep, carry = 0, index;

    for (index = 1; index <= minLength; index++) {
      augend = this.words[thisLength - index];
      addend = that.words[thatLength - index];

      sum = augend + addend + carry;

      keep = sum % BigUint.HALF;
      carry = (sum - keep) / BigUint.HALF;

      words.unshift(keep);
    }

    for (; index <= maxLength; index++) {
      sum = max.words[maxLength - index] + carry;

      keep = sum % BigUint.HALF;
      carry = (sum - keep) / BigUint.HALF;

      words.unshift(keep);
    }

    if (carry > 0) {
      words.unshift(carry);
    }

    return BigUint.from(words);
  }

  multiply(that) {
    const thisLength = this.length;
    const thatLength = that.length;
    const minLength = Math.min(thisLength, thatLength);
    const maxLength = Math.max(thisLength, thatLength);
    const min = minLength === thisLength ? this.words : that.words;
    const max = maxLength === thatLength ? that.words : this.words;

    const partials = [];

    let product, words, keep, carry = 0, sum, addend;

    for (let outerIndex = minLength - 1; outerIndex >= 0; outerIndex--) {
      words = [];

      partials.push(words);

      for (let j = minLength - 1; j > outerIndex; j--) {
        words.unshift(0);
      }

      for (let innerIndex = maxLength - 1; innerIndex >= 0; innerIndex--) {
        product = min[outerIndex] * max[innerIndex] + carry;

        keep = product % BigUint.HALF;
        carry = (product - keep) / BigUint.HALF;

        words.unshift(keep);
      }

      if (carry > 0) {
        words.unshift(carry);

        carry = 0;
      }
    }

    sum = BigUint.from(partials.pop());

    while (partials.length > 0) {
      sum = sum.add(BigUint.from(partials.pop()));
    }

    return sum;
  }
}

const a = document.getElementById('a');
const b = document.getElementById('b');
const c = document.getElementById('c');
const op = document.querySelector('select');

function calculate() {
	c.textContent = new BigUint(a.value)[op.value](new BigUint(b.value));
}

document.addEventListener('input', calculate);

calculate();
&#13;
<input id="a" type="number" min="0" step="1" value="41962049">
<select>
  <option value="add">+</option>
  <option value="multiply" selected>&times;</option>
</select>
<input id="b" type="number" min="0" step="1" value="1827116622">
=
<span id="c"></span>
&#13;
&#13;
&#13;