PHP calculate integer of division - php

I would like to calculate an integer part of division. The numerator and denominator (especially their precision) should not be altered because it might change from one calculation to other, also the denominator is as an example, its integer part as well as decimal part might be different.
I tried to use floor, ceil, round but none of them produced a correct result.
Please see the code below, perhaps you'll spot the error:
<?php
$valueArr = [
// should return 1999
199.90,
199.92,
199.95,
199.97,
// should return 2000
200.00,
200.02,
200.05,
200.07,
// should return 2001
200.10,
200.12,
200.15,
200.17,
];
$denominator = 0.1;
$resultArr = [];
foreach ($valueArr as $value) {
$key = (string) $value;
$result = floor($value / $denominator);
$resultArr[$key] = $result;
}
echo "Denominator:\n";
var_dump($denominator);
echo "\n";
print_r($resultArr);
that gives result:
Denominator:
float(0.1)
Array
(
[199.9] => 1999
[199.92] => 1999
[199.95] => 1999
[199.97] => 1999
[200] => 2000
[200.02] => 2000
[200.05] => 2000
[200.07] => 2000
[200.1] => 2000
[200.12] => 2001
[200.15] => 2001
[200.17] => 2001
)
where:
[200.1] => 2000
is wrong because integer part of (200.1 / 0.1) is 2001.
Do you know how to produce correct result for the $valueArr as above? What did I do wrong?
I'm using PHP 7.3.8 (cli)

So there's two issues you're going to run into here. First is the lack of precision on floating points in general, and the second is PHP's automatically coercing your inputs into floating points before you have a chance to use something like bcdiv.
As such: First step is to store your input numbers as strings so you're not losing precision out of the gate by the parser interpreting them as floating point numbers. Then use bcdiv on them.
Since you're just after the integer portion and bcdiv returns a string on success, we can just drop the decimal part with string functions.
<?php
$valueArr = [
// should return 1999
'199.90',
'199.92',
'199.95',
'199.97',
// should return 2000
'200.00',
'200.02',
'200.05',
'200.07',
// should return 2001
'200.10',
'200.12',
'200.15',
'200.17',
'381736192374124241.294',
];
$denominator = '0.1';
$resultArr = [];
foreach ($valueArr as $value) {
$key = (string) $value;
$result = explode('.', bcdiv($value, $denominator))[0];
$resultArr[$key] = $result;
}
echo "Denominator:\n";
var_dump($denominator);
echo "\n";
print_r($resultArr);
And if you want to do something like rounding, ceil, floor with the output of bcdiv, check out this answer:
https://stackoverflow.com/a/51390451/395384

I get the right results using bcdiv().
$result = bcdiv($value,$denominator);
I always use BcMath, seems more reliable to me.

You did not do anything wrong. This is a problem with computers. It's difficult to represent float-point numbers accurately in a fixed space.
Try this
foreach ($valueArr as $v) {
$resultArr []= floor($v * (1 / $denominator));
}
My advice would be to try to convert division operation into multiplication.
In your case, division by 0.1 === multiplication by 10. So, use that.

In case you don't have the bcdiv that comes with the BcMath extenstion
you may use the sprintf() function to achieve a proper result with the floor() and without any problem even in a case the denominator is a float smaller than 0.0001.
Instead of:
$result = floor($value / $denominator);
use this:
$result = floor(sprintf('%f', $value / $denominator));
and you'll get the proper:
[200.1] => 2001

Related

PHP slice off numbers after 4 non zero numbers in decimal

What I am trying to do is :
0.000000023455676554434 -> 0.0000002345
0.00000000000000000000002656565-> 0.00000000000000000000002656
0.012345 -> 0.01234
Code till now :
bcdiv(rtrim(sprintf('%.20f', $decimal), '0'),1,13);
The current code removes scientific notation, and trims any zeros towards the end if any, and cuts the decimals after 13 decimal points. but if the decimal is something like 0.023123123235435346 it would still show 13 decimal points while I am looking to get only 0.02312.
Any help wold be highly appreciated.
You can use base 10 log to get the number of zeros between the point and the first digit. This would do what you want:
$decimal = "0.00000000000000000000002656565";
echo bcdiv($decimal, 1, -1*floor(log($decimal, 10))-1+4);
Output: 0.00000000000000000000002656
I think preg_replace is also very easy to do. The value should be available as strings and not as float.
$trimValue = preg_replace('~(\d*\.0*[1-9]{4}).*~','$1',$value);
If you want 4 numbers after first non-zero number use this:
$trimValue = preg_replace('~(\d*\.0*[1-9]\d{3}).*~','$1',$value);
A complete test (First variant):
$data = [
'0.000000023455676554434', // -> 0.0000002345
'0.00000000000000000000002656565', //-> 0.00000000000000000000002656
'0.012345', // -> 0.01234
'0.023123123235435346', //0.02312
'0.00565',
];
$trimValues = [];
foreach($data as $value){
$trimValues[] = preg_replace('~(\d*\.0*[1-9]{4}).*~','$1',$value);
}
echo '<pre>';
var_export($trimValues);
Output:
array (
0 => '0.00000002345',
1 => '0.00000000000000000000002656',
2 => '0.01234',
3 => '0.02312',
4 => '0.00565',
)

