【问题标题】:Is it possible to handle integer overflow without an external library in JavaScript? [duplicate]是否可以在没有 JavaScript 外部库的情况下处理整数溢出? [复制]
【发布时间】:2016-11-12 20:15:34
【问题描述】:

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

输入:41962049 * 1827116622

输出:76669557221078480

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

我假设这是一个整数溢出问题,但我肯定是错的。

有没有一种方法可以在不使用 BigInteger 之类的外部库的情况下在 Javascript 中获得大数的精确算术乘积?这是针对不允许附加库的在线课程。

感谢您的帮助。

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

编辑 2:jfriend00,我认为这个问题与您链接的问题不同,因为我试图弄清楚是否有一种方法可以在不依赖外部库的情况下解决 JS 的限制。您在 cmets 中提供的答案帮助回答了我的问题,谢谢!

【问题讨论】:

标签: javascript node.js algorithm ieee-754


【解决方案1】:

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

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

在 252=4,503,599,627,370,496 和 253=9,007,199,254,740,992 之间,可表示的数字恰好是整数。对于下一个范围,从 253 到 254,所有内容都乘以 2,因此可表示的数字是偶数等。相反,对于上一个范围,从251到252,间距为0.5等

作为从 2n 到 2n+1 范围内的数字的一部分的间距是 2n−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();
<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>

【讨论】:

  • Patrick,这太棒了,谢谢。我会将它修改为在 CLI 中工作以提交给我班的评分者。我使用的解析stdinstdout的模板是:process.stdin.setEncoding('utf8'); var data = "" function leastCM(a, b) { //code } process.stdin.on('end', function() { var input = data.split(" "); var a = parseInt(input[0], 10); var b = parseInt(input[1], 10); console.log(leastCM(a, b)); process.exit(); }) process.stdin.on('readable', function(){ new_data = process.stdin.read(); if (new_data !== null) { data = data + new_data } });
  • @AnyaGlowa-Kollisch 有了这个,您可以使用var a = new BigUint(input[0])var b = new BigUint(input[1]),并使用乘法确定最小公倍数。如果这会让你的生活更轻松,我可以尝试实现任意精确除法,但你必须等到今晚。
  • 非常感谢。你是最棒的,帕特里克!
  • @AnyaGlowa-Kollisch 我按照承诺尝试了,但不幸的是我无法获得正确的算法。我会在本周晚些时候有更多时间时再试一次。
猜你喜欢
  • 1970-01-01
  • 2011-04-21
  • 1970-01-01
  • 2011-10-26
  • 1970-01-01
  • 1970-01-01
  • 2021-07-02
  • 2013-06-02
  • 1970-01-01
相关资源
最近更新 更多