What is the most efficient way to make and odds system? - php

I'm trying to design an odds system that goes from 1-100, however it also uses 0-1 for rarer odds.
I was told I should use a floating point format, but I don't know that.
Basically I have..
if (mt_rand(1,1000)/100 == $odds) {} else if (mt_rand(1,100) == $odds) {}
however that only yields the same probability.
I looked up floating point format in the PHP manual, but the answers there couldn't help me.

See Odds to understand how to convert your odds to a probability. If you have odds of 4:1 then there is a 1/5 == 0.2 probability of the event. If your odds are .2:1 then there is a 5/6 (about .833) probability of the event happening. In general, if the odds are m:n against then the probability is n/(m+n).
Now, if you want to simulate whether an event occurs or not, you need to get a random floating point number between 0 and 1 then check if this is less than the probability of the event. You can use something like mt_rand(0,1000)/1000 to get a random number between 0 and 1.
Examples:
$odds1 = 4; // 4:1
$prob1 = 1/($odds1+1); // 1/5
if( mt_rand(0,1000)/1000 <= $prob1 ) {
// event happened
}
$odds2 = .2; // .2:1
$prob2 = 1/($odds2+1); // 5/6
if( mt_rand(0,1000)/1000 <= $prob2 ) {
// event happened
}

Floating point values are inexact. (See Why does `intval(19.9 * 100)` equal `1989`? and search: php floating point inexact.)
You cannot use == for floating point values. A simple 5/10 == 0.5 might already be wrong due to inherent precision loss.
You can either round numbers before comparison, your what I'd advise in your case, pre-convert floats into integers:
# 52 == 100*0.52
if (mt_rand(1,100) == round(100*$odds)) {
Instead of comparing 0.99 with another float, you convert your odds into an integer 99 and compare it with an integer random in the range 1 to 100. If odds already was an integer, not a float, then the *100 multiplication will already cut it out of that first (faux float) comparison.

if($odds < 1){
// floating point math here
if((float)mt_rand(0,100) / 100.0 < $odds){
echo "you're a float winner, harry";
}
}else{
if(mt_rand(0,100) < $odds){
echo "you're an int winner, harry!";
}
}

Related

Is there a clever way to do this with pure math

I've got this spot of code that seems it could be done cleaner with pure math (perhaps a logarigthms?). Can you help me out?
The code finds the first power of 2 greater than a given input. For example, if you give it 500, it returns 9, because 2^9 = 512 > 500. 2^8 = 256, would be too small because it's less than 500.
function getFactor($iMaxElementsPerDir)
{
$aFactors = range(128, 1);
foreach($aFactors as $i => $iFactor)
if($iMaxElementsPerDir > pow(2, $iFactor) - 1)
break;
if($i == 0)
return false;
return $aFactors[$i - 1];
}
The following holds true
getFactor(500) = 9
getFactor(1000) = 10
getFactor(2500) = 12
getFactor(5000) = 13
You can get the same effect by shifting the bits in the input to the right and checking against 0. Something like this.
i = 1
while((input >> i) != 0)
i++
return i
The same as jack but shorter. Log with base 2 is the reverse function of 2^x.
echo ceil(log(500, 2));
If you're looking for a "math only" solution (that is a single expression or formula), you can use log() and then take the ceiling value of its result:
$factors = ceil(log(500) / log(2)); // 9
$factors = ceil(log(5000) / log(2)); // 13
I seem to have not noticed that this function accepts a second argument (since PHP 4.3) with which you can specify the base; though internally the same operation is performed, it does indeed make the code shorter:
$factors = ceil(log(500, 2)); // 9
To factor in some inaccuracies, you may need some tweaking:
$factors = floor(log($nr - 1, 2)) + 1;
There are a few ways to do this.
Zero all but the most significant bit of the number, maybe like this:
while (x & x-1) x &= x-1;
and look the answer up in a table. Use a table of length 67 and mod your power of two by 67.
Binary search for the high bit.
If you're working with a floating-point number, inspect the exponent field. This field contains 1023 plus your answer, except in the case where the number is a perfect power of two. You can detect the perfect power case by checking whether the significand field is exactly zero.
If you aren't working with a floating-point number, convert it to floating-point and look at the exponent like in 3. Check for a power of two by testing (x & x-1) == 0 instead of looking at the significand; this is true exactly when x is a power of two.
Note that log(2^100) is the same double as log(nextafter(2^100, 1.0/0.0)), so any solution based on floating-point natural logarithms will fail.
Here's (nonconformant C++, not PHP) code for 4:
int ceillog2(unsigned long long x) {
if (x < 2) return x-1;
double d = x-1;
int ans = (long long &)d >> 52;
return ans - 1022;
}

Floating point rounding error php ...how can I make sure it works correctly?

I have the following function that determines if I sale is fully paid for. I don't remember why I did it this way, but it has been working so far and I don't remember why I had to do it this way.
function _payments_cover_total()
{
//get_payments is a list of payment amounts such as:
//10.20, 10.21, or even 10.1010101101 (10 decimals max)
$total_payments = 0;
foreach($this->sale_lib->get_payments() as $payment)
{
$total_payments += $payment['payment_amount'];
}
//to_currency_no_money rounds total to 2 decimal places
if (to_currency_no_money($this->sale_lib->get_total()) - $total_payments ) > 1e-6 ) )
{
return false;
}
return true;
}
I am wondering if there is ever a case where due to a rounding error that this function would return false when it shouldn't.
The main part I have a question about is:
> 1e-6
I think before I had, but it was causing problems in some cases.
> 0
I think you are doing what is mentioned on php floating help page. To quote it directly :
To test floating point values for equality, an upper bound on the
relative error due to rounding is used. This value is known as the
machine epsilon, or unit roundoff, and is the smallest acceptable
difference in calculations.
$a and $b are equal to 5 digits of precision.
<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if(abs($a-$b) < $epsilon) {
echo "true";
}
?>
So in your case:
(to_currency_no_money($this->sale_lib->get_total()) - $total_payments) > 1e-6
relative error due to rounding should not be great than 1e-6 or 0.000001
if you are not sure about left operand being greater than right 100% time,then you should add abs() e.g for correctness.
$relative_error=to_currency_no_money($this->sale_lib->get_total()) - $total_payments;
if(abs($relative_error) > 1e-6){
return false
}
return true;
$x = (1.333-1.233)-(1.334-1.234);
echo $x;
//result = $x = -2.2204460492503E-16 - close to zero
//but (1.333-1.233)-(1.334-1.234) = 0.1 - 0.1 = 0 (in calculator)
if($x === 0){
echo "|zero";
}
else {
echo "|non zero"; //<== this is result
}
//screen = -2.2204460492503E-16|non zero
//how to get to zero?
if($x > 1e-6){//1e-6 mathematical constant
echo "|non zero";
}
else {
echo "|zero";//this is result
}
//screen -2.2204460492503E-16|non zero|zero
if ($x > 1e-6 )
{
echo " false";
//echo "|non zero";
//return false;
}
else{
echo " true";//<== this resut
//echo "|zero";
//return true;
}
//screen -2.2204460492503E-16|non zero|zero true
printf("%.1f<br />", 1e-1);
printf("%.2f<br />", 1e-2);
printf("%.3f<br />", 1e-3);
printf("%.4f<br />", 1e-4);
printf("%.5f<br />", 1e-5);
printf("%.6f<br />", 1e-6);
printf("%.7f<br />", 1e-7);
printf("%.8f<br />", 1e-8);
printf("%.9f<br />", 1e-9);
printf("%.10f<br />", 1e-10);
printf("%.11f<br />", 1e-11);
printf("%.12f<br />", 1e-12);
printf("%.29f<br />", -2.2204460492503E-16);
//0.1
//0.01
//0.001
//0.0001
//0.00001
//0.000001
//0.0000001
//0.00000001
//0.000000001
//0.0000000001
//0.00000000001
//0.000000000001
//-0.00000000000000022204460492503
I am sorry, but when dealing with currency, you shouldn't really be using PHP floats, as IMSoP stated. The reason is also from PHP float help pages:
Additionally, rational numbers that are exactly representable as
floating point numbers in base 10, like 0.1 or 0.7, do not have an
exact representation as floating point numbers in base 2, which is
used internally, no matter the size of the mantissa. Hence, they
cannot be converted into their internal binary counterparts without a
small loss of precision. This can lead to confusing results: for
example, floor((0.1+0.7)*10) will usually return 7 instead of the
expected 8, since the internal representation will be something like
7.9999999999999991118....
So never trust floating number results to the last digit, and do not
compare floating point numbers directly for equality. If higher
precision is necessary, the arbitrary precision math functions and gmp
functions are available.
Note that the help page specifically says you can't trust float results to the last digit, no matter how short (after comma) it is.
So while you do have very short floats (just 2 digits after comma), the float precision (1e-6) doesn't enter into it really, and you can't trust them 100%.
Since it is a question of money, in order to avoid angry customers and lawsuits accusing of penny shaving (https://en.wikipedia.org/wiki/Salami_slicing), the real solutions are:
1) either use PHP BC math, which works with string representation of numbers
http://php.net/manual/en/book.bc.php
2) as IMSoP suggested, use integers and store the amounts in smallest denomination (cents, eurocents or whatever you have) internally.
First solution might be a bit more resource intense: I haven't used BC math myself much, but storing strings and doing arbitrary precision math (which might be a bit of an overkill in this case) are by definition more RAM and CPU intense than working with integers.
It might, however, need less changes in other parts of the code.
The second solution requires changes to represenation, so that wherever user sees the amounts, they are normalized to dollars,cents (or whatever have you).
Note, however, that in this case also you run problems with rounding risks at some point, as when you do:
float shown_amount; // what we show to customer: in dollars,cents
int real_amount; // what we use internally: in cents
shown_amount = cent_amount / 100;
you may reintroduce the rounding problems as you have floats again and possibilities for rounding errors, so tread carefully and be sure to make calculations and roundings only on real_amount, never on shown_amount

