Imprecise numbers with microtime and floating point addition in PHP - php

I'm having a terrible time convincing myself what I've done here is a good idea. The specific section I find objectionable is:
return ((float)($now+$sec).'.'.$mic);
In order to preserve the floating point precision, I'm forced to either fall back on the BC or GMP libraries (neither of which is always available). In this case, I've resorted to jamming the numbers together with string concatenation.
<?php
// return the current time, with microseconds
function tick() {
list($sec, $mic, $now) = sscanf(microtime(), "%d.%d %d");
return ((float)($now+$sec).'.'.$mic);
}
// compare the two given times and return the difference
function elapsed($start, $end) {
$diff = $end-$start;
// the difference was negligible
if($diff < 0.0001)
return 0.0;
return $diff;
}
// get our start time
$start = tick();
// sleep for 2 seconds (should be ever slightly more than '2' when measured)
sleep(2);
// get our end time
$end = tick();
$elapsed = elapsed($start, $end);
// should produce output similar to: float(2.00113797188)
var_dump($elapsed);
?>
If I attempt to add two numbers like 123456789 (representing a timestamp) and 0.0987654321 (representing microseconds), using the addition operator (+) I invariably end up with 123456789.099. Even when casting the integer to float, the result is the same.
Is there a solution for this issue which is 1) not a hack and 2) doesn't involve string concatenation? I shouldn't have to fall back on this sort of garbled code in order to get an accurate timestamp with microsecond resolution.
Edit: As S. Gehrig has explained, floating point numbers in PHP can be, at times, a bit tricky to display. The "precision" indicated in the PHP configuration is regarding display. The actual values are not rounded like I thought. A far simpler solution to the above code would look like so:
// return the current time, with microseconds
function tick() {
return microtime(true);
}
// compare the two given times and return the difference
function elapsed($start, $end) {
return $end-$start;
}
// get our start time
$start = tick();
// sleep for 2 seconds (should be ever slightly more than '2' when measured)
sleep(2);
// get our end time
$end = tick();
$elapsed = elapsed($start, $end);
// should produce output similar to: float(2.00113797188)
var_dump($elapsed);
If you were to examine $start or $end before subtracting one from the other, it might appear they were rounded to the hundredths position. This is not the case. It seems arbitrary precision is maintained for arithmetic while the display is limited.

Why don't you use microtime(true) which simply returns a microsecond timestamp as float? The parameter [bool] $get_as_float was added in PHP 5.0.0.
Regarding the comment about the "loss" of precision:
$start = microtime(true);
$end = microtime(true);
echo $end - $start;
// prints 7.1526861190796
microtime(true) is not limited to 2 decimal places. What the poster encounters is the effect of the configuration setting precision which controls how many decimal places will be printed when outputting float variables. This has nothing to do with the internal precision microtime(true) uses. You can always use number_format() or (s)printf() to format the output to the precision you like.

First, spligak, I see that your code contains an error.
list($sec, $mic, $now) = sscanf(microtime(), "%d.%d %d");
return ((float)($now+$sec).'.'.$mic);
If $mic has fewer than six digits, you get garbage results. Do a desk check on
the case where microtime() returns "0.000009 1234567890"
Second, you can greatly reduce the floating-point error as follows:
(WARNING: untested code!)
// compare the two given times and return the difference
// get our start time
$start = microtime();
// sleep for 2 seconds (should be ever slightly more than '2' when measured)
sleep(2);
// get our end time
$end = microtime();
// work around limited precision math
// subtract whole numbers from whole numbers and fractions from fractions
list($start_usec, $start_sec) = explode(" ", $start);
list($end_usec, $end_sec) = explode(" ", $end);
$elapsed = ((float)$end_usec)-((float)$start_usec);
$elapsed += ((float)$end_sec)-((float)$start_sec);
// please check the output
var_dump($elapsed);

Floating point types are inherently imprecise. Either live with it, or don't use them.

Related

Converting an int that is more than 16 digits long into a string in php

