Float 1 converts to integer sometimes as 0, sometimes as 1 [duplicate] - php

This question already has answers here:
Is floating point math broken?
(31 answers)
PHP integer rounding problems
(5 answers)
Closed 8 years ago.
I am trying to get the first decimal place of a float number as an integer by subtracting the integer part, multiplying the remainder with 10 and then casting the result to int or using intval(). I noticed that the result for numbers with x.1 is correctly 1 as float, but after converting it to integer, it becomes sometimes 0, sometimes 1.
I tried to test it with numbers from 1.1 to 9.1:
for ($number = 1; $number < 10; $number++) {
$result = 10 * ($number + 0.1 - $number);
echo "<br/> number = " . ($number + 0.1) . ", result: ";
var_dump($result);
$result_int = intval($result);
var_dump($result_int);
}
Starting with 4.1 as input, the 1 oddly gets converted to 0:
number = 1.1, result: float(1) int(1)
number = 2.1, result: float(1) int(1)
number = 3.1, result: float(1) int(1)
number = 4.1, result: float(1) int(0)
number = 5.1, result: float(1) int(0)
number = 6.1, result: float(1) int(0)
number = 7.1, result: float(1) int(0)
number = 8.1, result: float(1) int(0)
number = 9.1, result: float(1) int(0)
Why at 4.1? That doesn't make any sense to me. Can anyone give me a hint what I am doing wrong?
PS: also tested at http://ideone.com/hr7M0A

You are seeing these results because floating point arithmetic is not perfectly accurate.
Instead of trying to manually get the first decimal point use fmod:
$result = substr(fmod($number, 1) * 10, 0, 1)

My php is a bit rusty, so my syntax in probably off, but shouldn't it be simpler to convert to string and take the rightmost digit ?
sprintf($Str, "%.1f", $number);
$digit=$Str[strlen($Str)-1]; // Last digit

Related

Why php rounds this number? [duplicate]

This question already has answers here:
Set precision for a float number in PHP
(5 answers)
Closed 3 years ago.
I have an app that getting Ethereum balance by address. The app receives balance from API and then puts it to the database. Balance comes in hex-integer:
$balance = $response->getBody(); //0x1e1e83d93bb6ebb88bbaf
Then I convert it to the WEI integer:
$hexInt = BC::hexdec($balance); // WEI "2275742359981542120930223"
And then I need to Convert WEI to ETH:
return $balance / '1000000000000000000';
If calculate it, it will be 2275742.359981542120930223, but PHP converts it to 2275742.3599815. As you see, php rounds this number after division. Why? And how can I get right result?
This happens because of the implicit casts. The division returns a float. Floats are not exact values. They have a precision. You can use number_format() to specify the amount of decimals/precision for output, but the float might not provide the necessary precision. Here are ini options for it.
One solution is using bcdiv() with the expected precision. Or you write your own formatting method using string functions:
$balance = '2275742359981542120930223';
$result = $balance / '1000000000000000000';
var_dump($result);
var_dump(number_format($result, 18, '.', ''));
var_dump(bcdiv($balance, '1000000000000000000', 18));
function formatETH(string $value, int $factor = 18) {
return substr($value, 0, -$factor).'.'.substr($value, -$factor);
}
var_dump(formatETH($balance));
Output:
float(2275742.3599815)
string(26) "2275742.359981541987508535"
string(26) "2275742.359981542120930223"
string(26) "2275742.359981542120930223"
Since your dealing with big numbers, and using https://github.com/krowinski/bcmath-extended you can use the BC function for division. That bcmath-extended has a wrapper for it (bcdiv), so try:
return BC::div($balance, '1000000000000000000');

Why is 0 so frequent is this number generation pattern?

