JavaScript中的模数运算 - 大数问题

21

我尝试使用JS的模运算函数进行计算,但没有得到正确的结果(应为1)。以下是硬编码代码片段。

var checkSum = 210501700012345678131468;
alert(checkSum % 97);

Result: 66

这里有什么问题?

谢谢, Benedikt

6个回答

19

从一个普通的银行账号计算IBAN,我得到了一个包含在字符串数据类型中的非常大的数字。从这个大数字中,我需要找出除以97的余数 -> 大数字 % 97。

一旦我将数据类型转换为整数,就会发生溢出,导致负整数和最终错误的余数值。由于我看到了一些冗长的代码(它们也产生了错误的结果),所以我无法抵制分享自己的代码。感谢Finding Modulus of a Very Large Number with a Normal Number的作者。

modulo: function(divident, divisor) {
    var partLength = 10;

    while (divident.length > partLength) {
        var part = divident.substring(0, partLength);
        divident = (part % divisor) +  divident.substring(partLength);          
    }

    return divident % divisor;
}

注意:我在这里使用了10个位置,因为它比JavaScript中的最大整数15(及其一些)的位置要小,它得出的数字比97大,并且是一个不错的整数。前两个参数很重要。


这绝对是我在网上找到的最佳解决方案。数学证明,兼容每个浏览器,简短易读,适用于IBAN检查的简单应用程序。 - Kar.ma
信用中的文章超链接已更改。https://www.devx.com/tip-bank/39012/ 从文章中:“假设您需要找到nBig%a:取输入大数(nBig)的可管理部分(例如,x)并从nBig中减去它(nBig = nBig-x)。 计算x的模(mod = x%a)。 将mod添加到nBig中(nBig = nBig + mod)。 这样,可以缩小数字nBig而不影响实际答案。” - UpTide

12
对Benedikt版本进行了一系列改进:`cRest += '' + cDivident;`是一个错误修复;`parseInt(divisor)`使得可以将两个参数都作为字符串传递;在结尾处检查空字符串使其始终返回数值;添加了var语句,以避免使用全局变量;将foreach转换为旧式的for循环,以便在使用旧版JavaScript的浏览器中正常工作;修复了`cRest == 0;`的错误(感谢@Dan.StackOverflow)。
function modulo(divident, divisor) {
  let cDivident = '';
  let cRest = '';

  for (let i in divident) {
    let cChar = divident[i];
    let cOperator = cRest + '' + cDivident + '' + cChar;

    if (cOperator < parseInt(divisor)) {
      cDivident += '' + cChar;
    } else {
      cRest = cOperator % divisor;
      if (cRest == 0) {
        cRest = '';
      }
      cDivident = '';
    }
  }
  cRest += '' + cDivident;
  if (cRest == '') {
    cRest = 0;
  }
  return cRest;
}

1
小问题/笔误。最后的 'cRest == 0' 应该改为 'cRest = 0'。 - Dan.StackOverflow
请注意,这仍然不能处理大约2 ** 50左右的数字。不确定如何处理,除非使用Python,其中2 ** 99999%7可以正常工作。(注意,'**'表示指数运算,因此2 ** 8 == 256)。 - Luc

10

对于那些只想复制粘贴ES6中可用的IBAN检查解决方案的人:

function isIBAN(s){
    const rearranged = s.substring(4,s.length) + s.substring(0,4);
    const numeric   = Array.from(rearranged).map(c =>(isNaN(parseInt(c)) ? (c.charCodeAt(0)-55).toString() : c)).join('');
    const remainder = Array.from(numeric).map(c => parseInt(c)).reduce((remainder, value) => (remainder * 10 + value) % 97,0);

    return  remainder === 1;}

你甚至可以把它写成一行代码。
取模运算是在存储实际数字的整数数组上执行的(被应用为函数字符串的除数)。
function modulo(divident, divisor){
   return Array.from(divident).map(c => parseInt(c)).reduce((remainder, value) => (remainder * 10 + value) % divisor,0);
};

这是因为取模运算在加法、减法和乘法中都具有分配律:
  • (a+b)%m = ((a%m)+(b%m))%m
  • (a-b)%m = ((a%m)-(b%m))%m
  • (ab)%m = ((a%m)(b%m))%m
将 IBAN 函数转换为 ES5 后的代码如下:
function (s) {
    var rearranged = s.substring(4, s.length) + s.substring(0, 4);
    var numeric = Array.from(rearranged).map(function (c) { return (isNaN(parseInt(c)) ? (c.charCodeAt(0) - 55).toString() : c); }).join('');
    var remainder = Array.from(numeric).map(function (c) { return parseInt(c); }).reduce(function (remainder, value) { return (remainder * 10 + value) % 97; }, 0);
    return remainder === 1;
};

5

看起来你已经成为这个问题的受害者:JavaScript 的 Number 类型能够表示的最大整数是多少?

简单概括下其他线程中的内容:

它们是 64 位浮点值,最大的精确整数值是 2^53。但是,根据规范 [8.5: Number Type] 的描述:

一些 ECMAScript 运算符仅处理范围介于 -2^31 到 2^31-1(包括)或 0 到 2^32-1(包括)之间的整数。这些运算符接受 Number 类型的任何值,但首先将每个这样的值转换为 2^32 个整数值之一。请参见第 0 和第 0 节中对 ToInt32 和 ToUint32 运算符的描述。

不过功劳归功于他人。Jimmy 在那边做了功课(嗯,谷歌)。


1
那么,这个问题有解决办法吗?我在谷歌上找不到有用的信息。 - Benedikt
你不能做除法并计算余数吗? - gnarf
考虑到上面给出的校验和已经超过了2^53,您需要在执行任何操作之前对数字进行一些更有趣的分割。 - Jonathan Fingland

4

最终,我的解决方案:

function modulo (divident, divisor) {
    cDivident = '';
    cRest = '';

    for each ( var cChar in divident ) {
        cOperator = cRest + '' + cDivident + '' + cChar;

        if ( cOperator < divisor ) {
            cDivident += '' + cChar;
        } else {
            cRest = cOperator % divisor;
            if ( cRest == 0 ) cRest = '';
            cDivident = '';
        }

    }

    return cRest;
}

3

Silent Matt开发了一个JavaScript ,用于处理大整数。它也可以解决这个问题。


但是这个库没有模运算功能,对吧? - Benedikt
2
是的,可以看一下divRem或remainder。 - Jérôme Verstrynge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接