Is there any function that easily echos an integer that is 15+ digits long?
The only way I've managed is like this:
$num = 123456789012345;
$num = number_format($num);
$num = str_replace(',', '', $num);
echo $num;
But even this way it is only accurate up to 17 digits. After the 16th digit the number isn't printed accurately (because as a float it starts getting inaccurate - see here).
EDIT: From the answers below I wrote ini_set('precision',40); and then echoed $num straight. All this did was to, simply put, not show the decimal point in the float number. And again after the 16th digit it starts getting inaccurate.
I also tried the other suggestion of changing it into an array and then iterating through it with str_split($num); and again the numbers were inaccurate from the 17th digit on!
The simplest solution would be to convert the integer into a string. I've tried:
$num = (string)$num;
//and
$num = strval($num);
But neither change anything and they act as if as they remained as an int??
My question is specifically why are the conversions into strings not working. Is there a way to turn the number into a string? Thanks
The only solution I can think of is changing the precision of floats in the php.ini
ini_set('precision', 25);
I don't know where you get those large numbers from, but I'd suggest a look into bc functions too!
The last thing I thought of is using the explode function to split the string into an array and interate through it.
EDIT: When all suggestions failed, your only choices are to check out the BC Math and/or GMP functions as well as MoneyMath. The BigInteger package should also do the trick, which uses GMP and BC.
Well, you see, it's not an "int" as you claimed :)
echo PHP_INT_MAX; // echoes 9223372036854775807
$n = 9223372036854775807;
echo $n; // echoes 9223372036854775807
$n = 9223372036854775808;
echo $n; // echoes 9.2233720368548E+18
Setting precision to something greater, as manniL said, does the trick.
ini_set("precision", 50);
$n = 9223372036854775808;
echo $n; // echoes 9223372036854775808

PHP: How to raise number to (tiny) fractional exponent?

