I am trying the below mentioned code(PHP) to find probability. The calculation includes combination calculation of large numbers, using BCmaths function but not getting results. Please suggest, how this can be done.
function combin($n, $r)
{
$C = 1;
for ($i=0; $i < $n-$r; $i++)
{
$C = bcdiv(bcmul($C, $n-$i), $i+1);
}
return $C;
}
$dv = (combin(68, 17))*(combin((7866-68),(177-17)))/combin(7866, 177);
echo $dv;
?>```
Once you start using the bc* functions, you should continue to use them throughout the code. You however, are taking the results of these functions and then using the standard PHP operators on them. You should be able to change your calling code to:
$dv = bcdiv(combin(7866, 177), bcmul(combin(68, 17), (combin(7866 - 68, 177 - 17))));
Related
I am building a little game and got stuck in developing the leveling system. I created a function that will exponentially increase the experience required for the next level. However, I am not sure how to turn it around so that I can put in the amount of experience a user has gained and get the corresponding level.
PHP function
function experience($level, $curve = 300) {
// Preset value to prevent notices
$a = 0;
// Calculate level cap
for ($x = 1; $x < $level; $x++) {
$a += floor($x+$curve*pow(2, ($x/7)));
}
// Return amount of experience
return floor($a/4);
}
The issue
I am wondering how I can reverse engineer this function in order to return the correct level for a certain amount of experience.
Using the above function, my code would output the following:
Level 1: 0
Level 2: 83
Level 3: 174
Level 4: 276
Level 5: 388
Level 6: 512
Level 7: 650
Level 8: 801
Level 9: 969
Level 10: 1154
What I am looking for is a way to invert this function so that I can input a certain amount and it will return the corresponding level.
A 1000 experience should return level 9 for example.
Plugging the values into excel and creating a trend line, I got the following equation:
y = 1.17E-09x^3 - 4.93E-06x^2 + 1.19E-02x + 6.43E-02
So your reverse engineered equation would be
function level($xp) {
$a = 1.17e-9;
$b = -4.93e-6;
$c = 0.0119;
$d = 0.0643
return round($a*pow($xp, 3) + $b*pow($xp,2) + $c * $xp + $d);
}
Results are accurate to within 1dp, but if your $curve changes, you'd need to recalculate. I also haven't extended higher than level 10.
Other options include caching the results of the lookup:
$levelXpAmounts = array()
function populateLevelArray($curve=300) {
$levelXpAmounts[$curve] = array();
for($level = $minlevel; $level <= $maxLevel; $level++) {
$levelXpAmounts[$curve][$level] = experience($level);
}
}
//at game load:
populateLevelArray()
Then, your reverse lookup would be
function level($xp, $curve=300) {
if (!array_key_exists($levelXpAmounts, curve)
populateLevelArray($curve);
for($level = $minlevel; $ level <= $maxLevel; $level++) {
if ($xp < $levelXpAmounts[$curve][$level]) {
return $level - 1;
}
}
}
That way, the iteration through all the levels is only done once for each different value of $curve. You can also replace your old experience() function with a (quite likely faster) lookup.
Note: it's been a while since I've written any php, so my syntax may be a little rusty. I apologize in advance for any errors in that regard.
You can do another function called level which uses the experience function to find the level:
function level($experience)
{
for ($level = 1; $level <= 10; $level++) {
if ($experience <= experience($level)) {
return $level;
}
}
}
function experience($level, $curve = 300)
{
$a = 0;
for ($x = 1; $x < $level; $x++) {
$a += floor($x+$curve*pow(2, ($x/7)));
}
return floor($a/4);
}
var_dump(level(1000));
You can clearly work the math here and find a reverse formula. Not sure whether it will be a nice and easy formula, so I would suggest you an alternative approach which is easy to implement.
Precalculate the results for all the levels you realistically want your person to achieve (I highly doubt that you need more than 200 levels, because based on my estimation you will need tens of billions exp points).
Store all these levels in the array: $arr = [0, 83, 174, 276, 388, 512, 650, ...];. Now your array is sorted and you need to find a position where your level should fit.
If you are looking for 400 exp points, you see that it should be inserted after 5-th position - so it is 5-th level. Even a simple loop will suffice, but you can also write a binary search.
This task could be solved in other way. This is method of partial sums.
Let's assume, you have a class , which stores an array of exponential values calculated by function:
function formula($level, $curve){ return floor($level+$curve*pow(2, ($level/7)));}
$MAX_LEVEL = 90;
function calculateCurve($curve){
$array = [];
for($i =0; $i< $MAX_LEVEL; $i++) $array.push(formula($i, $curve));
return $array;
}
Now we can calculate experience, needed for a level:
$curve = calculateCurve(300);
function getExperienceForLevel($level, $curve){
$S = 0;
for($i =0; $i < level; $i++) $S += $curve[$i];
}
And calculate level for experience:
function getLevelForExperience($exp, $curve){
for($i =0; $i < $MAX_LEVEL; $i++){
$exp -= $curve[$i];
if($exp < 0) return $i-1;
}
return $MAX_LEVEL;
}
I assume there could index problems - I didn't tested the code, but I suppose that main idea is clearly explained.
Pros:
Code cleaner, There no magic numbers and interpolation coeficients.
You can easy change your learning curve.
Possibility to improve and make calculating functions as O(1);
Cons:
There is an $curve array to store, or calculate somewhere.
Also. you could make even more advanced version of this:
function calculateCurve($curve){
$array = [];
$exp = 0;
for($i =0; $i< $MAX_LEVEL; $i++) {
$exp += formula($i, $curve);
$array.push($exp);
}
return $array;
}
Now calculating experience have O(1) complexity;
function getExperienceForLevel($level, $curve){
return $curve[min($MAX_LEVEL, $level)];
}
Perhaps not the best way, but it's working.
function level($experience, $curve = 300)
{
$minLevel = 1;
$maxLevel = 10;
for($level = $minLevel; $level <= $maxLevel; $level++)
{
if(experience($level, $curve) <= $experience && $experience < experience($level + 1, $curve))
{
return $level;
}
}
return $maxLevel;
}
Basically I'm working on a card management system in a particular module that has the job of creating bulk cards that are sequential. These cards are 19 digits long and because these cards have a monetary value I need to store the entire card value. The odd thing is that the system has no trouble managing VISA card number incrementing and those are 16 digits long. I'm assuming those last 3 digits are what is breaking the function but I have no idea how on Earth to handle this as I've never had to deal with such large values before.
$seqArray = array();
for($i = $_POST['startcardnumber']; $i <= $_POST['endcardnumber']; $i++) {
$i = sprintf('%0.0f',$i);
if(strlen($i) < $count) { $i = str_pad($i, $count, '0', STR_PAD_LEFT); }
array_push($seqArray, $i);
}
Any help is much appreciated.
Thanks to Fluffeh I found out that the BC Math functions were exactly what I needed. Below is the new for loop that I'm using to calculate and increment card numbers.
$seqArray = array();
for($s = $_POST['startcardnumber'], $e = $_POST['endcardnumber'];bccomp($s,$e) != 1; $s = bcadd($s, 1)) {
if(strlen($s) < $count) { $s = str_pad($s, $count, '0', STR_PAD_LEFT); }
array_push($seqArray, $s);
}
The BC Math library might be a little tedious to work with, but it will handle numbers in values you need with ease.
The downside is that you can't use simple things operators as expected:
For example, addition is done as the following - using the bcadd() function:
<?php
$a = '1.234';
$b = '5';
echo bcadd($a, $b); // 6
echo bcadd($a, $b, 4); // 6.2340
?>
Basic calculation of geometric mean is not that difficult, but I found myself hitting INF because list of numbers is big, up to 10k. So I tried to take a log of the numbers and exponentiate later, but still I got INF.
Next step was to chunk array of numbers, which seems good, but now I have a problem that if there is a reminder of the chunked array, result will be wrong. Is there any solution on this road, or would you prefer some other method of calculating geometric mean?
# testing with small number set
$a = array(13, 18, 13, 14, 13, 16, 14, 21, 13);
# number set will splice uneven with 2, thus giving wrong answer?
echo geometric_mean($a, 2);
echo " wrong<br />";
# number set will chunk evenly to 3 parts, thus giving right answer
echo geometric_mean($a, 3);
echo " correct<br />";
# straight way without splitting
echo _geometric_mean($a);
echo " correct<br />";
function geometric_mean($a, $size = 20) {
$a = array_chunk($a, $size);
foreach ($a as $b) {
# finding, if there is a reminder after split of an array
$c = count($b);
if ($c < $size) {
for ($i=$c; $i<$size; $i++) {
# adding last mean to the array, but it's not good
# adding 14.789726414533 would be ok...
$b[] = $m;
}
}
$m = _geometric_mean($b);
$d[] = $m;
}
# recursive call if array size is bigger
if (count($d) > $size) {
geometric_mean($d, $size);
}
return _geometric_mean($d);
}
# basic function to get geometric mean
function _geometric_mean($a) {
return pow(array_product($a), 1 / count($a));
}
Solution inspired found from: http://en.wikipedia.org/wiki/Geometric_mean#Relationship_with_arithmetic_mean_of_logarithms brought here by #ragol:
function geometric_mean($a) {
array_walk($a, function (&$i) {
$i = log($i);
});
return exp(array_sum($a)/count($a));
}
I'm not sure about efficiency but it works well on my app, no need for array splicing, recurring functions calls, and still no more INF.
That error means the number is too large for memory. Maybe the problem arises when you try to echo it. I'm not really sure. Try using this function:
is_infinite()
# Permission
7 read and write and execute (1+2+4)
6 read and write (2+4)
5 read and execute (1+4)
4 read only
3 write and execute (1+2)
2 write only
1 execute only
0 none
I like the pattern that you can store any combination of the options options in one integer number, and add options by doubling the last number (8, 16 ,32 etc).
I'd like to use this method, and I'd like to know if there's a name for it, and what is the fastest simplest method for turning numbers into results similar to this?
array(1=>false,2=>true,4=>true);//6
array(1=>true,2=>true,4=>true,8=>true);//15
Using bitwise operations as recommended. To get the array you are after.
This function will figure out the number of bit required for any value given and return the array in the format you suggested.
I've included a loop for 0 to 20 to verify.
<?php
function getBitArray($value) {
$numberOfBits = ceil(log($value + 1, 2));
$result = array();
$bit = 1;
for($i = 0; $i < $numberOfBits; $i++) {
$result[$bit] = ($value & $bit) == $bit;
$bit <<= 1;
}
return $result;
}
for($i = 0; $i < 20; $i++)
var_dump(getBitArray($i));
That's generally referred to as a bit field, and you can work with it using bitwise operators.
This method is known as bitwise operation, and is used in php like this. Here is a nice tutorial.
Unfortunately I inherited some code (c/c++) that does some string manipulation and now I need to copy/port that over to php so this functionality can be accessed over the internets.
Specifically the functionality takes some arbitrary strings and "adds" them together. (the c code iterates down the character array and then does some checking to make sure they are in the alphanumeric range)
I can't find specific code examples on how to do this (I am not a PHP developer) - can anyone point me to some resources that will explain this? (basically how to do string/character array manipulation)
EDIT
In response to some comments and answers:
I want the result in ascii, but essentially I will be adding base 36 numbers.
The C code right now converts to base 36 (from ascii)
then "adds" each element together (does not carry - although the original author intended that - and it for some strange reason does the "add" from most significant to least)
Then converts back to ascii.
Strings can be of different lengths
Based on the current answers i think I have enough of what I need. It is always frustrating sometimes learning a new language - you know exactly what you want and you can do it in other languages, just not the one that is for the task at hand...
Thanks for the responses so far.
Can't you just base_convert() them?
$sum = base_convert($str1, 36, 10) + base_convert($str2, 36, 10);
$sum36 = base_convert($sum, 10, 36);
Or do you need arbitrary precision? Here's a stab at arbitrary precision addition, in base 36:
function b36_add($str1, $str2)
{
$to10 = array();
for ($i = 0; $i < 36; ++$i)
{
$to10[base_convert($i, 10, 36)] = $i;
}
$len = max(strlen($str1), strlen($str2));
$str1 = str_repeat('0', $len - strlen($str1)) . $str1;
$str2 = str_repeat('0', $len - strlen($str2)) . $str2;
$pos = $len - 1;
$carry = 0;
$sum = '';
do
{
$tmp = base_convert($carry + $to10[$str1[$pos]] + $to10[$str2[$pos]], 10, 36);
$sum .= substr($tmp, -1);
$carry = (int) substr($tmp, 0, -1);
}
while (--$pos >= 0);
$sum = strrev($sum);
if ($carry)
{
$sum = base_convert($carry, 10, 36) . $sum;
}
return $sum;
}
If you have a string like this in php you can just call the index of an individual character like so:
<?
$x = "Hello";
print $x[0] . "\n";
So in other words, $string_var[n] gives you the nth char, 0-indexed.
First off, I'm assuming you want to add ascii values.
ord() might help you. Based on the other answer, something like:
<?php
function addStrings($x, $y){
// Assumes that both strings are the same length
for($i=0; $i<strlen($x); $i++){
$result[i] = ord($x[i]) + ord($y[i]);
}
return $result;
}
?>
If you use this, you'll probably want to do something if $x and $y are different lengths, but I think it gets the idea across.