1

I'd like to calculate the mathematical logarithm "by hand"...

... where stands for the logarithmBase and stands for the value.


Some examples (See Log calculator):

The base 2 logarithm of 10 is 3.3219280949

The base 5 logarithm of 15 is 1.6826061945

...

Hoever - I do not want to use a already implemented function call like Math.ceil, Math.log, Math.abs, ..., because I want a clean native solution that just deals with +-*/ and some loops.

This is the code I got so far:

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)

But it doesn't seems like the above method is the right way to calculate the logarithm to base N - so any help how to fix this code would be really appreciated.

Thanks a million in advance jonas.

  • You could: **1** Find the two whole numbers that get closest to the answer when plugged in. **2** Put the smaller of the two in a text/String variable. **3** recursively repeat the two to the desired decimal point, appending the new numbers to the end of the old ones. – Sean May 14 '18 at 17:03
  • Actually you are on the right track. You just need `result += precision` and then lower the precision, e.g. `precision /= 10;` – Jonas Wilms May 14 '18 at 17:07
  • @Sean I can follow all of your steps except the first one. What to you mean with **"Find the two whole numbers that get closest to the answer"**. Would you mind to explain this statement to me? Or share some lines pseudoCode I'm able to understand? `:)` –  May 14 '18 at 17:11
  • @JonasW. Do you want me to lower the precision **inside** the do-while loop or **afterward** and then repeat the loop for all precessions? Would also you mind to share some `pseudo / js code`? **Edit**: To add on - what is the **start value** of 'precision'? –  May 14 '18 at 17:12
  • @jonas00 in your case `1`, then if the `while` loop finishes but does not reach an accurate result, change it to `0.1` and repeat and so on – Jonas Wilms May 14 '18 at 17:18
  • @jonas00 basically what Jonas W. said, I’ll explain it in pseudocode later today if I remember. – Sean May 14 '18 at 18:14
  • OK sound nice, thanks in advance @Sean. –  May 14 '18 at 20:39
  • see [Building a logarithm function in C without using float type](https://stackoverflow.com/a/42108287/2521214) binary search is your friend – Spektre May 15 '18 at 06:41
  • Possible duplicate of [Building a logarithm function in C without using float type](https://stackoverflow.com/questions/42107700/building-a-logarithm-function-in-c-without-using-float-type) – Spektre May 15 '18 at 06:44
  • 1
    Note that it suffices to implement for a single base, as the values in other bases are just proportional. –  May 15 '18 at 07:38

2 Answers2

0

You could use a recursive approach:

 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);

Usable as:

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

You can influence the precision by changing depth, and you can change the range of accepted values (currently ~180) with curr


How it works:

If we already reached the wanted depth or if we already found an accurate value:

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

Then it just returns curr and is done. Otherwise it checks if the logarithm we want to find is lower or higher than the current one:

         base ** curr > n

It will then continue searching for a value recursively by 1) lowering depth by one 2) increasing / decreasing curr by the current precision 3) lower precision


If you hate functional programming, here is an imperative version:

  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;
  }

By the way, the algorithm i used is called "logarithmic search" commonly known as "binary search".

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Thanks for your answer. But tbh I can not really get the logic behind your code. *(is not supposed to sound rude)*. What is depth? And why are the values that different my changing depth & curr? Would you mind to add a second approach like you've mentioned in the comments? Like using my code but just improve it by adding the precision property? `:)` –  May 14 '18 at 17:46
  • @jonas00 actually it wasnt working until now :) i will check if it works now and them add some comments – Jonas Wilms May 14 '18 at 17:48
  • That explains a lot `:)`. However - If you feel like it, I would be glad about some lines of pseudo code, because I will not use javascript later, but a different language. By implication, at some point I have to translate your answer code from js. Which will be extremely difficult for the shorthand of your code. (Like do-while, etc) –  May 14 '18 at 17:48
  • @jonas00 there it is :) – Jonas Wilms May 14 '18 at 17:56
  • Using the ** operator is a cheat, as it relies on log and exp. –  May 14 '18 at 19:49
  • @YvesDaoust `const exp = (n, e) => e == 0 ? 1 : n * exp(n, e - 1)` – Jonas Wilms May 14 '18 at 20:18
  • Many thanks to you Jonas W. I will wait for @Seans answer before I check the answer as right. However - thanks so much. –  May 14 '18 at 20:40
  • @JonasW.: no, you are using fractional powers. –  May 14 '18 at 20:46
  • @yves one can still polyfill this... I'm waiting for your answer :) – Jonas Wilms May 15 '18 at 05:41
  • @JonasW.: polyfill ? –  May 15 '18 at 06:21
0

First method: with a table of constants.

First normalize the argument to a number between 1 and 2 (this is achieved by multiplying or dividing by 2 as many times as necessary - keep a count of these operations). For efficiency, if the values can span many orders of magnitude, instead of equal factors you can use a squared sequence, 2, 4, 16, 256..., followed by a dichotomic search when you have bracketed the value.

F.i. if the exponents 16=2^4 works but not 256=2^8, you try 2^6, then one of 2^5 and 2^7 depending on outcome. If the final exponent is 2^d, the linear search takes O(d) operations and the geometric/dichotomic search only O(log d). To avoid divisions, it is advisable to keep a table of negative powers.

After normalization, you need to refine the mantissa. Compare the value to √2, and if larger multiply by 1/√2. This brings the value between 1 and √2. Then compare to √√2 and so on. As you go, you add the weights 1/2, 1/4, ... to the exponent when a comparison returns greater.

In the end, the exponent is the base 2 logarithm.

Example: 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

...

The true value is 4.7549.

Note that you can work with other bases, in particular e. In some contexts, base 2 allows shortcuts, this is why I used it. Of course, the square roots should be tabulated.

Second method: with a Taylor series.

After the normalization step, you can use the standard series

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

which converges for |x| < 1. (Caution: we now have natural logarithms.)

As convergence is too slow for values close to 1, it is advisable to use the above method to reduce to the range [1, √2). Then every new term brings a new bit of accuracy.

Alternatively, you can use the series for log((1 + x)/(1 - x)), which gives a good convergence speed even for the argument 2. See https://fr.wikipedia.org/wiki/Logarithme_naturel#D%C3%A9veloppement_en_s%C3%A9rie

Example: with x = 1.6875, y = 0.2558 and

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

lg 27 ~ 4 + 0.5232 / ln 2 = 4.7548