Abs() - issue with absolute value function in PHP

Can anyone explain why this code does this in regards to abs() (absolute value) -
In my code it will display 'GREATER' - although 0.50 is never GREATER than 0.5, am I missing out something here with the abs function?
$logic = abs(1.83333333333 - 2.33333333333); // 0.50
$limit = 0.50;
if ($logic > $limit) {
echo 'IS GREATER';
} else {
echo 'IS NOT GREATER';
}
Passing floating point numbers to abs you will get a floating point number as result. In that case you can experience problems with the floating point representation: a floating point is never absolutely precise, thus you are most likely getting a number that is not exactly 0.50 but something like 0.500000...01. You could try to round the result to the desired precision (in your case I guess it is two) with the php round function.
If you don't want to round as suggested by #Aldo's answer and your server supports the GMP math functions, you could use gmp_abs() instead. This way you don't run into PHP's inherent floating point problems.
Due to the way floating point math works, your absolute value $logic results in this value:
0.50000000000000022204
which is greater than 0.5
NB: above evaluated using Javascript which uses double precision math for all numbers:
Math.abs(1.83333333333 - 2.33333333333).toFixed(20)
Never compare floats by equality - user the epsilon technique instead PHP: Floating Point Numbers
define('EPSILON', 1.0e-8);
$logic = abs(1.83333333333 - 2.33333333333); // 0.50
$limit = 0.50;
$diff = $logic - $limit;
if (abs($diff) < EPSILON)
echo 'IS EQUAL';
else
echo 'IS NOT EQUAL';