I was just goofing around with PHP and I decided to generate some random numbers with PHP_INT_MIN (-9223372036854775808) and PHP_INT_MAX (9223372036854775807). I simply echoed the following:
echo rand(-9223372036854775808, 9223372036854775807);
I kept refreshing to see the numbers generated and to view the randomness of the numbers, as a result I started to notice a pattern emerging. Every 2-4 refreshes 0 appeared and this happened without fail, at one stage I even got 0 to appear 4x in a row.
I wanted to experiment further so I created the following snippet:
<?php
$countedZero = 0;
$totalGen = 250;
for ($i = 1; $i <= $totalGen; $i++) {
$rand = rand(-9223372036854775808, 9223372036854775807);
if ($rand == 0) {
echo $i . ": <font color='red'>" . $rand . "</font><br/>";
$countedZero++;
} else {
echo $i . ": " . $rand . "<br/>";
}
}
echo "0 was generated " . $countedZero . "/" . $totalGen . " times which is " . (($countedZero / $totalGen) * 100) . "%."
?>
this would give me a clear idea of what the generation rate is. I ran 8 tests:
The first 3 tests were using a $totalGen of 250. (3 tests total).
The second 3 tests were using a $totalGen of 1000. (6 tests total).
The third test was just to see what the results would be on a larger number, I chose 10,000. (7 tests total).
The fourth test was the final test, I was intrigued at this point because the last (large number) test got such a high result surprisingly so I raised the stakes and set $totalGen to 500,000. (8th test total).
Results
I took a screenshot of the results. I took the first output, I didn't keep testing it to try and get it to fit a certain pattern:
Test 1 (250)
(1).
(2).
(3).
Test 2 (1000)
(1).
(2).
(3).
Test 3 (10,000)
(1).
Test 4 (500,000)
(1).
From the above results, it is safe to assume that 0 has a very high probability of showing up even when the range of possible numbers is at its maximum. So my question is:
Is there a logical reason to why this is happening?
Considering how many numbers it can choose from why is 0 a recurring number?
Note Test 8 was originally going to be 1,000,000 but it lagged out quite badly so I reduced it to 500,000 if someone could test 1,000,000 and show the results by editing the OP it would be much appreciated.
Edit 1
As requested by #maiorano84 I used mt_rand instead of rand and these were the results.
Test 1 (250)
(1).
(2).
(3).
Test 2 (1000)
(1).
(2).
(3).
Test 3 (10,000)
(1).
Test 4 (500,000)
(1).
The results as you can see show that 0 still has a high probability of showing up. Also using the function rand provided the lowest result.
Update
It seems that in PHP7 when using the new function random_int it fixes the issue.
Example PHP7 random_int
https://3v4l.org/76aEH
This is basically an example of how someone wrote a bad rand() function. When you specify the min/max range in rand(), you hit a part of PHP's source that just results in imperfect distribution in the PRNG.
Specifically lines 44-45 of php_rand.h in php-src, which is the following macro:
#define RAND_RANGE(__n, __min, __max, __tmax) \
(__n) = (__min) + (zend_long) ((double) ( (double) (__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0)))
From higher up the call stack (lines 300-302 in rand.c of php-src):
if (argc == 2) {
RAND_RANGE(number, min, max, PHP_RAND_MAX);
}
RAND_RANGE being the macro defined above. By removing the range parameters by just calling rand() instead of rand(-9223372036854775808, 9223372036854775807) you will get even distribution again.
Here's a script to demonstrate the effects...
function unevenRandDist() {
$r = [];
for ($i = 0; $i < 10000; $i++) {
$n = rand(-9223372036854775808,9223372036854775807);
if (isset($r[$n])) {
$r[$n]++;
} else {
$r[$n] = 1;
}
}
arsort($r);
// you should see 0 well above average in the top 10 here
var_dump(array_slice($r, 0, 10));
}
function evenRandDist() {
$r = [];
for ($i = 0; $i < 10000; $i++) {
$n = rand();
if (isset($r[$n])) {
$r[$n]++;
} else {
$r[$n] = 1;
}
}
arsort($r);
// you should see the top 10 are about identical
var_dump(array_slice($r, 0, 10)); //
}
unevenRandDist();
evenRandDist();
Sample Output I Got
array(10) {
[0]=>
int(5005)
[1]=>
int(1)
[2]=>
int(1)
[3]=>
int(1)
[4]=>
int(1)
[5]=>
int(1)
[6]=>
int(1)
[7]=>
int(1)
[8]=>
int(1)
[9]=>
int(1)
}
array(10) {
[0]=>
int(1)
[1]=>
int(1)
[2]=>
int(1)
[3]=>
int(1)
[4]=>
int(1)
[5]=>
int(1)
[6]=>
int(1)
[7]=>
int(1)
[8]=>
int(1)
[9]=>
int(1)
}
Notice the inordinate difference in the number of times 0 shows up in the first array vs. the second array. Even though technically they are both generating random numbers within the same exact range of PHP_INT_MIN to PHP_INT_MAX.
I guess you could blame PHP for this, but it's important to note here that glibc rand is not known for generating good random numbers (regardless of crypto). This problem is known in glibc's implementation of rand as pointed out by this SO answer
I took a quick look at your script and ran it through the command line. The first thing I had noticed is that because I was running a 32-bit version of PHP, my Integer Minimum and Maximum were different from yours.
Because I was using your original values, I was actually getting 0 100% of the time. I resolved this by modifying the script like so:
$countedZero = 0;
$totalGen = 1000000;
for ($i = 1; $i <= $totalGen; $i++) {
$rand = rand(~PHP_INT_MAX, PHP_INT_MAX);
if ($rand === 0) {
//echo $i . ": <font color='red'>" . $rand . "</font><br/>";
$countedZero++;
} else {
//echo $i . ": " . $rand . "<br/>";
}
}
echo "0 was generated " . $countedZero . "/" . $totalGen . " times which is " . (($countedZero / $totalGen) * 100) . "%.";
I was able to confirm that each test would yield just shy of a 50% hit rate for 0.
Here's the interesting part, though:
$rand = rand(~PHP_INT_MAX+1, PHP_INT_MAX-1);
Altering the range to these values causes the likelihood of zero coming up to plummet to an average of 0.003% (after 8 tests). The weird part was that after checking the value of $rand that was not zero, I was seeing many values of 1, and many random negative numbers. No positive numbers greater than 1 were showing up.
After changing the range to the following, I was able to see consistent behavior and more randomization:
$rand = rand(~PHP_INT_MAX/2, PHP_INT_MAX/2);
Here's what I'm pretty sure is happening:
Because you're dealing with a range here, you have to take into account the difference between the minimum and the maximum, and whether or not PHP can support that value.
In my case, the minimum that PHP is able to support is -2147483648, the maximum 2147483647, but the difference between them actually ends up being 4294967295 - a much larger number than PHP can store, so it truncates the maximum in order to try to manage that value.
Ultimately, if the difference of your minimum and maximum exceeds the PHP_INT_MAX constant, you're going to see unexpected behavior.