I'm doing a calculation in PHP using bcmath, and need to raise e by a fractional exponent. Unfortunately, bcpow() only accepts integer exponents. The exponent is typically higher precision than a float will allow, so normal arithmetic functions won't cut it.
For example:
$e = exp(1);
$pow = "0.000000000000000000108420217248550443400745280086994171142578125";
$result = bcpow($e, $pow);
Result is "1" with the error, "bc math warning: non-zero scale in exponent".
Is there another function I can use instead of bcpow()?
Your best bet is probably to use the Taylor series expansion. As you noted, PHP's bcpow is limited to raising to integer exponentiation.
So what you can do is roll your own bc factorial function and use the wiki page to implement a Taylor series expansion of the exponential function.
function bcfac($num) {
if ($num==0) return 1;
$result = '1';
for ( ; $num > 0; $num--)
$result = bcmul($result,$num);
return $result;
}
$mysum = '0';
for ($i=0; $i<300; $i++) {
$mysum = bcadd($mysum, bcdiv(bcpow($pow,$i), bcfac($i)) );
}
print $mysum;
Obviously, the $i<300 is an approximation for infinity... You can change it to suit your performance needs.
With $i=20, I got
1.00000000000000000010842021724855044340662275184110560868263421994092888869270293594926619547803962155136242752708629105688492780863293090291376157887898519458498571566021915144483905034693109606778068801680332504212458366799913406541920812216634834265692913062346724688397654924947370526356787052264726969653983148004800229537555582281617497990286595977830803702329470381960270717424849203303593850108090101578510305396615293917807977774686848422213799049363135722460179809890014584148659937665374616
This is comforting since that small of an exponent should yield something really close to 1.0.
Old question, but people might still be interested nonetheless.
So Kevin got the right idea with the Taylor-polynomial, but when you derive your algorithm from it directly, you can get into trouble, mainly your code gets slow for long input-strings when using large cut-off values for $i.
Here is why:
At every step, by which I mean with each new $i, the code calls bcfac($i). Everytime bcfac is called it performs $i-1 calculations. And $i goes all the way up to 299... that's almost 45000 operations! Not your quick'n'easy floating point operations, but slow BC-string-operations - if you set bcscale(100) your bcmul has to handle up to 10000 pairs of chars!
Also bcpow slows down with increasing $i, too. Not as much as bcfac, because it propably uses something akin to the square-and-multiply method, but it still adds something.
Overall the time required grows quadraticly with the number of polynomial terms computed.
So... what to do?
Here's a tip:
Whenever you handle polynomials, especially Taylor-polynomials, use the Horner method.
It converts this: exp(x) = x^0/0! + x^1/1! + x^2/2! + x^3/3! + ...
...into that: exp(x) = ((( ... )*x/3+1 )*x/2+1 )*x/1+1
And suddenly you don't need any powers or factorials at all!
function bc_exp($number) {
$result = 1;
for ($i=299; $i>0; $i--)
$result = bcadd(bcmul(bcdiv($result, $i), $number), 1);
return $result;
}
This needs only 3 bc-operations for each step, no matter what $i is.
With a starting value of $i=299 (to calculate exp with the same precision as kevin's code does) we now only need 897 bc-operations, compared to more than 45000.
Even using 30 as cut-off instead of 300, we now only need 87 bc-operations while the other code still needs 822 for the factorials alone.
Horner's Method saving the day again!
Some other thoughts:
1) Kevin's code would propably crash with input="0", depending on how bcmath handles errors, because the code trys bcpow(0,0) at the first step ($i=0).
2) Larger exponents require longer polynomials and therefore more iterations, e.g. bc_exp(300) will give a wrong answer, even with $i=299, whyle something like bc_exp(3) will work fine and dandy.
Each term adds x^n/n! to the result, so this term has to get small before the polynomial can start to converge. Now compare two consecutive terms:
( x^(n+1)/(n+1)! ) / ( x^n/n! ) = x/n
Each summand is larger than the one before by a factor of x/n (which we used via the Horner method), so in order for x^(n+1)/(n+1)! to get small x/n has to get small as well, which is only the case when n>x.
Inconclusio: As long as the number of iterations is smaller than the input value, the result will diverge. Only when you add steps until your number of iterations gets larger than the input, the algorithm starts to slowly converge.
In order to reach results that can satisfie someone who is willing to use bcmath, your $i needs to be significantly larger then your $number. And that's a huge proplem when you try to calculate stuff like e^346674567801
A solution is to divide the input into its integer part and its fraction part.
Than use bcpow on the integer part and bc_exp on the fraction part, which now converges from the get-go since the fraction part is smaller than 1. In the end multiply the results.
e^x = e^(intpart+fracpart) = e^intpart * e^fracpart = bcpow(e,intpart) * bc_exp(fracpart)
You could even implement it directly into the code above:
function bc_exp2($number) {
$parts = explode (".", $number);
$fracpart = "0.".$parts[1];
$result = 1;
for ($i=299; $i>0; $i--)
$result = bcadd(bcmul(bcdiv($result, $i), $fracpart), 1);
$result = bcmul(bcpow(exp(1), $parts[0]), $result);
return $result;
}
Note that exp(1) gives you a floating-point number which propably won't satisfy your needs as a bcmath user. You might want to use a value for e that is more accurate, in accordance with your bcscale setting.
3) Talking about numbers of iterations: 300 will be overkill in most situations while in some others it might not even be enough. An algorithm that takes your bcscale and $number and calculates the number of required iterations would be nice. Alraedy got some ideas involving log(n!), but nothing concrete yet.
4) To use this method with an arbitrary base you can use a^x = e^(x*ln(a)).
You might want to divide x into its intpart and fracpart before using bc_exp (instead of doing that within bc_exp2) to avoid unneccessary function calls.
function bc_pow2($base,$exponent) {
$parts = explode (".", $exponent);
if ($parts[1] == 0){
$result = bcpow($base,$parts[0]);
else $result = bcmul(bc_exp(bcmul(bc_ln($base), "0.".$parts[1]), bcpow($base,$parts[0]);
return result;
}
Now we only need to program bc_ln. We can use the same strategy as above:
Take the Taylor-polynomial of the natural logarithm function. (since ln(0) isn't defined, take 1 as developement point instead)
Use Horner's method to drasticly improve performance.
Turn the result into a loop of bc-operations.
Also make use of ln(x) = -ln(1/x) when handling x > 1, to guarantee convergence.
usefull functions(don't forget to set bcscale() before using them)
function bc_fact($f){return $f==1?1:bcmul($f,bc_fact(bcsub($f, '1')));}
function bc_exp($x,$L=50){$r=bcadd('1.0',$x);for($i=0;$i<$L;$i++){$r=bcadd($r,bcdiv(bcpow($x,$i+2),bc_fact($i+2)));}return $r;}#e^x
function bc_ln($x,$L=50){$r=0;for($i=0;$i<$L;$i++){$p=1+$i*2;$r = bcadd(bcmul(bcdiv("1.0",$p),bcpow(bcdiv(bcsub($x,"1.0"),bcadd($x,"1.0")),$p)),$r);}return bcmul("2.0", $r);}#2*Sum((1/(2i+1))*(((x-1)/x+1)^(2i+1)))
function bc_pow($x,$p){return bc_exp(bcmul((bc_ln(($x))), $p));}

PHP how to show all decimal places?

this might be a stupid question but I have searched again and again without finding any results.
So, what I want is to show all the decimal places of a number without knowing how many decimal places it will have. Take a look at this small code:
$arrayTest = array(0.123456789, 0.0123456789);
foreach($arrayTest as $output){
$newNumber = $output/1000;
echo $newNumber;
echo "<br>";
}
It gives this output:
0.000123456789
1.23456789E-5
Now, I tried using 'number_format', but I don't think that is a good solution. It determines an exact amount of decimal places, and I do not know the amount of decimal places for every number. Take a look at the below code:
$arrayTest = array(0.123456789, 0.0123456789);
foreach($arrayTest as $output){
$newNumber = $output/1000;
echo number_format($newNumber,13);
echo "<br>";
}
It gives this output:
0.0001234567890
0.0000123456789
Now, as you can see there is an excess 0 in the first number, because number_format forces it to have 13 decimal places.
I would really love some guidance on how to get around this problem. Is there a setting in PHP.ini which determines the amount of decimals?
Thank you very much in advance!
(and feel free to ask if you have any further questions)
It is "impossible" to answer this question properly - because a binary float representation of a decimal number is approximate: "What every computer scientist should know about floating point"
The closest you can come is write yourself a routine that looks at a decimal representation of a number, and compares it to the "exact" value; once the difference becomes "small enough for your purpose", you stop adding more digits.
This routine could then return the "correct number of digits" as a string.
Example:
<?php
$a = 1.234567890;
$b = 0.123456789;
echo returnString($a)."\n";
echo returnString($b)."\n";
function returnString($a) {
// return the value $a as a string
// with enough digits to be "accurate" - that is, the value returned
// matches the value given to 1E-10
// there is a limit of 10 digits to cope with unexpected inputs
// and prevent an infinite loop
$conv_a = 0;
$digits=0;
while(abs($a - $conv_a) > 1e-10) {
$digits = $digits + 1;
$conv_a = 0 + number_format($a, $digits);
if($digits > 10) $conv_a = $a;
}
return $conv_a;
}
?>
Which produces
1.23456789
0.123456789
In the above code I arbitrarily assumed that being right to within 1E-10 was good enough. Obviously you can change this condition to whatever is appropriate for the numbers you encounter - and you could even make it an optional argument of your function.
Play with it - ask questions if this is not clear.

Why is this microtime showing up weird in PHP

Why is this microtime showing up weird in PHP
$start4 = microtime(true);
// run some php code
$end4 = microtime(true);
print "Time4: ". ($end4 - $start4)."<br />";
The above is showing:
Time4: 2.69412994385E-5
Something with a more complex, longer running process shows up like this instead:
Time1: 0.000292062759399
E-5 is scientific notation. Seems to happen when you concatenate it with the string value. Try using number_format... ?
print "Time4: ". number_format($end4 - $start4, 10)."<br />";
//or use: printf(".10f", $end - $start)
It's normal for long numbers (very small or very large) to be converted to powers of 10. The E-5 just means that the number displayed is beeing multiplied by (10/10/10/10/10) which makes it a very small number.
0.000000000000123 is much harder to read than
1.23E-13 (for example).
Nevertheless if you do want to view the number in another format:
$start = microtime(true);
$end = microtime(true);
echo "Time: ", number_format($end - $start, 50);
This will add 50 decimal houses to the number on display.
Hope it helps!
It seems that this microtime() is showing up weird because PHP has a threshold, on either side of which it displays either a number in scientific notation or one in decimal notation. Both of these are technically "floats" (see documentation).
It seems that this threshold is somewhere between 0.8 seconds and 0.9 seconds; at least that's what my tests concluded. Using the following code will show scientific notation:
$start4 = microtime(true);
sleep(0.8);
$end4 = microtime(true);
echo 'Time4: ' . ($end4 - $start4) . '<br />';
But if we change our wait time to sleep(0.9), a decimal number is produced. This may or may not be the case on all systems or installations, but this is at least what my tests showed.
You can counteract this yourself by using the sprintf() function, like this:
$start4 = microtime(true);
sleep(0.8);
$end4 = microtime(true);
echo 'Time4: ' . sprintf('%f', $end4 - $start4) . '<br />';
This will always show the time as a decimal number.
Dont forget float:
number_format( (float) microtime(true), 10 );
------------------^
update: appended to top answer.

What's the best way to get the fractional part of a float in PHP?

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;

Categories