PHP does not find two equal numbers to be equal? bug? [duplicate]

This question already has answers here:
Closed 12 years ago.
Possible Duplicate:
compare floats in php
i have a condition:
if($x <= .3)
echo 1;
it will echo 1 only if x is less than .3
if $x is EQUAL to .3, i do not get a 1.
i have tried wrapping the x in floatval(), but no luck
i tried to echo the $x and i get "0.3"
i have tried if ($x == .3) - nothing
if i have tried if (.3 == .3) which obviously works
any ideas? is this a PHP bug?
It's all about binary representation of floating point numbers :
var_dump(sprintf("%.40f", 0.3));
// string(42) "0.2999999999999999888977697537484345957637"
Basically, 0.3 can't be represented exactly in base 2, so it gets truncated after a few digits. It's basically like 1/3 in base 10 : you can type 0.3, but 0.33 is more precise, so is 0.333, 0.3333, etc. You can't represent it exactly.
Floating point values are not exact. You can check to see if it's roughly <= 0.3 like this:
if ($x <= 0.3000001) {
echo 'yay';
}
Here you go, big, red and fat: Floating Point Numbers:
It is typical that simple decimal
fractions like 0.1 or 0.7 cannot be
converted into their internal binary
counterparts without a small loss of
precision. This can lead to confusing
results: for example,
floor((0.1+0.7)*10) will usually
return 7 instead of the expected 8,
since the internal representation will
be something like 7.9.
This is due to the fact that it is
impossible to express some fractions
in decimal notation with a finite
number of digits. For instance, 1/3 in
decimal form becomes 0.3.
So never trust floating number results
to the last digit, and never compare
floating point numbers for equality.
If higher precision is necessary, the
arbitrary precision math functions and
gmp functions are available.
PS: There is even more fun:
INF == INF => false
INF < INF => true
INF > INF => true
So infinity is not infinity and infinity is smaller than infinity and greater than infinity at the same time. If you think about it, it does actually make some sense...
hmmm, i have to assume that your var $x is really not equal to .3.
i just tested this:
<?
$var = 0.3;
if( $var <= .3 )
{
echo 'yay';
}
else
{
echo 'boo';
}
?>
and it outputs yay
$x = .4;
if ($x <= .3) { echo 1; }
Works fine here...tried different values at $x
however... where are you getting $x ?? you might need to "round" the value before comparing.
Floating points are not 100% accurate. In short, the fractional component is generally stored by adding 1/(2^n) together. e.g., 1/2 + 1/4 is how 3/4 would be stored. So this isn't a bug, nor is it a specific PHP question.
However, this should always be true:
$x = 0.3;
if ($x == 0.3) echo "true";
because the same inaccuracy would be present in both.
But this is not guaranteed to be true:
$x = 0.1;
$y = 0.2;
if ($x + $y == 0.3) echo "true";
A simple way to work around this is to use a delta:
if (abs($a - $b) < $delta) echo "true"
where $delta is a very small number.
If you need accuracy, then check out something like the BCMath extension.
If this is for money, then it's usually easier to just do calculations in whole cents (integers), where $1.23 = 123.