PHP round to integer

I want to round a number and I need a proper integer because I want to use it as an array key. The first "solution" that comes to mind is:
$key = (int)round($number)
However, I am unsure if this will always work. As far as I know (int) just truncates any decimals and since round($number) returns a float with theoretically limited precision, is it possible that round($number) returns something like 7.999999... and then $key is 7 instead of 8?
If this problem actually exists (I don't know how to test for it), how can it be solved? Maybe:
$key = (int)(round($number) + 0.0000000000000000001) // number of zeros chosen arbitrarily
Is there a better solution than this?
To round floats properly, you can use:
ceil($number): round up
round($number, 0): round to the nearest integer
floor($number): round down
Those functions return float, but from Niet the Dark Absol comment: "Integers stored within floats are always accurate, up to around 2^51, which is much more than can be stored in an int anyway."
round(), without a precision set always rounds to the nearest whole number. By default, round rounds to zero decimal places.
So:
$int = 8.998988776636;
round($int) //Will always be 9
$int = 8.344473773737377474;
round($int) //will always be 8
So, if your goal is to use this as a key for an array, this should be fine.
You can, of course, use modes and precision to specify exactly how you want round() to behave. See this.
UPDATE
You might actually be more interested in intval:
echo intval(round(4.7)); //returns int 5
echo intval(round(4.3)); // returns int 4
What about simply adding 1/2 before casting to an int?
eg:
$int = (int) ($float + 0.5);
This should give a predictable result.
Integers stored within floats are always accurate, up to around 253, which is much more than can be stored in an int anyway. I am worrying over nothing.
For My Case, I have to make whole number by float or decimal type
number. By these way i solved my problem. Hope It works For You.
$value1 = "46.2";
$value2 = "46.8";
// If we print by round()
echo round( $value1 ); //return float 46.0
echo round( $value2 ); //return float 47.0
// To Get the integer value
echo intval(round( $value1 )); // return int 46
echo intval(round( $value2 )); // return int 47
My solution:
function money_round(float $val, int $precision = 0): float|int
{
$pow = pow(10, $precision);
$result = (float)(intval((string)($val * $pow)) / $pow);
if (str_contains((string)$result, '.')) {
return (float)(intval((string)($val * $pow)) / $pow);
}
else {
return (int)(intval((string)($val * $pow)) / $pow);
}
}
Round to the nearest integer
$key = round($number, 0);

Always round up on first decimal

I want my variable's first decimal to always be rounded up. For example:
9.66 goes to 9.7
9.55 goes to 9.6
9.51 goes to 9.6
9.00000001 goes to 9.1
How do I do this?
Use round() with an optional precision and round type arguments, e.g.:
round($value, 1, PHP_ROUND_HALF_UP)
The optional second argument to round() is the precision argument and it specifies the number of decimal digits to round to. The third optional argument specifies the rounding mode. See the PHP manual for round for details.
Using round() does not always round up, even when using PHP_ROUND_HALF_UP (e.g. 9.00001 is not rounded to 9.1). You could instead try to use multiplication, ceil() and division:
ceil($value * 10.0) / 10.0
Since these are floating-point values, you might not get exact results.
I made couple tests and suggest the following answer with test cases
<?php
echo '9.66 (expected 9.7) => '.myRound(9.66).PHP_EOL;
echo '9.55 (expected 9.6) => '.myRound(9.55).PHP_EOL;
echo '9.51 (expected 9.6) => '.myRound(9.51).PHP_EOL;
echo '9.00000001 (expected 9.1) => '.myRound(9.00000001).PHP_EOL;
echo '9.9 (expected ??) => '.myRound(9.9).PHP_EOL;
echo '9.91 (expected ??) => '.myRound(9.91).PHP_EOL;
function myRound($value)
{
return ceil($value*10)/10;
}
I'm not a php programmer so will have to answer in "steps". The problem you have is the edge case where you have a number with exactly one decimal. (e.g. 9.5)
Here's how you could do it:
Multiply your number by 10.
If that's an integer, then return the original number (that's the edge case), else continue as follows:
Add 0.5
Round that in the normal way to an integer (i.e. "a.5" rounds up).
Divide the result by 10.
For step (2), sniffing around the php documentation reveals a function bool is_int ( mixed $var ) to test for an integer.
You will need a custom ceil() function, your requirements cannot be satisfied by the default function or by the round.
Use this: online test
You can use this technique. Just explode the given number / string, get the number which is next value / digit of the .. after getting this you need to increment that value and check if the value is greater than 9 or nor, if then divide that and add the carry to the first portion of the main number.
$var = '9.96';
$ar = explode(".", $var);
$nxt = substr($ar[1], 0, 1) + 1;
if($nxt > 9){
$tmp = (string) $nxt;
$num = floatval(($ar[0] + $tmp[0]).".".$tmp[1]);
}
else
$num = floatval($ar[0].".".$nxt);
var_dump($num); // float(10)

