I want to solve a problem from Project Euler (BTW, problem 25), and I found a solution in Python:
fibonacci = 1
old1 = 0
old2 = 1
limit = 1000
i = 1
while len(str(fibonacci)) < limit:
fibonacci = old1 + old2
old1 = old2
old2 = fibonacci
i = i + 1
print(i)
It took 1.5 seconds to calculate.
I implemented the same in PHP, this is the code:
$fibonacci = 1;
$old1 = 0;
$old2 = 1;
$limit = 1000;
$i = 1;
while (strlen((string)$fibonacci) < $limit){
$fibonacci = $old1 + $old2;
$old1 = $old2;
$old2 = $fibonacci;
$i = $i + 1;
}
print($i);
And it took more than 30 minutes, and still calculating...
I know that Python is considered faster than PHP, but still it shouldn't be so big a difference. How to improve my PHP code to get the results faster, if there is a way to do it?
EDIT:
I edit this post based on comments below so first my solution was not going to work.
One solution can be instead of old while to put this one:
while (strlen(number_format($fibonacci, 0, '', '')) < $limit){ ... }
But again is a big speed issue.
So the final solution is using BCMath:
$fibonacci = '1';
$old1 = '0';
$old2 = '1';
$limit = 1000;
$i = 1;
while (strlen($fibonacci) < $limit){
$fibonacci = bcadd($old1, $old2);
$old1 = $old2;
$old2 = $fibonacci;
$i = $i + 1;
}
echo $fibonacci . "<br />";
print($i);
So you can get the results at the same speed as Python in PHP.
Definitely, the PHP is going into an infinite loop. There's no way it could be taking that long if there wasn't something wrong...
I don't think counting the digits of these numbers with strlen is going to work in PHP. PHP is dealing with the numbers in scientific notation, in lower precision than Python.
I added debugging echo statements to PHP, to print out $fibonacci and $i for each step.
A typical Python line looks like
fib is 7540113804746346429
i is 92
In PHP, that's
fib is 7.54011380475E+18
i is 92
To accomplish this in PHP, you'll probably need to use a higher precision math library.
Check out http://www.php.net/manual/en/book.bc.php - you can use the bcadd function to accomplish the addition, and it will work as it does in Python.
It isn't a speed issue, it's a logic problem in the while condition for termination.
It's probably not going to finish. When you convert the current value of $fibonacci to a string in your while test, it will be converted to scientific format and truncated to a limited set of decimal places (dependent on your precision setting) when you cast it to string. That number of digits will be a lot less than 1000, so the while termination condition won't ever be met.
The problem is, that you are working with big numbers. You should use BC Math Functions (php.net/bc). So your code can be:
$fibonacci = "1";
$old1 = "0";
$old2 = "1";
$limit = 1000;
$i = 1;
while (strlen($fibonacci) < $limit){
$fibonacci = bcadd($old1, $old2);
$old1 = $old2;
$old2 = $fibonacci;
$i = $i + 1;
}
print($i);
I have tried it and it takes about 0.095s.
Many of project Euler problems will have to handle big numbers.
PHP will make your big numbers look like 2.579234678963E+12 which is the Exponential representation of the number... It's obviously hard to work with.
So, for most of problems, it's best to go with BCMath Functions. This will keep your number as it is, even if it is a giant number.
Note that using echo bcmul(500,500); will never be as fast as echo 500*500. And, BCMath function return values are always strings.
To fix your problem replace all arithmetic operations with the corresponding BCMath function.
I optimized a bit the Python code. Using len(str()) to check the number of digits is very slow. Replaced by math.log10 run your program much faster
The first term in the Fibonacci sequence to contain 1000 digits is : 4782
Calculated in 0.008573 seconds
import time
from math import log10
def digits(n): # Return the number of digits for n>=1
return int(log10(n))+1
fibonacci = 1L # Thanks to Python to handle very big numbers
old1 = 0
old2 = 1
limit = 1000
i = 1
start = time.time() #Start timer for bench
while digits(fibonacci) < limit:
fibonacci = old1 + old2
old1 = old2
old2 = fibonacci
i += 1
print "The first term in the Fibonacci sequence to contain %s digits is : %s" % (str(limit), str(i))
print "Calculated in %3.6f seconds" % (time.time() - start)
Since the problem seems to be with converting to strings, here's a much faster way to do it that doesn't require it. This is essentially the same algorithm as you have posted (so I don't feel bad showing it to you) but demonstrates how to use division to test the length of an integer instead of converting it to a string.
def fibonacci_digits(limit):
limit = 10**limit
fib = 1
old1 = 0
old2 = 1
i = 1
size = 1
while size < limit:
fib = old1 + old2
if not size//fib: # // is pythons integer division operator, not a comment
size *= 10
old1 = old2
old2 = fib
i += 1
return i
print fibonacci_digits(1000)
Converting to a string is slow and is almost never the right thing to do. Here's the timeit results:
$ python -mtimeit -s'import fib' 'fib.fibonacci_digits(1000)'
10 loops, best of 3: 30.2 msec per loop
$ python -mtimeit -s'import fib' 'fib.fibonacci_digits2(1000)'
10 loops, best of 3: 1.41 sec per loop
Related
I am wondering if there is a more elegant way to create a random mix of 4 1's and 0's in PHP. The way I do it works but I am curious if there is a way to do the same thing with less code?
$b1 = rand(0,1);
$b2 = rand(0,1);
$b3 = rand(0,1);
$b4 = rand(0,1);
$randpattern = $b1.$b2.$b3.$b4;
Slightly shorter still:
$randpattern = substr(decbin(rand(16, 31)), -4);
The rand(16,31) will generate a random number between 16 and 31 which is made into a binary number, with decbin(), between 10000 en 11111. Finally the substr() picks only the last four characters.
Sure:
str_pad(decbin(rand(0, 15)), 4, '0', STR_PAD_LEFT);
Call rand() only once. It will give a random number between 0 and 15. 15 in binary is 1111. You can also write 15 in binary to make it clear. rand(0, 0b1111)
Convert into binary.
If number is less than 1000 then left pad it with 0.
You can simply use a loop :
$randpattern = '' ;
while( strlen($randpattern) < 4 )
$randpattern .= rand(0,1);
I have two points (x1 and x2) and want to generate a normal distribution in a given step count. The sum of y values for the x values between x1 and x2 is 1. To the actual problem:
I'm fairly new to Python and wonder why the following code produces the desired result, but about 100x slower than the same program in PHP. There are about 2000 x1-x2 pairs and about 5 step values per pair.
I tried to compile with Cython, used multiprocessing but it just improved things 2x, which is still 50x slower than PHP. Any suggestions how to improve speed to match at least PHP performance?
from scipy.stats import norm
import numpy as np
import time
# Calculates normal distribution
def calculate_dist(x1, x2, steps, slope):
points = []
range = np.linspace(x1, x2, steps+2)
for x in range:
y = norm.pdf(x, x1+((x2-x1)/2), slope)
points.append([x, y])
sum = np.array(points).sum(axis=0)[1]
norm_points = []
for point in points:
norm_points.append([point[0], point[1]/sum])
return norm_points
start = time.time()
for i in range(0, 2000):
for j in range(10, 15):
calculate_dist(0, 1, j, 0.15)
print(time.time() - start) # Around 15 seconds or so
Edit, PHP Code:
$start = microtime(true);
for ($i = 0; $i<2000; $i++) {
for ($j = 10; $j<15; $j++) {
$x1 = 0; $x2 = 1; $steps = $j; $slope = 0.15;
$step = abs($x2-$x1) / ($steps + 1);
$points = [];
for ($x = $x1; $x <= $x2 + 0.000001; $x += $step) {
$y = stats_dens_normal($x, $x1 + (($x2 - $x1) / 2), $slope);
$points[] = [$x, $y];
}
$sum = 0;
foreach ($points as $point) {
$sum += $point[1];
}
$norm_points = [];
foreach ($points as &$point) {
array_push($norm_points, [$point[0], $point[1] / $sum]);
}
}
}
return microtime(true) - $start; # Around 0.1 seconds or so
Edit 2, profiled each line and found that norm.pdf() was taking 98% of time, so found a custom normpdf function and defined it, now time is around 0.67s which is considerably faster, but still around 10x slower than PHP. Also I think redefining common functions goes against the idea of Pythons simplicity?!
The custom function (source is some other Stackoverflow answer):
from math import sqrt, pi, exp
def normpdf(x, mu, sigma):
u = (x-mu)/abs(sigma)
y = (1/(sqrt(2*pi)*abs(sigma)))*exp(-u*u/2)
return y
The answer is, you aren't using the right tools/data structures for the tasks in python.
Calling numpy functionality has quite an overhead (scipy.stats.norm.pdf uses numpy under the hood) in python and thus one would never call this functions for one element but for the whole array (so called vectorized computation), that means instead of
for x in range:
y = norm.pdf(x, x1+((x2-x1)/2), slope)
ys.append(y)
one would rather use:
ys = norm.pdf(x,x1+((x2-x1)/2), slope)
calculating pdf for all elements in x and paying the overhead only once rather than len(x) times.
For example to calculate pdf for 10^4 elements takes less than 10 times more time than for one element:
%timeit norm.pdf(0) # 68.4 µs ± 1.62 µs
%timeit norm.pdf(np.zeros(10**4)) # 415 µs ± 12.4 µs
Using vectorized computation will not only make your program faster but often also shorter/easier to understand, for example:
def calculate_dist_vec(x1, x2, steps, slope):
x = np.linspace(x1, x2, steps+2)
y = norm.pdf(x, x1+((x2-x1)/2), slope)
ys = y/np.sum(y)
return x,ys
Using this vectorized version gives you a speed-up around 10.
The problem: norm.pdf is optimized for long vectors (nobody really cares how fast/slow it is for 10 elements if it is very fast for one million elements), but your test is biased against numpy, because it uses/creates only short arrays and thus norm.pdf cannot shine.
So if it is really about small arrays and you are serious about speeding it up you will have to roll out your own version of norm.pdf Using cython for creating this fast and specialized function might be worth a try.
I'm not sure if this title is correct but here's basically what I am trying to do.
I am trying to check if a number is less than 100 and if it isn't I would like to know what factor of 10 I need to divide it by to get below 100 i.e. for 7923 the factor is 100 to make it 79.23 and for 452,936,489 the factor would be 10,000,000 to make it 45.2936489.
Is there a function or a piece of script that does that out there?
Cheers
$number = 452936489;
$factor = pow(10, ceil(log($number/100) / log(10)));
Ok. basic math:
you need to find a power of 10 divisor that reduces your number below 100, so the log business figures out the exact fractional power of 10 required to turn 10 into your original number. That comes out to be around 6.6560373....
That gets rounded up to 7, and is then used to raise 10 to that power.
10^7 = 10,000,000
452936489 / 10^7 = 45.2936489
<?
$num = 7923;
$x = 10;
while(true)
{
$result = $num/$x;
if($result < 100)
{
die($x."");
}
else
{
$x *= 10;
}
}
?>
let's say I have a variable containing an integer or a float (since integers might overflow into a float in PHP).
I want to run some operation to get the leftmost digit and the rest of the remaining digits.
To explain better:
<?php
$x = NULL; //this will hold first digit
$num = 12345; //int
/// run operation
//outputs
//$x = 1;
//$num = 2345;
var_dump($x, $num);
?>
Now, I know there's multitudes of ways to do this if you represent the number as a string, but I'm trying to avoid type casting it into a string.
I'm probably looking for a solution which includes bitwise operations, but I'm pretty weak in that topic so I'm hoping someone who usually works low-level might be able to answer this!
Thanks a bunch.
I'm sure there is a way to do this without casting it to a string, but why? The string detour is so easy:
$x = (int)substr($num, 0, 1);
It'll give you a nice, proper integer.
Obviously, this does no extended checking for faulty input, and requires $num to be a valid number.
Avoids using any string manipulation, but no guarantees for float or even negative values
$x = NULL; //this will hold first digit
$num = 12345; //int
$m = 1;
while(true) {
$m *= 10;
if ($m > $num)
break;
}
$m /= 10;
$x = (int) floor($num / $m);
$num = $num % $m;
//outputs
//$x = 1;
//$num = 2345;
var_dump($x, $num);
Math-only method:
function leftMost($num) {
return floor($num/pow(10,(floor((log10($num))))));
}
explained I guess...
1+ log10 of num calculates the number of digits a number is, we floor it to remove any decimal values, put it as the exponent so for a 1 digit number we get 10^0=1, or a 8 digit number we get 10^8. We then are just divding 12345678/10000000 = 1.2345678, which gets floor'd and is just 1.
note: this works for numbers between zero and one also, where it will return the 2 in 0.02, and a string transform will fail.
If you want to work with negative numbers, make $num = abs($num) first.
To get the rest of the digits
$remainingnum = (int)substr((string)$num, 1, strlen($num));
If you typcast the value to a string you can use the array type selector.
For example:
$n = (string)12345676543.876543;
echo (int)$n[0];
#Mark Baker offered the best solution, though you should do abs(floor($num)) before applying the algorithm.
I know you stated you wanted to avoid casting to a string, but if you want to loop over the digits in PHP, this will be the fastest way:
$len = strlen($n);
for ($i = 0; $i < $len; ++$i)
$d = $n[$i];
In a quick-and-dirty benchmark, it was around 50% faster than the equivalent set of mathematical expressions, even when minimizing the calls to log and exp.
How would you find the fractional part of a floating point number in PHP?
For example, if I have the value 1.25, I want to return 0.25.
$x = $x - floor($x)
$x = fmod($x, 1);
Here's a demo:
<?php
$x = 25.3333;
$x = fmod($x, 1);
var_dump($x);
Should ouptut
double(0.3333)
Credit.
Don't forget that you can't trust floating point arithmetic to be 100% accurate. If you're concerned about this, you'll want to look into the BCMath Arbitrary Precision Mathematics functions.
$x = 22.732423423423432;
$x = bcsub(abs($x),floor(abs($x)),20);
You could also hack on the string yourself
$x = 22.732423423423432;
$x = strstr ( $x, '.' );
The answer provided by nlucaroni will only work for positive numbers. A possible solution that works for both positive as well as negative numbers is:
$x = $x - intval($x)
If if the number is negative, you'll have to do this:
$x = abs($x) - floor(abs($x));
My PHP skills are lacking but you could minus the result of a floor from the original number
However, if you are dealing with something like perlin noise or another graphical representation, the solution which was accepted is correct. It will give you the fractional part from the lower number.
i.e:
.25 : 0 is integer below, fractional part is .25
-.25 : -1 is integer below, fractional part is .75
With the other solutions, you will repeat 0 as integer below, and worse, you will get reversed fractional values for all negative numbers.
Some of the preceding answers are partial. This, I believe, is what you need to handle all situations:
function getDecimalPart($floatNum) {
return abs($floatNum - intval($floatNum));
}
$decimalPart = getDecimalPart($floatNum);
You can use fmod function:
$y = fmod($x, 1); //$x = 1.25 $y = 0.25
To stop the confusion on this page actually this is the best answer, which is fast and works for both positive and negative values of $x:
$frac=($x<0) ? $x-ceil($x) : $x-floor($x);
I ran speed tests of 10 million computations on PHP 7.2.15 and even though both solutions give the same results, fmod is slower than floor/ceil.
$frac=($x<0) ? $x-ceil($x) : $x-floor($x);
-> 490-510 ms (depending on the sign of $x)
$frac=fmod($x, 1);
-> 590 - 1000 ms (depending on the value of $x)
Whereas the actual empty loop itself takes 80 ms (which is included in above timings).
Test script:
$x=sqrt(2)-0.41421356237;
$time_start = microtime(true);
for ($i=0;$i<=9999999;$i++) {
//$frac=fmod($x, 1); // version a
$frac=($x<0) ? $x-ceil($x) : $x-floor($x); // version b
}
$time_end = microtime(true);
$time = $time_end - $time_start;