What would be the best way to detect if a float has a zero fraction value (e.g. 125.00) in PHP?

See, I want to write a function that takes a float number parameter and rounds the float to the nearest currency value (a float with two decimal places) but if the float parameter has a zero fraction (that is, all zeroes behind the decimal place) then it returns the float as an integer (or i.e. truncates the decimal part since they're all zeroes anyways.).
However, I'm finding that I can't figure out how to determine if if a fraction has a zero fraction. I don't know if there's a PHP function that already does this. I've looked. The best I can think of is to convert the float number into an integer by casting it first and then subtract the integer part from the float and then check if the difference equals to zero or not.
if($value == round($value))
{
//no decimal, go ahead and truncate.
}
This example compares the value to itself, rounded to 0 decimal places. If the value rounded is the same as the value, you've got no decimal fraction. Plain and simple.
A little trick with PHPs type juggling abilities
if ($a == (int) $a) {
// $a has a zero fraction value
}
I think the best way:
if ((string)$value == (int)$value){
...
}
Example:
$value = 2.22 * 100;
var_dump($value == (int)$value); // false - WRONG!
var_dump($value == round($value)); // false - WRONG!
var_dump((string)$value == (int)$value); // true - OK!
function whatyouneed($number) {
$decimals = 2;
printf("%.".($number == (int)($number) ? '0' : $decimals)."F", $number);
}
So basically it's either printf("%.2F") if you want 2 decimals and printf("%.2F") if you want none.
Well, the problem is that floats aren't exact. Read here if you're interested in finding out why. What I would do is decide on a level of accuracy, for example, 3 decimal places, and base exactness on that. To do that, you multiply it by 1000, cast it to an int, and then check if $your_number % 1000==0.
$mynumber = round($mynumber *1000);
if ($mynumber % 1000==0)
{ isInt() }
Just so you know, you don't have to write a function to do that, there's already one that exists:
$roundedFloat = (float)number_format("1234.1264", 2, ".", ""); // 1234.13
If you want to keep the trailing .00, just omit the float cast (although it will return a string):
$roundedFloatStr = number_format("1234.000", 2, ".", ""); // 1234.00

Categories