I know you can tell a variable to go to a certain number of decimal places using:
string number_format ( float $number [, int $decimals = 3 ] )
However, I cannot find a way of doing this en masse for all numbers. Since I have hundreds of variables, all of which need to have the same number of decimal places (whether it means rounding or extending zeros), I would like to be able to just tell it to automatically perform this command across the board instead of typing in the above command for all variables.
Does this function exist, or am I forced to type it all out the long way? Is there maybe a line in the .ini file that I could change if there is no command?
Thanks in advance.
I'm not storing these numbers in a database - I'm echo-ing them on the screen and sending an email.
(There is an equation going on from user input being generated by an HTML form).
The person receiving the email has asked that all numbers be rounded to the third decimal, whether that means rounding off at the thousandths or extending zeros to the thousandths if it only needed whole numbers, tenths, or hundredths. There are literally hundreds of individual variables, i.e. $number1, $number2, etc.
If you are talking about all numbers applied to string variables in your script you could run this line:
if(is_int($value) || is_float($value)){
$value = number_format ( float $number ,3 );
}
Obviously as you reference strings you can also run a similar query to check if your string is a floating numeric:
$values = get_defined_vars();
foreach ($values as $key=>$value){
if(is_string($value) && (float)$value == (string)$value){
$value = number_format ((float)$value , 3 );
}
/***
Save over the original script variables with the edited ones.
Note double dollar
***/
$$key = $value;
}
unset($values,$value,$key);
You could do with reading that in base-2 (computer storage) floating point numbers are inherently stored inaccurately, read http://floating-point-gui.de/ .
Also, really, why would you need this seemingly needless precision?
But then at what stage do you run this command, at the start of your script when the values are mostly empty? Or at the end when the values are mostly been used and no longer active?
I think my answer answers your question but I think that your question does not actually articulate your issue.
In response to the comment / edited qestion:
Simply rather than using $values = get_defined_vars(); use the values from saving the inputs into an array and doing foreach above through the array.
$values = array(array of numbers);
foreach ($values as &$value){
if(is_string($value) && (float)$value == (string)$value){
$value = number_format ((float)$value , 3 );
}
}
unset($value,$key);
This will update all values in the array to being of the spcified numerical placements.
You can use the decimal extension to achieve this, with a central place to create and format. Something like this:
class Number
{
public static function create($value, $precision = Decimal::DEFAULT_PRECISION): Decimal
{
return new Decimal($value, $precision);
}
public static function format(Decimal $decimal, int $places = 3): string
{
return $decimal->toFixed(3);
}
}
Related
I'm trying to convert cents to dollars (I don't need dollar sign, just value) but when I divide a number smaller than 100 by 100 I get a strange result.
Eg.: 1/100 give me 0,01.0
I don't need that comma, I need 0.01 as it should be.
I also tried number_format but it returns a string and when I cast the result to float I get the same strange value.
How can I fix it?
Thanks in advance for your help
This is the function I'm using:
public static function convertFromCents($value) {
if(is_numeric($value)) {
$value = $value/100;
} else {
$value = 0;
}
return $value;
}
This are the proof of what I'm saying:
It looks like what you see are messages of your IDE, not the real value of your variable.
In order to achieve what you want, you can simply use round function.
The second parameter is precision, which determines how many digits will appear after the point:
// return 0.33333333333333
echo 1/3;
// return 0.33
echo round(1/3, 2);
this might be a stupid question but I have searched again and again without finding any results.
So, what I want is to show all the decimal places of a number without knowing how many decimal places it will have. Take a look at this small code:
$arrayTest = array(0.123456789, 0.0123456789);
foreach($arrayTest as $output){
$newNumber = $output/1000;
echo $newNumber;
echo "<br>";
}
It gives this output:
0.000123456789
1.23456789E-5
Now, I tried using 'number_format', but I don't think that is a good solution. It determines an exact amount of decimal places, and I do not know the amount of decimal places for every number. Take a look at the below code:
$arrayTest = array(0.123456789, 0.0123456789);
foreach($arrayTest as $output){
$newNumber = $output/1000;
echo number_format($newNumber,13);
echo "<br>";
}
It gives this output:
0.0001234567890
0.0000123456789
Now, as you can see there is an excess 0 in the first number, because number_format forces it to have 13 decimal places.
I would really love some guidance on how to get around this problem. Is there a setting in PHP.ini which determines the amount of decimals?
Thank you very much in advance!
(and feel free to ask if you have any further questions)
It is "impossible" to answer this question properly - because a binary float representation of a decimal number is approximate: "What every computer scientist should know about floating point"
The closest you can come is write yourself a routine that looks at a decimal representation of a number, and compares it to the "exact" value; once the difference becomes "small enough for your purpose", you stop adding more digits.
This routine could then return the "correct number of digits" as a string.
Example:
<?php
$a = 1.234567890;
$b = 0.123456789;
echo returnString($a)."\n";
echo returnString($b)."\n";
function returnString($a) {
// return the value $a as a string
// with enough digits to be "accurate" - that is, the value returned
// matches the value given to 1E-10
// there is a limit of 10 digits to cope with unexpected inputs
// and prevent an infinite loop
$conv_a = 0;
$digits=0;
while(abs($a - $conv_a) > 1e-10) {
$digits = $digits + 1;
$conv_a = 0 + number_format($a, $digits);
if($digits > 10) $conv_a = $a;
}
return $conv_a;
}
?>
Which produces
1.23456789
0.123456789
In the above code I arbitrarily assumed that being right to within 1E-10 was good enough. Obviously you can change this condition to whatever is appropriate for the numbers you encounter - and you could even make it an optional argument of your function.
Play with it - ask questions if this is not clear.
This problem is best expressed in code:
$var1 = 286.46; // user input data
$var2 = 3646; // user input data
$var3 = 25000; // minumum amount allowed
$var4 = ($var1 * 100) - $var2; // = 250000
if ($var4 < $var3) { // if 250000 < 250000
print 'This returns!';
}
var_dump($var4) outputs: float(25000) and when cast to int, outputs: int(24999) - and thereby lies the problem.
I don't really know what to do about it though. The issue occurs upon multiplication by 100, and while there are little tricks I can do to get around that (such as *10*10) I'd like to know if there's a 'real' solution to this problem.
Thanks :)
This is a horrible hacky solution and I slightly hate myself for it, but this gives the expected behaviour:
<?php
$var1 = 286.46; // user input data
$var2 = 3646; // user input data
$var3 = 25000; // minumum amount allowed
$var4 = ($var1 * 100) - $var2; // = 250000
if ((string) $var4 < (string) $var3) { // if 250000 < 250000
print 'This returns!';
}
Cast them to strings, and they get converted back to int/float as appropriate for the comparison. I don't like it but it does work.
Really you need BC Math for precise floating point mathematics in PHP.
Its always a good idea to use ceil (or floor based on what you want) when using float number as int
In your case try ceil($var4) before comparison!
That's what floats do sometimes, it is all due to how floats are unable to precisely represent integers from time to time.
Instead of casting it to an int, you can round the number to an integer value and then cast it to an int. (possibly that cast unnecessary, but PHP isn't to clear about how such things happen internally, and even if you know how they happen right now, they may not in the future.
I think you could use bccomp for comparing floating point values but i think it's a function that's not in the PHP Core.
Otherwise i found this function here but i couldn't test it to see if it works
function Comp($Num1,$Num2,$Scale=null) {
// check if they're valid positive numbers, extract the whole numbers and decimals
if(!preg_match("/^\+?(\d+)(\.\d+)?$/",$Num1,$Tmp1)||
!preg_match("/^\+?(\d+)(\.\d+)?$/",$Num2,$Tmp2)) return('0');
// remove leading zeroes from whole numbers
$Num1=ltrim($Tmp1[1],'0');
$Num2=ltrim($Tmp2[1],'0');
// first, we can just check the lengths of the numbers, this can help save processing time
// if $Num1 is longer than $Num2, return 1.. vice versa with the next step.
if(strlen($Num1)>strlen($Num2)) return(1);
else {
if(strlen($Num1)<strlen($Num2)) return(-1);
// if the two numbers are of equal length, we check digit-by-digit
else {
// remove ending zeroes from decimals and remove point
$Dec1=isset($Tmp1[2])?rtrim(substr($Tmp1[2],1),'0'):'';
$Dec2=isset($Tmp2[2])?rtrim(substr($Tmp2[2],1),'0'):'';
// if the user defined $Scale, then make sure we use that only
if($Scale!=null) {
$Dec1=substr($Dec1,0,$Scale);
$Dec2=substr($Dec2,0,$Scale);
}
// calculate the longest length of decimals
$DLen=max(strlen($Dec1),strlen($Dec2));
// append the padded decimals onto the end of the whole numbers
$Num1.=str_pad($Dec1,$DLen,'0');
$Num2.=str_pad($Dec2,$DLen,'0');
// check digit-by-digit, if they have a difference, return 1 or -1 (greater/lower than)
for($i=0;$i<strlen($Num1);$i++) {
if((int)$Num1{$i}>(int)$Num2{$i}) return(1);
else
if((int)$Num1{$i}<(int)$Num2{$i}) return(-1);
}
// if the two numbers have no difference (they're the same).. return 0
return(0);
}
}
}
The problem is that floats just cannot represent some numbers. Since PHP doesn't have a "decimal" (or other fixed-point) type, you can basically only hack your way around these problems.
Assuming the first number in your example $var1 = 286.46 denotes some kind of money, you could just convert that to cents directly after the user entered it (e.g. through stripping the point and reading it as an integer) and thus calculate everything using integer math.
That's not a general solution - and I doubt that one exists (short of using arbitrary precision numbers, which some PHP extensions provide - but I that smells like overkill to me).
After a couple of minutes i've realize the bug that i was having: the magic 2147483647 number, that upper limit for integer type on PHP/32. I need to manage biggers number in my function:
public function albumExists($name) // e.g. 104112826372452
{
$albums = $this->getAlbums();
// If $name is int, search the key in $albums
if(is_int($name) && ($found = array_key_exists($id = intval($name), $albums)))
return ($found ? $id : false);
// Start looking for $name as string
foreach($album as $id => $a) if ($a->name == $name) return intval($id);
return false; // Found nothing
}
in order to give the ability to search both by id and name. But intval() will always return the upper limit. How can handle quite big numbers like, say, 104112826372452? Ideas?
EDIT: usage example:
$album = $fb->createAlbum('Test Album'); // Will return album id
// The use albumExists to check if id exists
$photo1 = $fb->uploadPhoto('mypic1.png', null, $album);
$photo2 = $fb->uploadPhoto('mypic2.png', null, 'Test Album'); // find or create
If you're converting to an int for sanity purposes (so it appears), perhaps you could just adjust it to evaluate it purely on it's numeric basis instead of int datatype:
if(ctype_digit($name) && ($found = array_key_exists($id = $name, $albums)))
return ($found ? $id : false);
//etc
Actually, should this work too?
if(ctype_digit($name) && ($found = array_key_exists($name, $albums)))
return ($found ? $name: false);
//etc
As workaround you can use the gmp or bcmath functions for that.
It's not quite clear why you insist on casting to PHP integers. Just leave your database numbering as strings, when don't need to calculate with them. Not everything that looks like a number needs to be represented as number.
I guess your real problem is the differentation with is_int(). Just use is_numeric() in its place, which works with arbitrary-length numeric strings and does not depend on integer-casted values.
An int has an upper limit, and bigger numbers will be represented as floats, which are imprecise and therefore a bad idea to use in this situation. Use a string to store such numbers and the BC Math extension if you need to do calculations on it.
Unfornuately PHP int type can only go upto 2147483647 but PHP float can hold integers upto 10000000000000
Check out php.net
http://php.net/manual/en/language.types.integer.php
UPDATE
PHP.net says that a float can accurately hold an integer upto 10000000000000. Im not sure if float has an upper limit though.
One option is to run PHP on a 64bit OS as the in size is determined by the underlying operating system. This is obviously dependent if you can get access to 64bit hardware, one thing to note is that this will be faster than using gmp/bcmath but unless pure speed is your aim it probably won't be an issue for you
I have a e-commerce shop and on the shopping cart page it gives me a separate price for every product, but I need total price.
in order to do that, I need to calculate all these values together and that's fine.
But, what bugs me is that I should calculate the sum of variables that are given in this format:
$455.00
What is the best way to extract the value "455" so I could add it to another value afterwards?
I hope I made myself clear...
Don't use float, but instead use an integer in cent. Floats are not precise (see Floating Point Precision), so the calculation tend to fail if you use floats. That's especially a burden if it is related to payments.
$str = '$455.00';
$r = sscanf($str, '$%d.%d', $dollar, $cent);
if ($r <> 2 or $cent > 99 or $cent < 0 or $dollar > 9999 or $dollar < 0) throw new Exception(sprintf('Invalid string "%s"', $str));
$amountInDollarCents = $dollar * 100 + $cent;
echo $str, ' -> ', $amountInDollarCents;
Demo
If you need only the dollar sign removed, use str_replace. To convert that to int or float, typecast it. However, using float results in non-exact calculations so be careful with it!
$newval = (int)str_replace('$', '', '$455.00');
I think that your ECommerce site only has $ (USD)
$price= substr($string_price,1);
This will convert your string to a float:
$price = (float)substr("$455.00", 1);
echo($price);
For more information, you can see this answer, which has a couple of good links for you in it.
What about the following:
$amount = array();
$amount[0] = '$455.15';
$amount[2] = '$85.75';
$total = 0;
foreach ($amount AS $value) {
$value = str_replace('$', '', $value);
$total += $value;
}
echo $total . "\n";
The cleaning operation is:
$value = str_replace('$', '', $value);
You might want to extract it in a function, especially if you need to use it in more than one place.
Another thing to think about is, why do you have the value in such way? It's a display format and such conversion should be the last to be done, ideally by the template. Maybe, if possible, you should consider to fix the code before, instead of applying a patch like this one.
It really looks like your program is doing it wrong. You should really represent all prices as (double) instead of a string. Then only when you need to show the price to the user you would prepend the $ sign to it, converting it to a string. But your program should really treat prices as numbers and not strings.
If you storing your price in the database as a string "$5.99" then you are really doing it wrong.
It's been a long time since I worked with PHP, so I don't know what the best practice would be for working with currency. One quick method would be to remove "$" and ".", and just add together the resulting as integers.
use str_replace() for instance, and replace "$" and "." with an empty string: http://se2.php.net/manual/en/function.str-replace.php
This will give you the whole sum in cents (thus avoiding some potential rounding problems). You can then divide it by 100 and format it however you like to display the sum as dollars.