BigInt 的对数

Logarithm of a BigInt

有没有办法得到 JavaScript 中 BigInt 的对数?

对于普通数字,您将使用此代码:

const largeNumber = 1000;
const result = Math.log(largeNumber);

但是,我需要使用阶乘数,可能高于 170!所以常规数字类型不起作用。 Math.log 不适用于 BigInt。那么如何得到对数呢?

const largeNumber = BigInt(1000);
const result = ???

你能看看这对你有用吗?函数returns一个BigInt.

function log10(bigint) {
    const n = bigint.toString(10).length;
    return bigint > 0n ? BigInt(n - 1) : null;
}

const largeNumber = BigInt('9039845039485903949384755723427863486200719925474009384509283489374539477777093824750398247503894750384750238947502389475029384755555555555555555555555555555555555555554444444444444444444444444222222222222222222222255666666666666938475938475938475938408932475023847502384750923847502389475023987450238947509238475092384750923847502389457028394750293847509384570238497575938475938475938475938475555555555559843991')

console.log(log10(largeNumber).toString())

Log2 分别是:

 const largeNumber = BigInt('9039845039485903949384755723427863486200719925474009384509283489374539477777093824750398247503894750384750238947502389475029384755555555555555555555555555555555555555554444444444444444444444444222222222222222222222255666666666666938475938475938475938408932475023847502384750923847502389475023987450238947509238475092384750923847502389457028394750293847509384570238497575938475938475938475938475555555555559843991')

    function log2(bigint) {
        const n = bigint.toString(2).length;
        return bigint > 0n ? BigInt(n - 1) : null;
    }
    
    console.log(log2(largeNumber).toString())

如果您不想 return BigInt,那么以下方法也可能对您有用:

function log10(bigint) {
  if (bigint < 0) return NaN;
  const s = bigint.toString(10);

  return s.length + Math.log10("0." + s.substring(0, 15))
}

function log(bigint) {
  return log10(bigint) * Math.log(10);
}

function natlog(bigint) {
  if (bigint < 0) return NaN;

  const s = bigint.toString(16);
  const s15 = s.substring(0, 15);

  return Math.log(16) * (s.length - s15.length) + Math.log("0x" + s15);
}

const largeNumber = BigInt('9039845039485903949384755723427863486200719925474009384509283489374539477777093824750398247503894750384750238947502389475029384755555555555555555555555555555555555555554444444444444444444444444222222222222222222222255666666666666938475938475938475938408932475023847502384750923847502389475023987450238947509238475092384750923847502389457028394750293847509384570238497575938475938475938475938475555555555559843991');

console.log(natlog(largeNumber)); // 948.5641152531601
console.log(log10(largeNumber), log(largeNumber), log(-1))
// 411.95616098588766
// 948.5641152531603
// NaN

log10() 将为您作为参数输入的任何 BigInt 或 Int 数字 return 一个标准精度浮点数。


正如@Mielipuoli 非常正确地提到的,自然对数可以计算为

function log(bigint) {
  return log10(bigint) / Math.log10(Math.E);
}

或者,更简单,如我上面的代码片段所示,如 log10(bigint) * Math.log(10)

@Nat 已经在下面的评论中解释了这种方法的工作原理,即分别计算对数的整数和小数部分并将它们相加。关于结果的精度:Math.log10() 以其通常的 13 到 14 位十进制数字精度处理浮点数,因此,对于结果,这也是您所期望的。

出于这个原因,我将 BigInt 数字的字符串表示形式截断为 15 个字符。在隐式类型转换为 float 时,任何其他小数位都将被忽略。

我还在此处添加了十六进制字符串版本,由@PeterCordes 建议并由@somebody 进一步开发为natlog()。它工作 - 可能比我原来的解决方案更快 - 并产生“相同”的结果(只有最后显示的数字在两个结果之间有偏差)!

受 MWO 回答的启发,您可以简单地将 BigInt 转换为与您要计算的对数具有相同底数的字符串,并获得字符串长度。

例如要计算 floor(log2(9007199254740991)) 你可以 BigInt("9007199254740991").toString(2).length - 1.

注意 toString 只允许从 2 到 36 的碱基。

其他答案已经充分解决了您在标题中提出的问题,即:“我如何计算 BigInt 的对数?”。但是,您还提到您对阶乘的对数特别感兴趣,对此不同的算法避免了范围困难。

应用 log(ab) = log(a) + log(b),以下函数计算阶乘的对数:

function logFactorial(n) {
  let total = 0;
  for (let current = 1; current <= n; ++current) {
    total += Math.log10(current);
  }

  return total;
}

console.log(logFactorial(170));

根据我之前的评论,如果有人发现自己正在寻找一个非常高精度的对数,可以使用几个提供此功能的大十进制包。例如,下面的代码片段使用 decimal.js 来计算 1000 位的精度...

  • 170!使用 BigInt 验证 170!使用 decimal.js
  • 170!使用 decimal.js
  • ln( 170!)
  • log10( 170!)
  • exp(ln(170!))
  • round( exp( ln( 170! ) ) )

<style>
textarea {
    width: 100%;
    height: 100vh;
}
</style>

<textarea id=result width:"100%" height:"100vh"></textarea>

<script src="https://cdnjs.cloudflare.com/ajax/libs/decimal.js/10.3.1/decimal.min.js"></script>

<script>

let result = document.getElementById( 'result' );

Decimal.precision = 1000;
Decimal.toExpPos = 1000;


b = BigInt( 1 );
d = new Decimal( 1 );
for ( let di = 2, bi = 2n; di <= 170; di++, bi++ ) {
  d = Decimal.mul( d, di );
  b = b * bi;
}

result.value = `BigInt 170! = ${b}\n\n`;
result.value += `decimal.js 170! = ${d.toString()}\n\n`;

result.value += `ln( 170! ) = ${Decimal.ln( d ).toString()}\n\n`;
result.value += `log10( 170! ) = ${Decimal.log10( d ).toString()}\n\n`;

result.value += `exp( ln ( 170! ) ) = ${Decimal.exp( Decimal.ln( d ) ).toString()}\n\n`;
result.value += `round( exp( ln ( 170! ) ) ) = ${Decimal.round( Decimal.exp( Decimal.ln( d ) ) ).toString()}\n\n`;
  
</script>

顺便说一句,有趣的是,即使是 1000 位数字,仍然存在舍入误差。通常情况下,人们会通过包括更多“隐藏”的小数位来进行一些加法精度的计算,然后四舍五入到所需的精度。

如果是纯字符串形式,我的就偷懒

- log()    here means natural-log ln()
- length() here means string length, sometimes called len()

- input bigint_str x

     ( length(x) * log(10) ) + log( "0." x )

单行,没有循环,没有递归,没有专门的bigint库——什么都没有。

当然,它的精度上限为 IEEE 64-bit double precision FP,因此它精确到 15 or so 有效小数 digits

因为一个在前面加上“0”。在第二部分,该部分不会溢出或下溢,除非您的字符串太长,例如比如超过 500k 位等

如果是这样,trim 它下降到前 300 位左右 - 这已经足够了,因为总的来说,它由描述数量级的左侧术语主导,右侧侧仅执行较小的精度调整