Experiencing an issue with rounding [duplicate] - php

This question already has answers here:
Compare floats in php
(17 answers)
Closed 8 years ago.
Why does $x1 not equal $z2? I've tried round() as well as explicitly changing the precision to 8. $x1 should equal $z2
$x1 = 251.47267993;
$y1 = 3861.62758730;
$z1 = $x1 / $y1;
echo "{$x1} / {$y1} = {$z1}\n\n"; // ok looks good
$x2 = .06512090;
$y2 = 3861.62758730;
$z2 = $x2 * $y2;
echo "{$x2} x {$y2} = {$z2}\n\n"; // hmm no - $z2 should be === to $x1
// why do these numbers NOT match? and how can I make them match?
// set some precision somewhere?
echo $x1 . " = " . $z2 . "\n";
More info:
_251.47267993_ /3861.62758730 =.06512090
.06512090 *3861.62758730 =_251.47266394_
251.47267993 - 251.47266394 = .00001599
Note that the 2 underlined numbers should be identical we divided then
multiplied by the same number. Should be inverses ... right ... but not when we
don't have an infinite number of digits to play with - as is, they are not even
close, they differ by "0.00001599"

Don't know what your purpose is, but why are you not doing this?
$x = 251.47267993;
$y = 3861.62758730;
$z1 = $x / $y;
$z2 = $z1 * $y;
echo $x . " = " . $z2 . "\n";
Equals everytime

There is another problem: echoing float value. You have to increase your precision.
echo 1 / 3, PHP_EOL; // 0.33333333333333
ini_set('precision', 60);
echo 1 / 3, PHP_EOL; // 0.333333333333333314829616256247390992939472198486328125
Why are you trying to use value from screen but not from variable?

In PHP built-in functions are not exactly the best for that kind of calculations. For example:
echo (int) ((0.1 + 0.7) * 10);
will output 7 instead of 8.
Anyways, 251.47267993 / 3861.62758730 is not 0.06512090. Depending on the computer architecture when doing this computing it will print different results. For me it printed 0.065120904138202, which is perfectly fine because the precision is about ~14 decimal digits in a floating point.
What you can do, is use BCMath library, which is documented really good in the php.net manual. For this particular situation, you could use:
$z1 = bcdiv($x1, $y1, 200);
instead of:
$z1 = $x1 / $y1;
And see that this number is not that small what you've been thinking. For more information, check this out BCMath library.

Related

Finding factors in known ratio

I'm making a calculator for pipe fittings. The idea is that the user inputs the angle of the turn, then the calculator will tell you how many of what fittings to use. I have access to 8°, 11.25°, 22.5°, 45°, and 90° fittings. But, I can simplify it into 8° and 11.25° fittings, since 22.5°, 45°, and 90° are multiples of 11.25. I can take the # of 11.25 degree fittings, then use the following code to break it into larger fittings.
$num90 = floor($angle11 / 90);
$runningtotal = $angle11 - $num90 * 90;
$num45 = floor($runningtotal / 45);
$runningtotal = $runningtotal - $num45 * 45;
$num22 = floor($runningtotal / 22.5);
$runningtotal = $runningtotal - $num22 * 22.5;
$num11 = floor($runningtotal / 11.25);
$runningtotal = $runningtotal - $num11 * 11.25;
echo "You will need:";
echo "<br>";
echo "$num90 -- 90° fittings";
echo "<br>";
echo "$num45 -- 45° fittings";
echo "<br>";
echo "$num22 -- 22.5° fittings";
echo "<br>";
echo "$num11 -- 11.25° fittings";
Basically, I need to solve the equation:
"8x + 11.25y = angle"
Where "angle" is a known value, and X and Y are integers.
I've made a list of all the possible angles using these fittings, so the program will use the closest possible angle to their request (e.g. if they need a 150° turn, they'll be shown the fittings needed for a 150.5° connection, which is possible). That means that X and Y will be whole numbers. I already have the code to select the closest angle, I'm not worried about it.
I've looked into solutions for the Change Making Problem, which deals with something extremely similar. Most of the equations and algorithms they found go way over my head in terms of complexity. I'm a recent high school graduate, so my math level isn't as good as others.
How would I go about solving this equation? Is this maybe too complicated for me, a beginner? Am I overlooking some super simple solution?
Or, should I just use the wolframalpha API to offload the math onto their side?
Any help would be very appreciated!
Try this:
print_r(get_best_fit(150));
function get_best_fit($angle){
$a = 8;
$b = 11.25;
$best_diff = $angle;
$best_fit = [0,0];
for($x = 0; $x <= $angle/$a; $x++){
$y = round(($angle-$a*$x)/$b);
$diff = $angle-($x*$a+$y*$b);
if(abs($diff) < $best_diff){
$best_diff = abs($diff);
$best_fit = [$x,$y];
}
}
return $best_fit;
}
Output
Array ( [0] => 16 [1] => 2 )
So you would need 16 x 8 + 2 x 11.25.

