【问题标题】:Calculate logarithm by hand手动计算对数
【发布时间】:2018-05-14 17:01:03
【问题描述】:

我想“手动”计算mathematical logarithm...

...其中 代表logarithmBase, 代表值。


一些例子(见Log calculator):

The base 2 logarithm of 10 is 3.3219280949

The base 5 logarithm of 15 is 1.6826061945

...

但是 - 我不想使用像 Math.ceil, Math.log, Math.abs, ... 这样已经实现的函数调用,因为我想要一个干净的原生解决方案,只处理 +-*/ 和一些循环。

这是我目前得到的代码:

function myLog(base, x)  {
  let result = 0;
  do {
    x /= base;
    result ++;
  } while (x >= base)
  return result;
}

let x = 10,
    base = 2;

let result = myLog(base, x)
console.log(result)

但上面的方法似乎不是计算 以 N 为底的对数的正确方法 - 所以任何关于如何修复此代码的帮助将不胜感激。

在此先感谢一百万乔纳斯。

【问题讨论】:

  • 您可以:1 找出插入时最接近答案的两个整数。2 将两者中较小的一个放在一个文本/字符串变量。 3 递归地重复这两个到所需的小数点,将新数字附加到旧数字的末尾。
  • 其实你在正确的轨道上。您只需要 result += precision 然后降低精度,例如precision /= 10;
  • @Sean 除了第一个步骤外,我可以按照您的所有步骤进行操作。 “找出最接近答案的两个整数”对你来说意味着什么。你介意向我解释一下这个说法吗?或者分享一些我能理解的伪代码? :)
  • @JonasW。你想让我降低精度 inside do-while 循环还是 afterward 然后为所有进动重复循环?你也介意分享一些pseudo / js code吗? 编辑:添加 - 'precision'的起始值是多少?
  • 请注意,实现单个基数就足够了,因为其他基数中的值只是成比例的。

标签: javascript math numbers integer logarithm


【解决方案1】:

第一种方法:使用常量表。

首先将参数标准化为 1 到 2 之间的数字(这是通过根据需要多次乘以或除以 2 来实现的 - 记录这些操作的计数)。为了提高效率,如果值可以跨越多个数量级,则可以使用平方序列 2、4、16、256...,而不是相等的因子,然后在将值括起来后进行二分搜索。

F.i.如果指数 16=2^4 有效但 256=2^8 无效,则尝试 2^6,然后根据结果尝试 2^5 和 2^7 之一。如果最终指数为 2^d,则线性搜索需要 O(d) 操作,而几何/二分法搜索只需 O(log d)。为避免分裂,建议保留一张负面权力表。

归一化后,需要对尾数进行细化。将该值与 √2 进行比较,如果更大,则乘以 1/√2。这使值介于 1 和 √2 之间。然后比较√√2等等。在进行过程中,当比较返回更大时,将权重 1/2、1/4、... 添加到指数。

最后,指数是以 2 为底的对数。

示例:lg 27

27 = 2^4 x 1.6875

1.6875 > √2 = 1.4142 ==> 27 = 2^4.5 x 1.1933

1.1933 > √√2 = 1.1892 ==> 27 = 2^4.75 x 1.0034

1.0034 < √√√2 = 1.0905 ==> 27 = 2^4.75 x 1.0034

...

真实值为4.7549。

请注意,您可以使用其他基础,尤其是 e。在某些情况下,base 2 允许使用快捷方式,这就是我使用它的原因。当然,平方根应该是表格。

第二种方法:泰勒级数。

标准化步骤后,您可以使用标准系列

log(1 + x) = x - x²/2 + x³/3 - ...

收敛于|x| &lt; 1。 (注意:我们现在有了自然对数。)

由于接近 1 的值收敛太慢,建议使用上述方法减小到范围 [1, √2)。然后每个新术语都会带来新的准确性。

或者,您可以使用 log((1 + x)/(1 - x)) 的系列,即使对于参数 2,它也能提供良好的收敛速度。请参阅 https://fr.wikipedia.org/wiki/Logarithme_naturel#D%C3%A9veloppement_en_s%C3%A9rie

示例:x = 1.6875,y = 0.2558 和

2 x (0.2558 + 0.2558³/3 + 0.2558^5/5) = 0.5232

lg 27 ~ 4 + 0.5232 / ln 2 = 4.7548

【讨论】:

    【解决方案2】:

    您可以使用递归方法:

     const log = (base, n, depth = 20, curr = 64, precision = curr / 2) => 
       depth  <= 0 || base ** curr === n 
          ? curr
          : log(base, n, depth - 1, base ** curr > n ? curr - precision : curr + precision, precision / 2);
    

    可用作:

    log(2, 4) // 2
    log(2, 10) // 3.32196044921875
    

    您可以通过更改depth 来影响精度,并且您可以通过curr 更改接受值的范围(目前约为180)


    它是如何工作的:

    如果我们已经达到了想要的深度,或者我们已经找到了一个准确的值:

     depth  <= 0 || base ** curr === n 
    

    然后它只返回curr 并完成。否则,它会检查我们想要找到的对数是低于还是高于当前的对数:

             base ** curr > n
    

    然后它将继续递归地搜索一个值 1) 将depth 降低一 2)按当前精度增加/减少curr 3) 精度较低


    如果你讨厌函数式编程,这里有一个命令式的版本:

      function log(base, n, depth = 20) {
       let curr = 64, precision = curr / 2;
       while(depth-- > 0 && base ** curr !== n) {
         if(base ** curr > n) {
           curr -= precision;
         } else {
           curr += precision;
         }
         precision /= 2;
        }
        return curr;
      }
    

    顺便说一下,我使用的算法叫做"logarithmic search",俗称“二分查找”。

    【讨论】:

    • 感谢您的回答。但是,我无法真正了解您的代码背后的逻辑。 (不应该听起来粗鲁)。什么是深度?为什么我改变深度和电流的价值观不同?您是否介意添加第二种方法,就像您在 cmets 中提到的那样?就像使用我的代码,但只是通过添加精度属性来改进它? :)
    • @jonas00 实际上它直到现在才工作:) 我会检查它现在是否工作,他们会添加一些 cmets
    • 这解释了很多:)。但是 - 如果你喜欢的话,我会很高兴有几行伪代码,因为我以后不会使用 javascript,而是使用不同的语言。暗示,在某些时候我必须从 js 翻译你的答案代码。对于您的代码的速记来说,这将是极其困难的。 (比如 do-while 等)
    • @jonas00 就是这样:)
    • 使用**操作符是作弊,因为它依赖于日志和exp。
    猜你喜欢
    • 2016-05-09
    • 2014-07-17
    • 2017-07-17
    • 2018-12-21
    • 1970-01-01
    • 2015-01-28
    • 2017-12-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多