Reputation: 505
I get very strange PHP behavior with my function that allow to search within array of associative arrays to find array that have some key with searchable value.
My PHP code:
<?php
// Test array - results from database
$test_array[0] = array('period' => '2018', 'payment_type' => 'Direct Debit', 'value' => 0);
$test_array[1] = array('period' => '2018', 'payment_type' => 'Manual', 'value' => 20.85);
$test_array[2] = array('period' => '2018', 'payment_type' => 'Credit Debit Card', 'value' => 0);
// Function to find subarrays by payment type
function searchReportArrayForKeyValue($array, $searchvalue) {
$result_array = Array();
for($i = 0; $i < count($array); ++$i) {
$array_inside = $array[$i];
$keys = array_keys($array_inside);
for($j = 0; $j < count($array_inside); ++$j) {
$value = $array_inside[$keys[$j]];
if($value == $searchvalue) {
$result_array = $array_inside;
}
}
}
return $result_array;
}
var_dump(searchReportArrayForKeyValue($test_array, 'Direct Debit'));
var_dump(searchReportArrayForKeyValue($test_array, 'Manual'));
var_dump(searchReportArrayForKeyValue($test_array, 'Credit Debit Card'));
?>
If I run this code I should get 3 different arrays returned (0, 1, 2 keys from test array), however all three functions calls return 'Credit Debit Card' array key: http://take.ms/hZfec (screenshot). BUT if I change 'value' => 0, to some float/integer all works as expected, for example if I change test array to this:
$test_array[0] = array('period' => '2018', 'payment_type' => 'Direct Debit', 'value' => 11.2);
$test_array[1] = array('period' => '2018', 'payment_type' => 'Manual', 'value' => 20.85);
$test_array[2] = array('period' => '2018', 'payment_type' => 'Credit Debit Card', 'value' => 10.5);
I get 3 correct different subarrays returned by my three function calls: http://take.ms/SSTu1 (screenshot)
Why this happens? How this ZERO in 'value' break arrays iteration? What is wrong in my function?
P.S.: previously I have 'for each' in code, and changed it to 'for' to make sure this issue does not related to pointer reset in 'for each' inside 'for each', but this does not helped in this situation.
Upvotes: 0
Views: 86
Reputation: 32272
You can't compare floating point values directly because not all rational decimal numbers have rational floating-point equivalents.
See: https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems
To properly compare floating point numbers for you need to do something like:
$e = 0.00001;
if( abs($float_a - $float_b) < $e );
Where $e
is a sufficiently small margin of error for a particular comparison.
Your solution of casting to string only works because PHP's default float format precision is 14 digits and the imprecision is less than that.
That said, no one should ever use floating point to record amounts of money for these exact reasons, and more. Generally you store the value as an integer of the base unit of currency, eg: $1 == 100 cents, 1BTC == 1,000,000 satoshis.
However there are further concerns, such as safely splitting $4 across 3 recipients, safely rounding amounts without losing pennies, etc. For this reason there is Fowler's Money Pattern, and libraries that implement it.
Upvotes: 1
Reputation: 163632
You are using a loose comparison using ==
In every loop, the last comparision is 0 == Direct Debit
which is true
, and then you set $result_array = $array_inside;
You could see it when you run for example:
echo "$value == $searchvalue = " . ($value == $searchvalue ? "true" : "false") . PHP_EOL;
if($value == $searchvalue) {
Upvotes: 1
Reputation: 505
Finally I fixed this with this code changes:
I changed this:
if($value == $searchvalue)
To this:
if(strval($value) == strval($searchvalue))
But I'am still does not understand how and why this gives so strange behavior.
Upvotes: 1