Why is Bcmath returning innacurate results

Im having trouble getting bcmath to work with bitcoin based fractions on my server php 7.1 , ubuntu 18. Look at the following Code
bcscale(8);
$x1 = bcsub(0.04217 ,0.00007, 8);
$x2 = 0.04217 - 0.00007 ;
dd($x1 , $x2);
Result
"0.04217000"
0.0421
As you can see bcmath get return the first operand with some zeros added to it??.
Any Ideas?
The manual is a little subtile but, the parameters are supposed to be strings. If you make them strings it will work.
bcscale(8);
$x1 = bcsub('0.04217' ,'0.00007', 8);
$x2 = 0.04217 - 0.00007 ;
echo 'x1 = '. $x1 . PHP_EOL;
echo 'x2 = '. $x2;
RESULT
x1 = 0.04210000
x2 = 0.0421
Also from the manual
Caution
Passing values of type float to a BCMath function which expects a string as operand may not have the desired effect due to the way PHP converts float values to string, namely that the string may be in exponential notation (what is not supported by BCMath), and that the decimal separator is locale dependend (while BCMath always expects a decimal point).
As to the precision,
bcscale(8);
$x1 = bcsub('0.04217' ,'0.00007', 6);
// ^
$x2 = 0.04217 - 0.00007 ;
echo 'x1 = '. $x1 . PHP_EOL;
echo 'x2 = '. $x2;
RESULT
x1 = 0.042100
x2 = 0.0421
And
bcscale(8);
$x1 = bcsub('0.04217' ,'0.00007', 4);
// ^
$x2 = 0.04217 - 0.00007 ;
echo 'x1 = '. $x1 . PHP_EOL;
echo 'x2 = '. $x2;
RESULT
x1 = 0.0421
x2 = 0.0421

php round off value with trailing 2 decimals and multiples of 5

I tried this function
function roundUpToAny($n,$x=5) {
return (round($n)%$x === 0) ? round($n) : round(($n+$x/2)/$x)*$x;
}
echo roundUpToAny(4.52); // This show 5 instead of 4.55
echo roundUpToAny(5.1); // This show 5 instead of 5.10
How can i created such function
I think the following function does what you want:
function roundUpToAny($n, $x=5, $sf=2) {
$scale = pow(10,$sf);
return number_format(round(ceil($n*$scale / $x), $sf) * $x / $scale, $sf);
}
The first argument is the number to be converted, the second is the "rounding factor" (multiples of $x - you asked for 5 in the title of the question), the third is the number of figures after the decimal point.
Test:
echo roundUpToAny(1.23, 5, 2) ."\n";
echo roundUpToAny(4.54, 5, 2) ."\n";
echo roundUpToAny(5.101, 5, 3) ."\n";
echo roundUpToAny(1.19, 5, 3) ."\n";
Result:
1.25
4.55
5.105
1.190
UPDATE
You pointed out in a follow-up that the above code fails for inputs of 1.1 and 2.2. The reason for this is the fact that these numbers are not exactly representable in double precision (strange though this may sound). The following code compensates for this, by creating an intermediate value that is rounded to be an integer (and therefore can be represented exactly):
function roundUpToAny($n, $x=5, $sf=2) {
$scale = pow(10,$sf);
$temp = round($n * $scale, $sf); // get rid of small rounding errors
return number_format(round(ceil($temp / $x), $sf) * $x / $scale, $sf);
}
Testing:
echo roundUpToAny(1.1, 5, 2) ."\n";
Result:
1.10
As expected.

