8
\$\begingroup\$

Given a floating point x (x<100), return e^x and ln(x). The first 6 decimal places of the number have to be right, but any others after do not have to be correct.

You cannot have any "magic" constants explicitly stated (e.x. a=1.35914 since 1.35914*2 ~= e), but you can calculate them. Only use +, -, *, and / for arithmetic operators.

If x is less than or equal to 0, output ERROR instead of the intended value of ln(x).

Test Cases:

input: 2
output: 7.389056 0.693147
input: 0.25
output: 1.284025 -1.386294
input: 2.718281828
output: 15.154262 0.999999  (for this to output correctly, don't round to 6 places)
input: -0.1
output: 0.904837 ERROR

Shortest code wins.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ I assume you mean to exclude exponentiation of entirely (not just for the the calculation of e itself)? 2**(x*1.442695) for example seems a bit too easy. \$\endgroup\$
    – primo
    Commented Nov 25, 2012 at 18:04
  • \$\begingroup\$ @primo: yes, there is no exponentiation whatsoever, but things like product([2]*3) is ok. I guess I should rule out "magic constants" in the case of 1.442695. \$\endgroup\$
    – beary605
    Commented Nov 25, 2012 at 18:35
  • 1
    \$\begingroup\$ You might want to also specify a valid range for the input - any approximation can lose accuracy if x becomes too large or too small. \$\endgroup\$
    – arshajii
    Commented Nov 26, 2012 at 0:08
  • \$\begingroup\$ @A.R.S.: added a boundary. Thanks. \$\endgroup\$
    – beary605
    Commented Nov 26, 2012 at 0:14
  • 1
    \$\begingroup\$ No one uses APL!? Seriously? \$\endgroup\$
    – TwiNight
    Commented Dec 25, 2012 at 19:45

5 Answers 5

10
\$\begingroup\$

PHP 109 107 bytes

<?for($g=1e4;--$g;$l=$y/$g+abs($y=$x>1?1-1/$x:$x-1)*$l)$e=1+$e/$g*$x.=fgets(STDIN);echo"$e ",$x>0?$l:ERROR;

\$e^x\$ is a fairly straight-forward calculation. I use a nested form of the sum of inverse factorials, which not only increases the convergence rate, but also allows for exponentiation at the same time:

$$e^x = 1 + x \left( 1 + \frac x 2 \left( 1 + \frac x 3 \left( 1 + \frac x 4 \bigg( 1 + \dots \bigg)\right)\right)\right)$$

\$\ln x\$ is slightly more complicated. All convergent series seem to work for \$x \le 1\$ or \$x \ge 1 \$, but not both (Newton's iteration does not have this limitation, but requires the calculation of \$e^{x_n}\$ each step). This isn't really a problem, though, given the log identity:

$$\ln x = -\ln \frac 1 x$$

This means that if, for example, the iteration you're using only works on \$0 < x \le 1\$ and \$x > 1\$, you can use the multiplicative inverse of \$x\$ and negate the result. Because I was using a nested identity for \$e^x\$, I also chose to use a nested identity for \$\ln x\$:

$$\ln(1 + x) = x\left(1 - x\left(\frac 1 2 - x\left(\frac 1 3 - x\left(\frac 1 4 - \dots \right)\right)\right)\right)$$

where \$0 < x \le 1\$

Or equivalently, as demonstrated by Paul Walls' implementation:

$$\ln(1 + x) = x - x\left(\frac x 2 - x\left(\frac x 3 - x\left(\frac x 4 - \dots \right)\right)\right)$$

I define the \$0 < x \le 1\$ case as \$x-1\$ (which is necessarily negative), using the absolute value for the inner product, and then allowing a bare \$x\$ value in the fraction to correct the sign.

Sample I/O:

$ echo 2 | php exp_ln.php
7.3890560989307 0.69314718055995

$ echo 0.25 | php exp_ln.php
1.2840254166877 -1.3862943611199

$ echo 2.718281828 | php exp_ln.php
15.154262234523 0.99999999983113

$ echo -0.1 | php exp_ln.php
0.90483741803596 ERROR

Perl 95 93 89 bytes

$e=1+$e/$?*($x.=<>),$l=$_/$?+$l*abs,$_=$x>1?1-1/$x:$x-1while--$?;print"$e ",$x>0?$l:ERROR

Nearly identical to the PHP solution above, with a slightly larger iteration (65535 down to 0).

Edits:

  • Both 2 byte improvements due to Paul Walls.
  • Four more bytes saved in Perl by (ab)using $?, which is stored internally as an unsigned short, and by using $_ to save parentheses in abs.
\$\endgroup\$
2
  • \$\begingroup\$ For x = 1e-4, I get php "1.0001000050002 -8.9909564381939" and perl "1.00010000500017 -8.99099322429857". Should be like "1.000100005 -9.21034037195" Anyways nice job taking advantage of ln x = -ln 1/x to use a nested expression \$\endgroup\$
    – miles
    Commented Nov 26, 2012 at 13:49
  • \$\begingroup\$ @milest x=1e-5 will be off by even more, because it's the same calculation as x=100000. As it is, x=1e-2 and x=100 are accurate to full floating point precision, and x=1e-3 and x=1000 are accurate to about 5 digits. If more accuracy is needed, the arbitrary 1e4 bound that I've chosen can be increased accordingly. \$\endgroup\$
    – primo
    Commented Nov 26, 2012 at 14:25
4
\$\begingroup\$

Python 2 (168 char)

basic implementation of power series

I need to learn a golfing language =/

I increased the bound to 1e-14 (twice from 1e-7) since some values were off a bit in 6 decimal places, works well for input from 1e-5 to 100 (slows down at input approaches 0)

x=input();t,r,i=1,0,1.
while abs(t)>=1e-14:t,r,i=t*x/i,r+t,i+1
if x<=0:s='Error'
else:z=(x-1.)/(x+1);t,s,i=2*z,0,1
while abs(t/i)>=1e-14:t,s,i=t*z*z,s+t/i,i+2
print r,s
\$\endgroup\$
3
  • \$\begingroup\$ Doesn't python support m=1e-7? \$\endgroup\$ Commented Nov 25, 2012 at 20:53
  • \$\begingroup\$ yes it does but 1e-7 can be thought of as 1*10^-7 which might be thought of as exponentiation, so I'm not really sure if its allowed here \$\endgroup\$
    – miles
    Commented Nov 25, 2012 at 20:59
  • \$\begingroup\$ I will allow for scientific notation. \$\endgroup\$
    – beary605
    Commented Nov 26, 2012 at 0:02
4
\$\begingroup\$

Haskell, 166 (89 without I/O)

s=1e-7
e x|x>s=1/e(-x)|x>0=1+x|y<-e$x+s=y-y*s
l x|x<1=0-l(1/x)|1>0=sum$map(s/)[1,1+s..x]
n x|x<0="ERROR"|1>0=show$l x
main=interact$unwords.(\x->[show$e x,n x]).read

Takes an alternative approach: we use that

∂/∂x ex = ex

and solve the differential equation numerically, with a simple euler method. Similarly, use

ln x = 1x 1/x d‌x

and calculate the integral with the rectangular method.

Example:

$ echo 2 | ./exp-and-log
7.389056835370484 0.6931472554471929
$ echo 0.25 | ./exp-and-log
1.284025432730133 -1.3862944228194163
$ echo 2.718281828 | ./exp-and-log
15.15426430695358 1.0000000576151333
$ echo -0.1 | ./exp-and-log
0.9048374135134576 ERROR

It's quite amazingly inefficient, in fact it uses about 4 GB of memory even for these examples (since it's non-tail–recursive... you need to compile (in GHC) with -with-rtsopts=-K2G so it even accepts such a ridiculous stack size).

\$\endgroup\$
3
\$\begingroup\$

JavaScript, 103 101 99 97 93 90

This implementation is based on primo's comprehensive description of the algorithm he used.

for(e=l=x=prompt(g=1e5);--g;y=x-1,l=(x>1?y/=x:-y)*l+y/g)
    e=1+e/g*x;
alert([e,x>0?l:"ERROR"])

Edit:
- Trying to catch Perl. Stole a byte back from primo by copying his branch. :)
- 2 more bytes courtesy of primo.
- Finally, caught primo's version! With primo's help, of course... :)
- Simplified the assignment to L. Shaved 3 more bytes.

