I have some database figures that I am doing some simple math with. For some reason, I can't keep the total from rounding to the nearest dollar. I need to include the cents information, as well, though. I am positive that each itemPrice entry contains two decimal places in the database.
if (strpos($row2["itemDiscount"],'%') !== false) {
$itemDiscount = $row2["itemDiscount"];
$itemDetailTotalUnformatted = $row2["itemQuantity"]*($itemPrice*(1-($itemDiscount/100)));
}
else {
$itemDetailTotalUnformatted = $row2["itemQuantity"]*($row2["itemPrice"]-$row2["itemDiscount"]);
}
$itemDetailTotal = number_format($itemDetailTotalUnformatted, 2, '.', '');
echo $itemDetailTotal;
var_dump($row2):
50.00array(6) {
[0]=>
string(1) "2"
"itemQuantity"]=>
string(1) "2"
[1]=>
string(5) "30.00"
[itemPrice]=>
string(1) "30.00"
[2]=>
string(4) "5.00"
[itemPrice]=>
string(4) "5.00"
When dealing with currency, ALWAYS work in integers. Save the prices in cents, handle prices in cents, and only at the very end do you divide by 100 to present the result.
The reason for this is that ints have perfect precision (up to obscenely high values, where they are handled as floats instead), whereas floats do not. There is no fixed-point type in PHP.
Once you do that, your rounding problems will probably disappear.
Related
I am trying to create a project which will help students study various areas. The idea is that I have a piece of raw text, which contains quiz questions and answers which I want to parse as question header and answer options, which will be inserted into a database. However, the text is not properly formatted and due to the large amount of questions and answers (around ~20k per total), I cannot afford the time to manually insert them or format the text myself.
The raw text looks like this:
1. A car averages 27 miles per gallon. If gas costs $4.04 per gallon, which of the following is closest to how much the gas would cost for this car to travel 2,727 typical miles?
a) $44.44 b) $109.08 c) $118.80
d) $408.04 e)
$444.40
2. When x = 3 and y = 5, by how much does the value of 3x2 – 2y exceed the value of 2x2 – 3y ?
a) 4
b) 14
c) 16
d) 20 e) 50
I tried creating my own PHP functions to parse the text properly, however I cannot get myself to get past the random line breaks, spaces, etc.
What I am trying to obtain:
array(1) {
[0]=>
array(3) {
["questionNumber"]=>
string(1) "1"
["questionText"]=>
string(175) "A car averages 27 miles per gallon. If gas costs $4.04 per gallon, which of the following is closest to how much the gas would cost for this car to travel 2,727 typical miles?"
["options"]=>
array(5) {
["a"]=>
string(6) "$44.44"
["b"]=>
string(7) "$109.08"
["c"]=>
string(7) "$118.80"
["d"]=>
string(7) "$408.04"
["e"]=>
string(7) "$444.40"
}
}
}
The code I have so far:
$rawText = '1. A car averages 27 miles per gallon. If gas costs $4.04 per gallon, which of the following is closest to how much the gas would cost for this car to travel 2,727 typical miles?
a) $44.44 b) $109.08 c) $118.80
d) $408.04 e)
$444.40
2. When x = 3 and y = 5, by how much does the value of 3x2 – 2y exceed the value of 2x2 – 3y ?
a) 4
b) 14
c) 16
d) 20 e) 50
';
$rawTextLines = explode("\n", $rawText);
foreach ($rawTextLines as $lineNumber => $lineContents) {
$lContents = trim($lineContents);
if (empty ($lContents)) {
unset ($rawTextLines[$lineNumber]);
} else {
$rawTextLines[$lineNumber] = $lContents;
}
}
$processedQuestions = array ();
$currentQuestionHeader = 0;
foreach ($rawTextLines as $lineNumber => $lineContents) {
if (ctype_digit(substr($lineContents, 0, 1))) { // Question header
$questionHeaderInformation = explode('.', $lineContents);
$currentQuestionHeader = $questionHeaderInformation[0];
$processedQuestions[$currentQuestionHeader]['questionNumber'] = $currentQuestionHeader;
$processedQuestions[$currentQuestionHeader]['questionText'] = $questionHeaderInformation[1];
} else { // Question option
$options = explode(')', $lineContents);
if (count ($options) % 2 === 0) {
$processedQuestions[$currentQuestionHeader]['options'][trim($options[0])] = ucfirst(trim($options[1]));
} else {
}
}
}
Which produces this:
array(2) {
[1]=>
array(3) {
["questionNumber"]=>
string(1) "1"
["questionText"]=>
string(35) " A car averages 27 miles per gallon"
["options"]=>
array(1) {
["a"]=>
string(8) "$44.44 b"
}
}
[2]=>
array(3) {
["questionNumber"]=>
string(1) "2"
["questionText"]=>
string(96) " When x = 3 and y = 5, by how much does the value of 3x2 – 2y exceed the value of 2x2 – 3y ?"
["options"]=>
array(3) {
["a"]=>
string(1) "4"
["b"]=>
string(2) "14"
["c"]=>
string(2) "16"
}
}
}
As you can see, the current output does not match - not by far, what I am trying to obtain.
Thank you in advance.
Hellow,
^[0-9]+\. (.*)[\r\n]+a\)[\s]+(.*)[\s]+b\)[\s]+(.*)[\s]+c\)[\s]+(.*)[\s]+d\)[\s]+(.*)[\s]+e\)[\s]+(.*)[\s]*
Try it !
$re = '/^[0-9]+\. (.*)[\r\n]+a\)[\s]+(.*)[\s]+b\)[\s]+(.*)[\s]+c\)[\s]+(.*) [\s]+d\)[\s]+(.*)[\s]+e\)[\s]+(.*)[\s]*/m';
$str = '1. A car averages 27 miles per gallon. If gas costs $4.04 per gallon, which of the following is closest to how much the gas would cost for this car to travel 2,727 typical miles?
a) $44.44 b) $109.08 c) $118.80
d) $408.04 e)
$444.40
2. When x = 3 and y = 5, by how much does the value of 3x2 – 2y exceed the value of 2x2 – 3y ?
a) 4
b) 14
c) 16
d) 20 e) 50';
preg_match_all($re, $str, $matches, PREG_SET_ORDER, 0);
// Print the entire match result
var_dump($matches);
I tried making a script that could save all files from a webpage with a pattern in name to a server.
<?php
$base_url = "http://www.someurl.com/";
$sufix = ".jpg";
$id_start= "Name Numberstart"; //Picture 1
$id_end = "Name Numberend"; // Picture 235
$path_to_save = "filer/";
foreach(range($id_start, $id_end) as $id) {
file_put_contents($path_to_save.$id.$sufix, file_get_contents($base_url.$id.$sufix));
}
?>
If I only use id with numbers etc. 1-20 or 50 - 60 and so on, the script works perfectly. But when I have a name in front and white space, it doesn't seem to work. It just saves 1 file and stops.
According to php.net's description of range:
Character sequence values are limited to a length of one. If a length greater than one is entered, only the first character is used.
I take that to mean that
range("Name Numberstart", "Name Numbersend") == array("N");
And if we run php interactively, this is confirmed:
php > $var = range("Name Numberstart", "Name Numbersend");
php > var_dump($var);
array(1) {
[0]=>
string(1) "N"
}
So basically, you're actually getting the range of the first character of the first word to the first character of the second word. If we change the second word to "Pretty", we get:
php > var_dump(range("Name Numberstart", "Pretty Numbersend"));
array(3) {
[0]=>
string(1) "N"
[1]=>
string(1) "O"
[2]=>
string(1) "P"
}
I have a simple piece of code as below.
$amount = 447274.44882;
$rate = 0.00001;
echo floatNumber(bcmul($amount, $rate, 8), 8);
This outputs 0.00000000 when it should be 4.47274449. If I change the rate to 0.0001 then it outputs the correct number, anything higher than 4 decimals and it reports 0.
Am I doing something wrong or is this a known limitation or something? Seems quite a big one if that's the case.
If you cast 0.00001 to string using the default settings (and that's what will happen if you feed bcmul with floats since it expects strings) you'll get this:
var_dump( (string)0.00001 );
string(6) "1.0E-5"
It isn't clearly documented but bcmath functions apparently return cast to zero when faced to invalid input:
var_dump( bcadd('Hello', 'world!', 8) );
var_dump( bcadd('33', 'Foo', 8) );
var_dump( bcdiv('33', 'Foo', 8) );
string(10) "0.00000000"
string(11) "33.00000000"
Warning: bcdiv(): Division by zero
NULL
The whole idea of arbitrary precision libraries is to overcome the limitations of base 2 arithmetic and fixed size storage. Thus you'd need this:
var_dump( bcmul('447274.44882', '0.00001', 8) );
string(10) "4.47274448"
This is great to do math with 100-digit numbers but not particularly useful for simple rounding. In fact, the extension doesn't round at all—it merely truncates:
var_dump( bcmul('20.01', '1.444', 3) );
var_dump( bcmul('20.01', '1.444', 2) );
var_dump( bcmul('20.01', '1.444', 1) );
var_dump( bcmul('20.01', '1.444', 0) );
string(6) "28.894"
string(5) "28.89"
string(4) "28.8"
string(2) "28"
This question already has answers here:
Getting N random numbers whose sum is M
(9 answers)
Closed 1 year ago.
do you know a way to split an integer into say... 5 groups.
Each group total must be at random but the total of them must equal a fixed number.
for example I have "100" I wanna split this number into
1- 20
2- 3
3- 34
4- 15
5- 18
EDIT: i forgot to say that yes a balance would be a good thing.I suppose this could be done by making a if statement blocking any number above 30 instance.
I have a slightly different approach to some of the answers here. I create a loose percentage based on the number of items you want to sum, and then plus or minus 10% on a random basis.
I then do this n-1 times (n is total of iterations), so you have a remainder. The remainder is then the last number, which isn't itself truley random, but it's based off other random numbers.
Works pretty well.
/**
* Calculate n random numbers that sum y.
* Function calculates a percentage based on the number
* required, gives a random number around that number, then
* deducts the rest from the total for the final number.
* Final number cannot be truely random, as it's a fixed total,
* but it will appear random, as it's based on other random
* values.
*
* #author Mike Griffiths
* #return Array
*/
private function _random_numbers_sum($num_numbers=3, $total=500)
{
$numbers = [];
$loose_pcc = $total / $num_numbers;
for($i = 1; $i < $num_numbers; $i++) {
// Random number +/- 10%
$ten_pcc = $loose_pcc * 0.1;
$rand_num = mt_rand( ($loose_pcc - $ten_pcc), ($loose_pcc + $ten_pcc) );
$numbers[] = $rand_num;
}
// $numbers now contains 1 less number than it should do, sum
// all the numbers and use the difference as final number.
$numbers_total = array_sum($numbers);
$numbers[] = $total - $numbers_total;
return $numbers;
}
This:
$random = $this->_random_numbers_sum();
echo 'Total: '. array_sum($random) ."\n";
print_r($random);
Outputs:
Total: 500
Array
(
[0] => 167
[1] => 164
[2] => 169
)
Pick 4 random numbers, each around an average of 20 (with distribution of e.g. around 40% of 20, i.e. 8). Add a fifth number such that the total is 100.
In response to several other answers here, in fact the last number cannot be random, because the sum is fixed. As an explanation, in below image, there are only 4 points (smaller ticks) that can be randomly choosen, represented accumulatively with each adding a random number around the mean of all (total/n, 20) to have a sum of 100. The result is 5 spacings, representing the 5 random numbers you are looking for.
Depending on how random you need it to be and how resource rich is the environment you plan to run the script, you might try the following approach.
<?php
set_time_limit(10);
$number_of_groups = 5;
$sum_to = 100;
$groups = array();
$group = 0;
while(array_sum($groups) != $sum_to)
{
$groups[$group] = mt_rand(0, $sum_to/mt_rand(1,5));
if(++$group == $number_of_groups)
{
$group = 0;
}
}
The example of generated result, will look something like this. Pretty random.
[root#server ~]# php /var/www/dev/test.php
array(5) {
[0]=>
int(11)
[1]=>
int(2)
[2]=>
int(13)
[3]=>
int(9)
[4]=>
int(65)
}
[root#server ~]# php /var/www/dev/test.php
array(5) {
[0]=>
int(9)
[1]=>
int(29)
[2]=>
int(21)
[3]=>
int(27)
[4]=>
int(14)
}
[root#server ~]# php /var/www/dev/test.php
array(5) {
[0]=>
int(18)
[1]=>
int(26)
[2]=>
int(2)
[3]=>
int(5)
[4]=>
int(49)
}
[root#server ~]# php /var/www/dev/test.php
array(5) {
[0]=>
int(20)
[1]=>
int(25)
[2]=>
int(27)
[3]=>
int(26)
[4]=>
int(2)
}
[root#server ~]# php /var/www/dev/test.php
array(5) {
[0]=>
int(9)
[1]=>
int(18)
[2]=>
int(56)
[3]=>
int(12)
[4]=>
int(5)
}
[root#server ~]# php /var/www/dev/test.php
array(5) {
[0]=>
int(0)
[1]=>
int(50)
[2]=>
int(25)
[3]=>
int(17)
[4]=>
int(8)
}
[root#server ~]# php /var/www/dev/test.php
array(5) {
[0]=>
int(17)
[1]=>
int(43)
[2]=>
int(20)
[3]=>
int(3)
[4]=>
int(17)
}
$number = 100;
$numbers = array();
$iteration = 0;
while($number > 0 && $iteration < 5) {
$sub_number = rand(1,$number);
if (in_array($sub_number, $numbers)) {
continue;
}
$iteration++;
$number -= $sub_number;
$numbers[] = $sub_number;
}
if ($number != 0) {
$numbers[] = $number;
}
print_r($numbers);
This should do what you need:
<?php
$tot = 100;
$groups = 5;
$numbers = array();
for($i = 1; $i < $groups; $i++) {
$num = rand(1, $tot-($groups-$i));
$tot -= $num;
$numbers[] = $num;
}
$numbers[] = $tot;
It won't give you a truly balanced distribution, though, since the first numbers will on average be larger.
I think the trick to this is to keep setting the ceiling for your random # generator to 100 - currentTotal
The solution depends on how random you want your values to be, in other words, what random situation you're going to simulate.
To get totally random distribution, you'll have to do 100 polls in which each element will be binded to a group, in symbolic language
foreach i from 1 to n
group[ random(1,n) ] ++;
For bigger numbers, you could increase the selected group by random(1, n/100) or something like that until the total sum would match the n.
However, you want to get the balance, so I think the best for you would be the normal distribution. Draw 5 gaussian values, which will divide the number (their sum) into 5 parts. Now you need to scale this parts so that their sum would be n and round them, so you got your 5 groups.
The solution I found to this problem is a little different but makes makes more sense to me, so in this example I generate an array of numbers that add up to 960. Hope this is helpful.
// the range of the array
$arry = range(1, 999, 1);
// howmany numbers do you want
$nrresult = 3;
do {
//select three numbers from the array
$arry_rand = array_rand ( $arry, $nrresult );
$arry_fin = array_sum($arry_rand);
// dont stop till they sum 960
} while ( $arry_fin != 960 );
//to see the results
foreach ($arry_rand as $aryid) {
echo $arryid . '+ ';
}
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.