rounding a number, NOT necessarily decimel PHP

I have a question.
I am using php to generate a number based on operations that a user has specified
This variable is called
$new
$new is an integer, I want to be able to round $new to a 12 digit number, regardless of the answer
I was thinking I could use
round() or ceil()
but I believe these are used for rounding decimel places
So, I have an integer stored in $new, when $new is echoed out I want for it to print 12 digits. Whether the number is 60 billion or 0.00000000006
If i understand correctly
function showNumber($input) {
$show = 12;
$input = number_format(min($input,str_repeat('9', $show)), ($show-1) - strlen(number_format($input,0,'.','')),'.','');
return $input;
}
var_dump(showNumber(1));
var_dump(showNumber(0.00000000006));
var_dump(showNumber(100000000000000000000000));
gives
string(12) "1.0000000000"
string(12) "0.0000000001"
string(12) "999999999999"

Integer check in PHP

I'm writing an odds converter in PHP and came across this code for converting decimal odds to fractional ones
function dec2frac($dec) {
$decBase = --$dec;
$div = 1;
do {
$div++;
$dec = $decBase * $div;
} while (intval($dec) != $dec);
if ($dec % $div == 0) {
$dec = $dec / $div;
$div = $div / $div;
}
return $dec.'/'.$div;
}
When I tested the code, it would sometimes succeed in the calculations and sometime try to load the page for some time without success so I figured that it got stuck in the loop somehow. I set the time limit to 1 second and an echo in the loop, which confirmed my suspicion. I added these two echoes to the loop so I could see what went wrong.
echo "$dec $div<br/>";
echo var_dump(intval($dec) == $dec)." $dec is int<br/>";
Example printout when it fails, using decimal = 1.6
1.2 2
bool(false) 1.2 is int
1.8 3
bool(false) 1.8 is int
2.4 4
bool(false) 2.4 is int
3 5
bool(false) 3 is int //should break here and return 3/5
3.6 6
bool(false) 3.6 is int
4.2 7
bool(false) 4.2 is int
Example printout when it succeeds, using decimal = 1.8
1.6 2
bool(false) 1.6 is int
2.4 3
bool(false) 2.4 is int
3.2 4
bool(false) 3.2 is int
4 5
bool(true) 4 is int
Why doesn't it recognize the integers sometimes? How can I fix it so it exits the loop when an integer is found?
It looks like a floating precision rounding error. intval($dec) and $dec appear to be equal, but actually aren't. The difference is negligible but strict equalness fails.
On my 64bit system, with a precision of 1E-15 the function works, with a precision of 1E-16 it loops.
In general, strict comparison of two floating point numbers is to be avoided. Two floats are to be considered "equal" if their difference is less than a threshold. There are ways of calculating this threshold (google for "determining machine precision"), for it is not the same everywhere.
Since in my PHP.INI I have the default value
; The number of significant digits displayed in floating point numbers.
; http://php.net/precision
precision = 14
then the two numbers are shown equal even if one is 3 and the other 3.0000000000044, and the caltrop goes unnoticed.
With precision = 18, $dec is shown not to be what you'd expect:
1.20000000000000018
1.80000000000000027
2.40000000000000036
3.00000000000000044
Try:
function dec2frac($dec) {
$decBase = --$dec;
$div = 1;
do {
$div++;
$dec = $decBase * $div;
} while (abs(intval($dec) - $dec) > 1e-15);
if (($dec % $div) == 0) {
$dec = $dec / $div;
$div = $div / $div;
}
return $dec.'/'.$div;
}