\$\endgroup\$
6
  • \$\begingroup\$ It's somewhat humorous that defining Math.abs inline is actually shorter than using the built-in. \$\endgroup\$
    – primo
    Commented Nov 27, 2012 at 7:39
  • \$\begingroup\$ As far as catching perl goes, would it help if I pointed out that the initial values of e and l don't really matter? \$\endgroup\$
    – primo
    Commented Dec 2, 2012 at 6:58
  • \$\begingroup\$ @primo Nice catch! \$\endgroup\$
    – Paul Walls
    Commented Dec 2, 2012 at 8:13
  • \$\begingroup\$ I've got 93, by combining the y and l definitions: l=(x>1?y=1-1/x:-(y=x-1))*l+y/g \$\endgroup\$
    – primo
    Commented Dec 14, 2012 at 7:29
  • \$\begingroup\$ Very elegant. I probably spent over an hour trying to figure out a way to exploit the relationship between x<1 and y<0. You sir, are a genius! :) \$\endgroup\$
    – Paul Walls
    Commented Dec 14, 2012 at 9:37
1
\$\begingroup\$

R (101 bytes)

Note that $$e^{x} = \lim_{n \to \infty}\left(1 + \frac{x}{n}\right)^n.$$ Then, for large enough n, we can approximate e by a product.

n=1e+08;prod(rep(1+x/n,n))

Similarly, note that $$\log(x) = \int_{1}^{x}\frac{1}{t}dt \leq \Delta t \cdot \sum \frac{1}{t+\Delta t}$$

dt=0.1;sign(x-1)*sum((1/seq(min(1,x),max(1,x),by=dt))*dt))

Combined into one answer (80 bytes):

n=1e+08;c(prod(rep(1+x/n,n)),sign(x-1)*sum((1/seq(min(1,x),max(1,x),by=1/n))/n))

Will produce -Inf instead of ERROR in case x < 0.

[1] 7.3890560 0.6931472
[1]  1.284025 -1.386294
[1] 15.15426  1.00000
[1] 0.9048374      -Inf

Or, with the ERROR handling, 101 bytes:

n=1e+08;gsub("-Inf","ERROR",c(prod(rep(1+x/n,n)),sign(x-1)*sum((1/seq(min(1,x),max(1,x),by=1/n))/n)))
[1] "7.38905602540599"  "0.693147188059945"
[1] "1.28402541433558"  "-1.38629438611989"
[1] "15.154261581541"  "1.00000000372749"
[1] "0.904837420549773" "ERROR"   
\$\endgroup\$
1
  • \$\begingroup\$ A TIO link would be great. \$\endgroup\$
    – user7467
    Commented Oct 27, 2021 at 7:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.