Why is this not keeping the rounding on the $artWidthCM and $artHeightCM when I am adding it to the object?
If I echo out the values I get what is expected: '0.305'.
But when adding it to $artObj it prints as '0.304999999999999993338661852249060757458209991455078125'.
if ($result->num_rows > 0) {
$artObj = new stdClass();
while($row = $result->fetch_assoc()) {
//Dimensions
$dimenionsStrip = str_replace(' ', '', $row["Dimensions"]);
$dimensionsArr = explode("x", $dimenionsStrip);
$artWidthInch = 12; // $dimensionsArr[0];
$artHeightInch = 12; // $dimensionsArr[1];
$artWidthCM = (round(($artWidthInch * 2.54) * 2) / 2) / 100;
$artHeightCM = (round(($artHeightInch * 2.54) * 2) / 2) / 100;
// Build Object
$artObj->id = $row["ArtID"];
$artObj->artwidth = $artWidthCM;
$artObj->artheight = $artHeightCM;
}
$artJSON = json_encode($artObj, JSON_PRETTY_PRINT);
echo '['.$artJSON.']';
} else {
echo "No Artwork Found";
}
Prints JSON as follows:
[{ "id": "35628", "artwidth": 0.304999999999999993338661852249060757458209991455078125, "artheight": 0.304999999999999993338661852249060757458209991455078125 }]
On my local machine with exact same code it prints as:
[{ "id": "35628", "artwidth": 0.305, "artheight": 0.305 }]
This is a problem of floating point precision. See the Warning at https://www.php.net/manual/en/language.types.float.php
Highlight from the link:
So never trust floating number results to the last digit, and do not
compare floating point numbers directly for equality. If higher
precision is necessary, the arbitrary precision math functions and gmp
functions are available.
The floating point handling may be different on different machines, that's why it behaves "correctly" on one and "incorrectly" on other.
Related
I need to check which hexadecimal flags are included in a certain decimal variable. At the moment the script just works for small decimals but I've some longer ones.
In the following example it shows me the right flags when I check the flags inside $decimal2, but it doesn't work with $decimal.
I don't know the reason for this behaviour and what has to be changed.
This is the example demo.
$decimal = 613090029426844e18; // doesn't work
$decimal2 = 64; //works
const FLAG_1 = 0x1;
const FLAG_2 = 0x2;
const FLAG_3 = 0x4;
const FLAG_4 = 0x4;
const FLAG_5 = 0x32;
const FLAG_6 = 0x40;
const FLAG_7 = 0x400;
function show_flags ($decimal) {
if ($decimal & FLAG_1) {
echo "Flag 1 included.<br>\n";
}
if ($decimal & FLAG_2) {
echo "Flag 2 included.<br>\n";
}
if ($decimal & FLAG_3) {
echo "Flag 3 included.<br>\n";
}
if ($decimal & FLAG_4) {
echo "Flag 3 included.<br>\n";
}
if ($decimal & FLAG_5) {
echo "Flag 3 included.<br>\n";
}
if ($decimal & FLAG_6) {
echo "Flag 3 included.<br>\n";
}
if ($decimal & FLAG_7) {
echo "Flag 3 included.<br>\n";
}
}
show_flags($decimal);
First of all, none of the flags are set in your $decimal so it is expected to print nothing. Even if you did set those flags, let say by adding 0xfff to $decimal, it still won't print anything because the number is too large to be stored as an int, so it is stored as a float, which has limited precision. You can see this with var_dump()
$decimal = 613090029426844e18; // doesn't work
$decimal2 = 64; //works
var_dump($decimal);
var_dump($decimal2);
Output:
float(6.13090029426844E+32)
int(64)
A floating point number only stores the most significant digits (roughly 14 digits), any digit in the lower significant places are inevitably discarded.
$decimal3 = 613090029426844e18 + 0xfff;
Output:
float(6.13090029426844E+32)
So you won't be able to use the lower significant digits in a large number. You may want to take a look at BigInterger class Is there a BigInteger class in PHP?
for($i=0;$i<10000;$i++) {
$random=random_int(100,1000);
$div = random_int(1,100);
$rant_zoom = $random/$div;
$rant_zoom = $rant_zoom*$div;
if ($random != $rant_zoom) {
var_dump($random,$rant_zoom);
echo "-----------\r\n";
}
}
example for output
It is possible for floating point numbers to be inaccurate and not equal to the original value, but the results printed in var_dump show the same value, why is this?
I can't explain the underlying "magic" with the bits, but I'm pretty sure this is related to how fixed and float numbers are represented and cast.
A few things to note in your code:
the vars $div and $random are int and stay int
in you if you are comparing an int to a float with != so the values are casted before comparing them
While researching the answer I also came across this SO post, which shows that var_dump is secretly rounding the numbers
This is my version of your code, I wanted to try multiple approaches:
for($i=0; $i<100; $i++) {
$original = mt_rand(100, 1000);
$div = mt_rand(1,100);
$internal_part = $original / $div;
$internal = $internal_part * $div;
$bcmath_part = bcdiv($original, $div, 50);
$bcmath = bcmul($bcmath_part, $div, 50);
$diff_1 = $original - $internal;
$diff_2 = bcsub($original, $bcmath, 50);
var_dump($original, $internal, $div);
echo "<p>Full scale results:</p>";
echo sprintf("Internal: %s <br>", rtrim(number_format($internal, 100),0));
echo sprintf("Bcmath: %s <br>", rtrim(number_format($bcmath, 100),0));
echo "<p>both as Float</p>";
echo sprintf("Division: <code>%f</code> ", $internal_part);
echo sprintf("Internal: <code>%f</code> != <code>%f</code> Diff1: %f<br>", (float)$original, $internal, $diff_1);
echo sprintf("Division: <code>%f</code> ", $bcmath_part);
echo sprintf("Bcmath: <code>%f</code> != <code>%f</code> Diff2: %f <br>", (float)$original, $bcmath, $diff_2);
echo "<p>both as INT</p>";
echo sprintf("Internal: <code>%d</code> != <code>%d</code><br>", $original, (int)$internal);
echo sprintf("Bcmath: <code>%d</code> != <code>%d</code>", $original, (int)$bcmath);
echo "<hr>";
}
In the "Full scale" print, you should see that the internal division (default) results are just very-very close to an integer, but they aren't:
Just one example of 740 / 43 * 43:
Simple division and multiplication : 739.9999999999998863131622783839702606201171875
using Bcmath: 740
I think var_dump just gives up after a number of nines and calls it an integer, while the actual value is still a float.
Conclusion: use bcmath or something precise in these cases - especially in finance
I got a problem with this script
$total = 0;
$expected_total = 1111;
$i = [85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.48];
foreach ($i as $item) { $total += $item; }
if($total != $expected_total) {
echo json_encode([$total,$expected_total]);
}
The problem is that at the end of the sum the $total should be equal to the $expected_total.
I thought about different number types, I printed the type of the two vars and I was right, one was double and the other was integer, so I converted the integer to double
$expected_total = 1111.00;
but the result is still the same.
The only solution that I could find was comparing the rappresentation of the two numbers, casting them to a string.
if((string)$total != (string)$expected_total) {
echo json_encode([$total,$expected_total]);
}
But obviously this is kind of a hack.
Have you ever had a similar problem? How have you solved it?
PHP version : 5.5.9
Many Thanks
This is not only PHP problem. It is about representation of floating point numbers in memory. Floating numbers has limited precision. PHP uses IEEE 754. Read carefully the manual page and you will understand.
You can find in manual code snippet of how to do it.
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001; //very small number
if(abs($a-$b) < $epsilon) {
echo "true";
}
If you want to check them as integers you could round both values.
You should change the if-statement to the following:
if(round($total) != round($expected_total)) {
echo json_encode([$total,$expected_total]);
}
If you do it like this you will compare the rounded values, which will be the same.
There is a big red label in the PHP Manual, that says:
Floating point numbers have limited precision…
So never trust floating number results to the last digit, and do not compare floating point numbers directly for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available.
In this specific case, you could add the floating numbers using bcadd() and compare the totals using bccomp(). Both functions are provided by the BC Math extension:
foreach ($i as $item) {
$total = bcadd((string) $total, (string) $item, 2);
}
if (bccomp((string) $total, (string) $expected_total, 2) == 0) {
echo json_encode([$total,$expected_total]);
}
Thanks to stack, I learned about floating point imprecision, so I went over to the bc functions.
That works great on "normal" floats, but with extremely small floats, say 10^-10 types, bcadd always gives 0.
Can someone show me what I'm doing wrong to make these small floats add with precision?
Many thanks in advance!
PHP
$numerator = 1;
$denominator = 1000000000;
$quotientOne = $numerator / $denominator;
$numerator = 1;
$denominator = 1000000000000000;
$quotientTwo = $numerator / $denominator;
$smallSum = bcadd($quotientOne, $quotientTwo, 100);
echo $quotientOne . "<br>";
echo $quotientTwo . "<br>";
echo $smallSum . "<br>";
gives
1.0E-9
1.0E-15
0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
The / operator returns either a float or an integer. It's interfering with your attempts to use bcadd() correctly. The limitations of floating-point arithmetic will take hold before bcadd() gets a chance to show its stuff.
echo gettype($numerator / $denominator);
double
Use bcdiv() instead. Note that the bc*() functions take strings as their arguments, and they also return a string.
$ cat code/php/test.php
<?php
$num_1 = 1;
$denom_1 = 1000000000;
# Cast values to string type to be crystal clear about what you're doing.
$q_1 = bcdiv((string)$num_1, (string)$denom_1, strlen($denom_1));
printf("q_1: %s\n", $q_1);
$num_2 = 1;
$denom_2 = 1000000000000000;
# php will do an implicit conversion to string anyway, though.
$q_2 = bcdiv($num_2, $denom_2, strlen($denom_2));
printf("q_2: %s\n", $q_2);
printf("sum: %s\n", bcadd($q_1, $q_2, strlen($denom_2)));
?>
$ php code/php/test.php
q_1: 0.0000000010
q_2: 0.0000000000000010
sum: 0.0000000010000010
Arbitrary-precision arithmetic is inherently slower than floating-point arithmetic. That's the price you pay for dozens, hundreds, or thousands of digits of accuracy.
I cannot find how to use infinities with BC Math. Let's take something like:
$result = echo bcdiv("1", $divider);
It goes all well until $divider is 0. So you make an exception:
if (!bccomp($divider, "0")) {
$result = echo bcdiv("1", $divider);
}
else {
$result = INF;
}
The problem is that $result gets computed as "0" in future calculations. I can set $result as an arbitrary large number, but that destroys the concept of exact calculations. Also, it will get much slower if I put a too large number in order to get accurate calculations.
Is there a way to overcome this?