Floating point test assertion - why do these "identical" arrays fail? - php

I'm using assertSame() in PHPUnit to compare a database result with expected values. The results are floating point numbers.
PHPUnit returns this message (but I can't spot any differences):
Failed asserting that Array (
'1_1' => 11.111111111111
'1_2' => 33.333333333333
'1_3' => 55.555555555556
'1_4' => 0.0
'1_5' => null
'1_total' => 100.0
) is identical to Array (
'1_1' => 11.111111111111
'1_2' => 33.333333333333
'1_3' => 55.555555555556
'1_4' => 0.0
'1_5' => null
'1_total' => 100.0
)
Why is this failing and what is the correct way to compare an arrays of floating point values?

assertEquals has a $floating_delta argument for this type of cases:
$this->assertEquals($expected_array, $actual_array, '', 0.00001);
PHPUnit docs

The problem is almost certainly floating point precision. In the print_r, only so many digits are shown. If all significant bits were displayed, the situation is probably something like this:
Failed asserting that Array (
'1_1' => 11.1111111111110347
'1_2' => 33.3333333333331678
'1_3' => 55.5555555555562773
'1_4' => 0.0
'1_5' => null
'1_total' => 100.0
) is identical to Array (
'1_1' => 11.1111111111110346
'1_2' => 33.3333333333331679
'1_3' => 55.5555555555562771
'1_4' => 0.0
'1_5' => null
'1_total' => 100.0
)
Every floating point comparison—especially equality—must consider the lack of infinite precision.
if ($var == 0.005) /* just plain wrong! */
if (abs ($var, 0.005) < 0.001) /* more correct */
if (abs ($var, 0.005) < 0.0001) /* maybe more correct, depending on application */
if (abs ($var, 0.005) < 0.0000001) /* possibly more appropriate */

Unless anyone has a better suggestion, I will assume this is a floating point precision error, and follow the PHP manual advice: do not compare floating point numbers directly for equality.
So my own solution is to round the array values before comparison.

Related

Converting long strings to decimals with more than 13 places

This function is converting an array which may contain strings to numbers which may or may not have a decimal.
function toNumberArray(array $stringArray) {
print_r ($stringArray);
echo "<hr>";
$n = [];
for ($i = 0; $i < count($stringArray); $i++) {
$n[] = settype ( $stringArray[$i],"float");
$x = (float) $stringArray[$i];
echo $x;
echo "<br>";
}
return $n;
}
print_r (toNumberArray(["1.123456789012345","2.2000000000000002","3.2999999999999998"]));
Results:
Array ( [0] => 1.123456789012345 [1] => 2.2000000000000002 [2] => 3.2999999999999998 )
1.1234567890123
2.2
3.3
Array ( [0] => 1 [1] => 1 [2] => 1 )
Question:
Why is settype not converting the string to a float?
How to convert if there are 14 or more places after the decimal?
It is converting to a PHP float which is a 64-bit IEEE floating point number. You are probably expecting that PHP uses a 32-bit IEEE floating point number for float and a 64-bit IEEE floating point number for a double'. But, in PHPfloatanddouble` are synonyms. (see the link in jorgonor's comment).
You could use PHP's round function to convert the 64-bit number to 6 or 7 decimal points to simulate the effect you are probably looking for. See http://php.net/manual/en/function.round.php

php : result of sum array values is wrong