BC math string into scientific notation

i use bc math (http://php.net/manual/en/book.bc.php).
the value is, e.g.,
$value = "0.0000000000000000000001111111111111111111112";
how can i transform it into scientific notation. it should be like this:
$value = "1.111111111111111111112E-22";
i tried amongst others
sprintf("%E",$value) or a (float)
but the result are only
1.111111E-22 (sprintf)
That are not so many significant figures as it should be :(
Count how many zeroes there are.
Note that because you are using big numbers, you have to work on them as strings. So...
if( preg_match("/^0\.0*/",$value,$m)) {
$zeroes = strlen($m[0]);
$value = substr($value,$zeroes,1)
.rtrim(".".substr($value,$zeroes+1),"0.")
."E-".($zeroes-1);
}
elseif( preg_match("/(\d+)(?:\.(\d+))?/",$value,$m)) {
$zeroes = strlen($m[1]);
$value = substr($value,0,1)
.rtrim(".".substr($m[1],1).$m[2],"0.")
."E+".($zeroes-1);
}
// else 1 <= number < 10, so no transformation needed
Test cases:
1000000 => 1E+6
1234.5678 => 1.2345678E+3
0.9 => 9E-1
0.123 => 1.23E-1
0.00000011111122222 => 1.1111122222E-7
You can do $float_value = (float)$value; and get 1.1111111111111E-22 but beyond that the float cannot offer more precision so it can't show that 2 at the end.

Conversion and rounding help

I need to convert pounds to kilograms and vice versa -- and round the number to the nearest quarter (and possibly half). I need to be able to make a conversion, take that conversion and convert it back, and have all the values still be the same.
Sample code:
for ($i = 1; $i <= 100; $i = $i + .25)
{
$kilograms = convert_pounds_to_kilograms($i);
$pounds = convert_kilograms_to_pounds($kilograms);
$new_kilograms = convert_pounds_to_kilograms($pounds);
echo ("$i => $pounds => $kilograms => $new_kilograms<br/>");
}
function convert_pounds_to_kilograms($pounds)
{
assert(is_numeric($pounds) === TRUE);
$kilograms = $pounds * 0.45359237;
// Round to the nearest quarter
$kilograms = round($kilograms * 4, 0) / 4;
return $kilograms;
}
function convert_kilograms_to_pounds($kilograms)
{
assert(is_numeric($kilograms) === TRUE);
$pounds = $kilograms * 2.20462262185;
// Round to the nearest quarter
$pounds = round($pounds * 4, 0) / 4;
return $pounds;
}
The first line of output is correct:
1 => 1 => 0.5 => 0.5
The second is not correct:
1.25 => 1 => 0.5 => 0.5
(the value 1 should have been 1.25)
How do I do this? I'm not looking for precision in the conversion, obviously. I just need to be able to convert these imprecise values back and forth to the same number.
EDIT 1:
The reason for this is that I will be allowing users to enter their height in centimeters, meters, or feet/inches -- then saving whatever their entered value to centimeters (thus, the first conversion). Users can then view their height in either centimeters, meters, or feet/inches (thus, a possible conversion again).
So, say a user enters their height in ft/inches, I need to store that in centimeters. Then the user may want to see that height again in ft/inches -- meaning I need to convert the centimeters back to the original ft/inches value.
Users will probably be limited to entering and viewing values to quarter increments. Meaning, 5'8.25" is valid, but not 5'8.39".
Do not round in the function itself. Go as precise as you can. Only round right before you display it.
If you round it off in the functions, then the ROUNDED value is put into the next function. If you keep doing this, you're going to lose a lot of precision, and you'll get less precise results the more you loop it.
You are rounding to 0 decimal places, hence the 1.25 is rounded to 1.
Try removimg the round() function and see what happens.
http://php.net/manual/en/function.round.php
Edit
To address your comment change:-
$kilograms = convert_pounds_to_kilograms($i);
To:-
$kilograms = round(convert_pounds_to_kilograms($i), 0);
And remove round() from inside your functions.
You're rounding out the precision. Your $pounds that you're printing out are converted from original value ($i) to kilograms with a round function, and then back to pounds with a round function; the round() is causing your values to converge.

Categories