BC math string into scientific notation - php

i use bc math (http://php.net/manual/en/book.bc.php).
the value is, e.g.,
$value = "0.0000000000000000000001111111111111111111112";
how can i transform it into scientific notation. it should be like this:
$value = "1.111111111111111111112E-22";
i tried amongst others
sprintf("%E",$value) or a (float)
but the result are only
1.111111E-22 (sprintf)
That are not so many significant figures as it should be :(

Count how many zeroes there are.
Note that because you are using big numbers, you have to work on them as strings. So...
if( preg_match("/^0\.0*/",$value,$m)) {
$zeroes = strlen($m[0]);
$value = substr($value,$zeroes,1)
.rtrim(".".substr($value,$zeroes+1),"0.")
."E-".($zeroes-1);
}
elseif( preg_match("/(\d+)(?:\.(\d+))?/",$value,$m)) {
$zeroes = strlen($m[1]);
$value = substr($value,0,1)
.rtrim(".".substr($m[1],1).$m[2],"0.")
."E+".($zeroes-1);
}
// else 1 <= number < 10, so no transformation needed
Test cases:
1000000 => 1E+6
1234.5678 => 1.2345678E+3
0.9 => 9E-1
0.123 => 1.23E-1
0.00000011111122222 => 1.1111122222E-7

You can do $float_value = (float)$value; and get 1.1111111111111E-22 but beyond that the float cannot offer more precision so it can't show that 2 at the end.

Related

PHP calculate integer of division

I would like to calculate an integer part of division. The numerator and denominator (especially their precision) should not be altered because it might change from one calculation to other, also the denominator is as an example, its integer part as well as decimal part might be different.
I tried to use floor, ceil, round but none of them produced a correct result.
Please see the code below, perhaps you'll spot the error:
<?php
$valueArr = [
// should return 1999
199.90,
199.92,
199.95,
199.97,
// should return 2000
200.00,
200.02,
200.05,
200.07,
// should return 2001
200.10,
200.12,
200.15,
200.17,
];
$denominator = 0.1;
$resultArr = [];
foreach ($valueArr as $value) {
$key = (string) $value;
$result = floor($value / $denominator);
$resultArr[$key] = $result;
}
echo "Denominator:\n";
var_dump($denominator);
echo "\n";
print_r($resultArr);
that gives result:
Denominator:
float(0.1)
Array
(
[199.9] => 1999
[199.92] => 1999
[199.95] => 1999
[199.97] => 1999
[200] => 2000
[200.02] => 2000
[200.05] => 2000
[200.07] => 2000
[200.1] => 2000
[200.12] => 2001
[200.15] => 2001
[200.17] => 2001
)
where:
[200.1] => 2000
is wrong because integer part of (200.1 / 0.1) is 2001.
Do you know how to produce correct result for the $valueArr as above? What did I do wrong?
I'm using PHP 7.3.8 (cli)
So there's two issues you're going to run into here. First is the lack of precision on floating points in general, and the second is PHP's automatically coercing your inputs into floating points before you have a chance to use something like bcdiv.
As such: First step is to store your input numbers as strings so you're not losing precision out of the gate by the parser interpreting them as floating point numbers. Then use bcdiv on them.
Since you're just after the integer portion and bcdiv returns a string on success, we can just drop the decimal part with string functions.
<?php
$valueArr = [
// should return 1999
'199.90',
'199.92',
'199.95',
'199.97',
// should return 2000
'200.00',
'200.02',
'200.05',
'200.07',
// should return 2001
'200.10',
'200.12',
'200.15',
'200.17',
'381736192374124241.294',
];
$denominator = '0.1';
$resultArr = [];
foreach ($valueArr as $value) {
$key = (string) $value;
$result = explode('.', bcdiv($value, $denominator))[0];
$resultArr[$key] = $result;
}
echo "Denominator:\n";
var_dump($denominator);
echo "\n";
print_r($resultArr);
And if you want to do something like rounding, ceil, floor with the output of bcdiv, check out this answer:
https://stackoverflow.com/a/51390451/395384
I get the right results using bcdiv().
$result = bcdiv($value,$denominator);
I always use BcMath, seems more reliable to me.
You did not do anything wrong. This is a problem with computers. It's difficult to represent float-point numbers accurately in a fixed space.
Try this
foreach ($valueArr as $v) {
$resultArr []= floor($v * (1 / $denominator));
}
My advice would be to try to convert division operation into multiplication.
In your case, division by 0.1 === multiplication by 10. So, use that.
In case you don't have the bcdiv that comes with the BcMath extenstion
you may use the sprintf() function to achieve a proper result with the floor() and without any problem even in a case the denominator is a float smaller than 0.0001.
Instead of:
$result = floor($value / $denominator);
use this:
$result = floor(sprintf('%f', $value / $denominator));
and you'll get the proper:
[200.1] => 2001

number_format() php remove trailing zeros

Is there a way with number_format() to leave out decimal places if the number is not a float/decimal?
For example, I would like the following input/output combos:
50.8 => 50.8
50.23 => 50.23
50.0 => 50
50.00 => 50
50 => 50
Is there a way to do this with just a standard number_format()?
You can add 0 to the formatted string. It will remove trailing zeros.
echo number_format(3.0, 1, ".", "") + 0; // 3
A Better Solution: The above solution fails to work for specific locales. So in that case, you can just type cast the number to float data type. Note: You might loose precision after type casting to float, bigger the number, more the chances of truncating the number.
echo (float) 3.0; // 3
Ultimate Solution: The only safe way is to use regex:
echo preg_replace("/\.?0+$/", "", 3.0); // 3
echo preg_replace("/\d+\.?\d*(\.?0+)/", "", 3.0); // 3
Snippet 1 DEMO
Snippet 2 DEMO
Snippet 3 DEMO
If you want to use whitespace here is better solution
function real_num ($num, $float)
{
if (!is_numeric($num) OR is_nan($num) ) return 0;
$r = number_format($num, $float, '.', ' ');
if (false !== strpos($r, '.'))
$r = rtrim(rtrim($r, '0'), '.');
return $r;
}
Use:
$a = 50.00;
$a = round($a, 2);
Even though the number has 2 zeros trailing it, if you round it, it won't show the decimal places, unless they have some kind of value.
So 50.00 rounded using 2 places will be 50, BUT 50.23 will be 50.23.
Unless you specify at which point to round up or down, it won't change your decimal values. So just use default round()

Remove useless 0 after 3 digits and round after 6 digit

I use the floatval() function to remove useless 0s, but I want to keep at least 3 significant numbers. Like if I have 0.1800000 I want to show 0.180 and if I have 1.8454214 I want to show 1.845421. How can I do a round after 6 digits and remove useless 0s after 3 digits?
$value = 1.80000;
$value = floatval(round($value,6));
echo $value; //I get 1.8
Or if I have
$value = 1.84542146543;
$value = floatval(round($value,6));
echo $value; //I get 1.845421
And this works fine, but not if I've got a lot of 0s.
I always need minimum 3 decimal, but it can be more.
Try with the below code
$value = 1.80000;
$value = floatval(round($value,6));
$valArr = explode('.', $value);
if(isset($valArr[1])){
if(strlen($valArr[1]) < 3){
$valArr[1] = str_pad($valArr[1], 3, "0", STR_PAD_RIGHT);
$value = $valArr[0].'.'.$valArr[1];
}
else{
$value = floatval(round($value,6));
}
}
echo $value;
It will work if you want to print the 0s in your web page.
Use a combination of floor and sprintf to truncate the float to a string with 3 decimal places. Then use max to compare it with the rounded float. PHP will compare the values numerically, returning the first parameter (padded with zeros) if they are numerically the same, only returning the second value if it is numerically greater (ie there is a digit larger than 0 after the third decimal place).
$value = max(
sprintf("%.3f", floor($value * pow(10, 3)) / pow(10, 3)),
round($value, 6));

Convert Gigabytes to Bytes

I am in the position where I am trying to convert gigabytes to bytes from a submit form. I have searched around and I am unable to find anything suitable.
Currently when converting bytes to gigabytes I use this method, which works perfectly.
public function byteFormat($bytes, $unit = "", $decimals = 2)
{
$units = array('B' => 0, 'KB' => 1, 'MB' => 2, 'GB' => 3, 'TB' => 4,
'PB' => 5, 'EB' => 6, 'ZB' => 7, 'YB' => 8);
$value = 0;
if ($bytes > 0) {
// Generate automatic prefix by bytes
// If wrong prefix given
if (!array_key_exists($unit, $units)) {
$pow = floor(log($bytes)/log(1024));
$unit = array_search($pow, $units);
}
// Calculate byte value by prefix
$value = ($bytes/pow(1024,floor($units[$unit])));
}
// If decimals is not numeric or decimals is less than 0
// then set default value
if (!is_numeric($decimals) || $decimals < 0) {
$decimals = 2;
}
// Format output
return sprintf('%.' . $decimals . 'f '.$unit, $value);
}
There seems to be plenty of examples of bytes to other formats but not the other way around.
I have seen that I can convert the number 1.5 like so
round(($number[0] * 1073741824));
The result is 12992276070, however, when using the byteformat method shown above, I get the following 1610612736, this seems quite a difference between the two methods.
Can anyone suggest a more stable method for converting gigabytes to bytes.
Well there are two different unit symbol, decimal and binary.
As you can see here, decimal multiplication is by 1000 and binary by 1024.
so if you are using "B"(byte), just do something like:
$bytenumber=$giga*pow(1024,3);
if using "b"(bit) :
$bitnumber=$giga*pow(1000,3);
P.S:$giga is your giga number.
You can only get as accurate of a conversion as there are numbers after the decimal place. If you start with 1.29634 gigs you'll get a closer representation to it's actual byte value versus calling it 1.3 Gigs. Is that what you're after?
numberOfBytes = round (numberOfGb * 1073741824)
is the exact answer to your question. It seems, you have miscalculated. Try to check it on a calculator.
The other problem is that if you have the source number of 2 digits, it is incorrect to give an answer in more or less than 2 digits. The correct counting will be:
source: 1.5GB
counting: 1.5GB*1073741824 B/GB= 1610612736 B
rounding to the last significant digit: 1610612736 B ~= 1.6e9 B
answer: 1.6e9 B
But, of course, many clients do not really want the correct answer, they want THEIR answer. It is up to you to choose.

php Determine a float variable has two decimal places

MySQL data imoprt mongo database.
price float(15,2) in mysql, mongo is not float(15,2).
I want to Determine a var $price have two decimal places.
eg. 100.00 is right, 100 or 100.0 is wrong.
eg.1
$price = 100.00;
$price have two decimal, it's right.
eg.2
$price = 100.0;
$price have not two decimal, it's wrong.
I like to use Regular Expressions to do these things
function validateTwoDecimals($number)
{
if(preg_match('/^[0-9]+\.[0-9]{2}$/', $number))
return true;
else
return false;
}
(Thanks to Fred-ii- for the corrections)
Everybody is dancing around the fact that floating point numbers don't have a number of decimal places in their internal representation. i.e. in float 100 == 100.0 == 100.00 == 100.000 and are all represented by the same number, effectively 100 and is stored that way.
The number of decimal places in this example only has a context when the number is represented as a string. In which case any string function that counts the number of digits trailing the decimal point could be used to check.
number_format($price, $numberOfDecimalDigits) === $price;
or
strrpos($price, '.') === strlen($price) - 1 - $numberOfDecimalDigits;
Trivia: $price should not be called a "float variable". This is a string that happens to represent a float value. 100.00 as a float has zero decimal digits, and 100.00 === 100 as float :
$price = 100.00;
echo $price; // output: 100
$price2 = (float)100;
echo $price === $price2; // ouput: 1
In order for this to work, the number will need to be wrapped in quotes.
With the many scripts I've tested, using $price = 100.00; without quotes did not work, while $price = 100.10; did, so this is as best as it gets.
<?php
$number = '100.00';
echo $number.'<br>';
$count = explode('.',$number);
echo 'The number of digits after the decimal point is: ' . strlen($count[1]);
if(strlen($count[1]) == 2){
echo "<br>";
echo "There is 2 decimal points.";
}
else{
echo "<br>";
echo "There is not 2 decimal points.";
}
After you format the value, you can check with simply splitting the value as string into 2 parts, for example with explode ...
$ex=explode('.',$in,2); if (strlen($ex[1])==2)
{
// true
}
else
{
// false
}
But again, as i've commented already, if you really have floating input, this is just not a reliable way, as floating numbers are without set decimal places, even if they appears so because of the rounding at the float=>string conversion
What you can do, if you really have floating numbers and wish to have xxx.yy format numbers:
1) convert float to string using round($x,2), so it will round to 2 decimal places.
2) explode the number as i've described, and do the following:
while (strlen($ex[1]<2)) {$ex[1].='0';}
$number=implode('.',$ex);
I would use the following function for that:
function isFloatWith2Decimals($number) {
return (bool) preg_match('/^(?:[1-9]{1}\d*|0)\.\d{2}$/', $number);
}
This will also check if you have only one leading 0 so number like 010.23 won't be considered as valid whereas number like 0.23 will.
And if you don't care about leading 0 you could use simpler method:
function isFloatWith2Decimals($number) {
return (bool) preg_match('/^\d+\.\d{2}$/', $number);
}
Of course numbers need to be passed as string - if you pass 100.00 won't be considered as true, whereas '100.00' will

Categories