I have an array:
$test =array('49'=> '-0','51'=> '-0','50'=> '0','53'=> '-1.69','55'=> '0','57'=> '-2','59'=> '-6','60'=> '-12','65'=> '0','66'=> '0','67'=> '21.69','69'=> '0','70'=> '0','71'=> '0',);
echo "\n".'===== First Method ========';
echo "\n\n".print_r($test);
echo "\n array_sum: ".array_sum($test);
echo "\n\n".'===== Second Method ========';
$total = 0;foreach($test as $value) $total += $value;
echo "\n foreach:".$total."\n";
the result is
gd#gd:~/Desktop$ php test.php
===== First Method ========Array
(
[49] => -0
[51] => -0
[50] => 0
[53] => -1.69
[55] => 0
[57] => -2
[59] => -6
[60] => -12
[65] => 0
[66] => 0
[67] => 21.69
[69] => 0
[70] => 0
[71] => 0
)
1
array_sum: 3.5527136788005E-15
===== Second Method ========
foreach:3.5527136788005E-15
it is wrong, the result should be 0, not 3.5527136788E-15, how to fix it ?
This is just your standard floating point arithmetic precision error.
php -r "echo -1.69 + -2 + -6 + -12 +21.69;"
3.5527136788005E-15%
You can fix it by using ints rather than floats. For example, if you always expect 2 digits of precision, multiply all your numbers by 100, round them off to ints, sum them, and divide by 100.
php -r "echo (-169 + -200 -1200 +2169 + -600) / 100;"
0%
You are doing array_sum with strings. Remove the quotes on the values, or covert them to integers before using array_sum - I imagine it is converting the strings to integers incorrectly - only on my phone so can't check specifics.
Hope this helps.
Why not just give the values as floating point numbers rather than enclosing them in quotes. That would basically make it a string summation and strange result is expected. I think in before PHP version 4.something it used to convert the string to numbers. It might especially be problem with decimal numbers.
This is just an example of floating point imprecision. It's impossible to represent .69 exactly in binary (much like it's impossible to represent 1/3 exactly in decimal).
If you need exact numbers, you can look into using the bcmath php extension.

How SplPriorityQueue works when priority is not an integer?

I was wondering how SplPriorityQueue works when priority is string or int. Quick example:
$queue = new \SplPriorityQueue();
$queue->insert('b', 5);
$queue->insert('c', 5);
$queue->insert('d', 1);
$queue->insert('a', 10);
$queue->insert('1', 'a');
$queue->insert('2', 'b');
print_r($queue);
Output:
Array
(
[5] => a
[4] => b
[3] => c
[2] => d
[1] => 2
[0] => 1
)
Question: why items with int priority are listed first (i.e. a b c d)? When priority is string (items 1 2), is b considered greater than a?
This is determined by SplPriorityQueue::compare(). The documentation states about its return value:
Result of the comparison, positive integer if priority1 is greater
than priority2, 0 if they are equal, negative integer otherwise.
Note:
Multiple elements with the same priority will get dequeued in no particular order.
Note, that the parameters priority1 and priority2 are declared as mixed and there is no mention of converting to int.
This means, the usual rules for > apply (see comparison operator documentation):
string compared to string: lexical comparison (numerical if both strings are numerical)
int compared to int: numerical comparison
string compared to int: string is converted to number, numerical comparison
(int)'a' and (int)'b' resolves to 0, this is why these items come last after all numbers.
These are the relevant comparisons for your example:
php > var_dump(1 > 'a');
bool(true)
php > var_dump(1 > 'b');
bool(true)
php > var_dump('b' > 'a');
bool(true)

PostgreSQL error when bitvalue is false