Determine if $x is divisible evenly by $y in PHP

I simply want to know if $x is evenly divisible by $y. For example's sake assume:
$x = 70;
$y = .1;
First thing I tried is:
$x % $y
This seems to work when both numbers are integers but fails if they are not and if $y is a decimal less than 1 returns a "Division by zero" error, so then I tried:
fmod($x,$y)
Which returns equally confusing results, "0.099999999999996".
php.net states fmod():
Returns the floating point remainder of dividing the dividend (x) by the divisor (y)
Well according to my calculator 70 / .1 = 700. Which means the remainder is 0. Can someone please explain what I'm doing wrong?
One solution would be doing a normal division and then comparing the value to the next integer. If the result is that integer or very near to that integer the result is evenly divisible:
$x = 70;
$y = .1;
$evenlyDivisable = abs(($x / $y) - round($x / $y, 0)) < 0.0001;
This subtracts both numbers and checks that the absolute difference is smaller than a certain rounding error. This is the usual way to compare floating point numbers, as depending on how you got a float the representation may vary:
php> 0.1 + 0.1 + 0.1 == 0.3
bool(false)
php> serialize(.3)
'd:0.29999999999999999;'
php> serialize(0.1 + 0.1 + 0.1)
'd:0.30000000000000004;'
See this demo:
php> $x = 10;
int(10)
php> $y = .1;
double(0.1)
php> abs(($x / $y) - round($x / $y, 0)) < 0.0001;
bool(true)
php> $y = .15;
double(0.15)
php> abs(($x / $y) - round($x / $y, 0)) < 0.0001;
bool(false)
.1 doesn't have an exact representation in binary floating point, which is what causes your incorrect result. You could multiply them by a large enough power of 10 so they are integers, then use %, then convert back. This relies on them not being different by a big enough factor that multiplying by the power of 10 causes one of them to overflow/lose precision. Like so:
$x = 70;
$y = .1;
$factor = 1.0;
while($y*$factor != (int)($y*$factor)){$factor*=10;}
echo ($x*$factor), "\n";
echo ($y*$factor), "\n";
echo (double)(($x*$factor) % ($y*$factor))/$factor;
There is a pure math library in bitbucket : https://bitbucket.org/zdenekdrahos/bn-php
The solution will be then :
php > require_once 'bn-php/autoload.php';
php > $eval = new \BN\Expression\ExpressionEvaluator();
php > $operators = new \BN\Expression\OperatorsFactory();
php > $eval->setOperators($operators->getOperators(array('%')));
php > echo $eval->evaluate('70 % 0.1'); // 0
0.00000000000000000000
tested on php5.3
credits : http://www.php.net/manual/en/function.bcmod.php#111276
Float-point representation varies from machine to machine. Thankfully there are standards. PHP typically uses the IEEE 754 double precision format for floating-point representation which is one of the most common standards. See here for more information on that. With that said take a look at this calculator for a better understanding as to the why. As for the how I like Tim's solution especially if you're dealing with user input.
As you said, using the modulus operator works fine when it's an integer, so why not set it up so that it operates on integers. In my case, I needed to check divisibility by 0.25:
$input = 5.251
$x = round($input, 3); // round in case $input had more decimal places
$y = .25;
$result = ($x * 1000) % ($y * 1000);
In your case:
$input = 70.12
$x = round($input, 2);
$y = .1;
$result = ($x * 100) % ($y * 100);

how to create "pretty" numbers?