$n = 2; 10-$n = 87

well this is what i am doing:
$total = (array_sum($odds))+$evens;
$total = str_split($total);
echo 'total[1]: '.$total[1].'<br />';
echo '10-$total[1]: ' . (10-($total[1]));
and the output is:
total[1]: 2
10-$total[1]: 87
my guess is it is being treated as a string, but how do i fix it?
so, what i want to know is
wh does (10-($total[1])); = 87?
Update:
yeah my mistake, a phantom 7,
but can anyone now tell me why:
echo $flybuys.' % '.$check.'<br />';
   $res = $flybuys % $check;
   echo 'res: '.$res;
outputs:
6014359000000928 % 8
res: 7
The inaccurate modulus result is because 6014359000000928 (~2^52) is beyond the bounds of an int, so PHP interprets it as a float. That implies you have a 32-bit system (PHP data type sizes vary depending on architecture). If you need to do math on large numbers, you can use a library like GMP. E.g.:
$flybuys = gmp_init("6014359000000928");
$res = gmp_mod($flybuys, 8);
Make sure you pass large numbers to GMP as strings.
If it is getting recognized as a string you could try casting it to an int using
(int)$total[1];
To be honest, you could probably cast the $total array into an int right when you do the string split:
(int)$total = ...;
Strings that represent numbers can also be cast into (float), and depending on which version of php you have (double).
Couldn't reproduce this issue:
$total = 2222; // some imaginary number as I don't know your $odds and $evens;
$total = str_split($total);
var_dump($total);
/*
*array(4) {
* [0]=>
* string(1) "2"
* [1]=>
* string(1) "2"
* [2]=>
* string(1) "2"
* [3]=>
* string(1) "2"
*}
*/
var_dump($total[1]);
/*
* string(1) "2"
*/
var_dump((10-($total[1])));
/*
* int(8)
*/
Absolutely the expected behavior...
I added this as an answer because in a comment is not enough space:
If this is the implementation of the algorithm described here i really think that modulo check 6014359000000928 % 8 == 0 shouldn't be there.
For example consider the number with the first 15 digits like that: 6014 3590 0000 062. For that evens is 15, odds is 24, total is 39 and check is 1. Any number modulo 1 is 0. So 6014 3590 0000 0628 is valid as 6014 3590 0000 0620 is or 6014 3590 0000 0627. That doesn't make sense.
I think you have to check the last digit for equality with check. In that case only 6014 3590 0000 0621 would be valid.

Categories