I am having a little trouble saving bit-values into my PostgreSQL-DB using PDO. Whenever the bit(bool) is false i get this error
Warning: PDOStatement::execute(): SQLSTATE[22026]: String data, length mismatch: 7
ERROR: bit string length 0 does not match type bit(1) in /var/www/html/application/models/Database.php on line 75
The code is a little complicated to show, but here is what goes into the DB-class.
UPDATE users SET name=:name,email=:email,address=:address,zip=:zip,joindate=:joindate,phone=:phone,password=:password,activationcode=:activationcode,birthdate=:birthdate,lastdigits=:lastdigits,driverlicense=:driverlicense,e_presentation=:e_presentation,e_showphone=:e_showphone,e_showaddress=:e_showaddress,e_radius=:e_radius,e_showinsearch=:e_showinsearch,w_presentation=:w_presentation,w_showphone=:w_showphone,w_showaddress=:w_showaddress,w_radius=:w_radius,w_showinsearch=:w_showinsearch WHERE id=:id
And the data that is bound to the parameters
Array ( [:name] => My Name [:email] => myemail#gmail.com [:address] => My Address [:zip] => 79133 [:joindate] => 2012-09-18 12:39:56.769584 [:phone] => 073 917 13 97 [:password] => c6d18ac44b378ff3cecf09d9ebec31ad301c4394d7e1sdfjksc81cd3fbf47777f8df0ac9f33d14da18d71b76fc9c3e1210cb2efcabf6ed66f779d [:activationcode] => [:birthdate] => 1993-08-05 [:lastdigits] => 5079 [:driverlicense] => 0 [:e_presentation] => Test [:e_showphone] => 1 [:e_showaddress] => 1 [:e_radius] => 10 [:e_showinsearch] => 1 [:w_presentation] => Test [:w_showphone] => 1 [:w_showaddress] => 1 [:w_radius] => 10 [:w_showinsearch] => 1 [:id] => 28 ) 1
A quick Google-search shows me that others had the same problem, but no solution to it.
Maybe you are better served with a boolean type instead of bit(1)?
If you actually need to convert a boolean value to bit(1), a direct cast doesn't work:
select FALSE::bit(1), TRUE::bit(1)
But this works:
select FALSE::int::bit(1), TRUE::int::bit(1)
Cast to integer first, an then to bit.
If we consider that a bit is a number that is either 1 or 0, then false in php is not a number.
<? echo false; ?> prints an empty string, not the number 0.
In a lot of other situations in php, 0 and false will be equivalent, but they're still not the same thing, and from the point of view of PostgreSQL, an empty string as the value of a bit is not acceptable.
The php code should convert false to 0 when passing the bit value to execute() or similar.
Using
$sth->bindParam(':param', $value, PDO::PARAM_INT);
would also work when $value is false, since that would force a conversion to 0.

Split an array into two evenly by it's values

array
1703 => float 15916.19738
5129 => float 11799.15419
33 => float 11173.49945
1914 => float 8439.45987
2291 => float 6284.22271
5134 => float 5963.14065
5509 => float 5169.85755
4355 => float 5153.80867
2078 => float 3932.79341
31 => float 3924.09928
5433 => float 2718.7711
3172 => float 2146.1932
1896 => float 2141.36021
759 => float 1453.5501
2045 => float 1320.74681
5873 => float 1222.7448
2044 => float 1194.4903
6479 => float 1074.1714
5299 => float 950.872
3315 => float 878.06602
6193 => float 847.3372
1874 => float 813.816
1482 => float 330.6422
6395 => float 312.1545
6265 => float 165.9224
6311 => float 122.8785
6288 => float 26.5426
I would like to distribute this array into two arrays both ending up with a grand total (from the float values) to be about the same. I tried K-Clustering but that distributes higher values onto one array and lower values onto the other array. I'm pretty much trying to create a baseball team with even player skills.
Step 1: Split the players into two teams. It doesn't really matter how you do this, but you could do every other one.
Step 2: Randomly switch two players only if it makes the teams more even.
Step 3: Repeat step 2 until it converges to equality.
$diff = array_sum($teams[0]) - array_sum($teams[1]);
for ($i = 0; $i < 1000 && $diff != 0; ++$i)
{
$r1 = rand(0, 8); // assumes nine players on each team
$r2 = rand(0, 8);
$new_diff = $diff - ($teams[0][$r1] - $teams[1][$r2]) * 2;
if (abs($new_diff) < abs($diff))
{
// if the switch makes the teams more equal, then swap
$tmp = $teams[0][$r1];
$teams[0][$r1] = $teams[1][$r2];
$teams[1][$r2] = $tmp;
var_dump(abs($new_diff));
$diff = $new_diff;
}
}
You'll have to adapt that code to your own structures, but it should be simple.
Here's a sample output:
int(20)
int(4)
int(0)
I was using integers from 0 to 100 to rate each player. Notice how it gradually converges to equality, although an end result of 0 is not guaranteed.
You can stop the process after a fixed interval or until it reaches some threshold.
There are more scientific methods you could use, but this works well.
This is extremely simplistic, but have you considered just doing it like a draft? With the array sorted as in your example, Team A gets array[0], Team B gets array[1] and array[2] the next two picks go to Team A, and so on.
For the example you give, I got one team with ~50,000 and the other with ~45,000.

Categories