my question is: is there a good (common) algorithm to create numbers, which match well looking user understood numbers out of incomming (kind of random looking for a user) numbers.
i.e. you have an interval from
130'777.12 - 542'441.17.
But for the user you want to display something more ...say userfriendly, like:
130'000 - 550'000.
how can you do this for several dimensions?
an other example would be:
23.07 - 103.50 to 20 - 150
do you understand what i mean?
i should give some criteria as well:
the interval min and max should
include the given limits.
the "rounding" should be in a
granularity which reflects the
distance between min and max (meaning
in our second example 20 - 200
would be too coarse)
very much honor you'll earn if you know a native php function which can do this :-)
*update - 2011-02-21 *
I like the answer from #Ivan and so accepted it. Here is my solution so far:
maybe you can do it better. i am open for any proposals ;-).
/**
* formats a given float number to a well readable number for human beings
* #author helle + ivan + greg
* #param float $number
* #param boolean $min regulates wheter its the min or max of an interval
* #return integer
*/
function pretty_number($number, $min){
$orig = $number;
$digit_count = floor(log($number,10))+1; //capture count of digits in number (ignoring decimals)
switch($digit_count){
case 0: $number = 0; break;
case 1:
case 2: $number = round($number/10) * 10; break;
default: $number = round($number, (-1*($digit_count -2 )) ); break;
}
//be sure to include the interval borders
if($min == true && $number > $orig){
return pretty_number($orig - pow(10, $digit_count-2)/2, true);
}
if($min == false && $number < $orig){
return pretty_number($orig + pow(10, $digit_count-2)/2, false);
}
return $number;
}
I would use Log10 to find how "long" the number is and then round it up or down. Here's a quick and dirty example.
echo prettyFloor(23.07);//20
echo " - ";
echo prettyCeil(103.50);//110
echo prettyFloor(130777.12);//130000
echo " - ";
echo prettyCeil(542441.17);//550000
function prettyFloor($n)
{
$l = floor(log(abs($n),10))-1; // $l = how many digits we will have to nullify :)
if ($l<=0)
$l++;
if ($l>0)
$n=$n/(pow(10,$l)); //moving decimal point $l positions to the left eg(if $l=2 1234 => 12.34 )
$n=floor($n);
if ($l>0)
$n=$n*(pow(10,$l)); //moving decimal point $l positions to the right eg(if $l=2 12.3 => 1230 )
return $n;
}
function prettyCeil($n)
{
$l = floor(log(abs($n),10))-1;
if ($l<=0)
$l++;
if ($l>0)
$n=$n/(pow(10,$l));
$n=ceil($n);
if ($l>0)
$n=$n*(pow(10,$l));
return $n;
}
This example unfortunately will not convert 130 to 150. As both 130 and 150 have the same precision. Even thou for us, humans 150 looks a bit "rounder". In order to achieve such result I would recommend to use quinary system instead of decimal.
You can use php's round function which takes a parameter to specify the precision.
<?php
echo round(3.4); // 3
echo round(3.5); // 4
echo round(3.6); // 4
echo round(3.6, 0); // 4
echo round(1.95583, 2); // 1.96
echo round(1241757, -3); // 1242000
echo round(5.045, 2); // 5.05
echo round(5.055, 2); // 5.06
?>
The number_format() function handles "prettifying" numbers with arbitrary thousands/decimal characters and decimal places, but you'd have to split your ranges/strings into individual numbers, as number_formation only works on one number at a time.
The rounding portion would have to handled seperately as well.
I haven't seen ready algorithm or function for that. But it should be simple, based on string replacement (str_replace, preg_replace), number_format and round functions.
This actually is kind of a special case, that can be addressed with the following function:
function roundto($val, $toceil=false) {
$precision=2; // try 1, 2, 5, 10
$pow = floor(log($val, 10));
$mult = pow(10, $pow);
$a = $val/$mult*$precision;
if (!$toceil) $a-=0.5; else $a+=0.5;
return round($a)/$precision*$mult;
}
$v0=130777.12; $v1=542441.17;
echo number_format(roundto($v0, false), 0, '.', "'").' - '
.number_format(roundto($v1, true), 0, '.', "'").'<br/>';
$v0=23.07; $v1=103.50;
echo number_format(roundto($v0, false), 0, '.', "'").' - '
.number_format(roundto($v1, true), 0, '.', "'").'<br/>';
Outputs exactly this:
100'000 - 550'000
20 - 150
For any other case of number formatting it might be interesting to have a look at my newly published PHP class "php-beautiful-numbers", which I use in almost ever project to display run times ("98.4 µs" [= 9.8437291615846E-5]) or numbers in running text (e.g. "you booked two flights." [= 2]).
https://github.com/SirDagen/php-beautiful-numbers

Categories