I've inherited a project written in PHP which has a double entry style cashbook, which requires that the amount going in is equal to the amount going out.
When validating figures entered via a form (to work out if it equals zero), it runs the code below which works fine until there is a decimal number, which seems to make it randomly fall over and return an incorrect value.
In the example below, it returns a really tiny number from 53.87 - 53.87, which should be zero.This has been stripped back to eliminate other things from causing problems, so I've removed a lot of input validation etc.
<?php $inputs = array(
array(
"in" => '',
"out" => '249.6',
),
array(
"in" => '',
"out" => '396',
),
array(
"in" => '554.4',
"out" => ''
),
array(
"in" => '145.07',
"out" => ''
),
array(
"in" => '',
"out" => '53.87',
),
);
$fTotal = 0;
echo "Start at 0: ";
foreach($inputs as $key=>$sRef) {
$itemValid = true;
$aItem = array();
$amountIn = $inputs[$key]['in'];
$amountOut = $inputs[$key]['out'];
if ($itemValid) {
echo "'".$fTotal."'";
$is_in = 0;
if ($amountIn > 0.0) {
$is_in = 1;
echo "+";
$amount = $amountIn;
$fTotal = $fTotal + $amountIn;
} else {
$is_in = 0;
echo "-";
$amount = $amountOut;
$fTotal = $fTotal - $amountOut;
}
echo "'".$amount."'=";
echo "'".$fTotal."' | ";
$aItem["is_in"] = $is_in;
$aItem["amount"] = $amount;
$aItems[] = $aItem;
}
}
You can run this on a sandbox here.
Here is the expected output:
Start at 0: '0'-'249.6'='-249.6' | '-249.6'-'396'='-645.6' | '-645.6'+'554.4'='-91.2' | '-91.2'+'145.07'='53.87' | '53.87'-'53.87'='0' |
Here is the actual output:
Start at 0: '0'-'249.6'='-249.6' | '-249.6'-'396'='-645.6' | '-645.6'+'554.4'='-91.2' | '-91.2'+'145.07'='53.87' | '53.87'-'53.87'='-4.9737991503207E-14' |
What is wrong here?
Update
Following help below, here's the working code for anyone who stumbles on this in the future.
This is happening because of floating point (decimal) maths where computers like binary maths. Sometimes decimal numbers don't have a nice representation in binary so these tiny differences happen.
If you can state that every number can be rounded to 2 decimal points, and based on your limited dataset, wrapping your 'sums' in number_format($fTotal + $amountIn , 2) etc can sort this out for you.
Alternatively for a "more accurate", just wrap the final one eg. echo "'".number_format($fTotal, 2)."' | "; (or 0 or whatever)
Your numbers are being interpreted as strings.
Try this:
<?php
$inputs = array(
array(
"in" => 0,
"out" => 249.6,
),
array(
"in" => 0,
"out" => 396,
),
array(
"in" => 554.4,
"out" => 0
),
array(
"in" => 145.07,
"out" => 0
),
array(
"in" => 0,
"out" => 53.87,
),
);
$fTotal = 0;
echo "Start at 0: ";
foreach($inputs as $key => $sRef) {
$itemValid = true;
$amountIn = $sRef['in'];
$amountOut = $sRef['out'];
if ($itemValid) {
echo $fTotal;
$is_in = 0;
if ($amountIn > 0.0) {
$is_in = 1;
echo "+";
$amount = floatval($amountIn);
$fTotal = floatval($fTotal) + floatval($amountIn);
} else {
$is_in = 0;
echo "-";
$amount = floatval($amountOut);
$fTotal = floatval($fTotal) - floatval($amountIn);
}
echo $amount;
echo ' = ' . $fTotal . " | ";
}
}
The bcsub() function in PHP is an inbuilt function and is used to subtract one arbitrary precision number from another.
Syntax:
string bcsub ( $num_str1, $num_str2, $scaleVal)
Example :
<?php $num_str1 = "8"; $num_str2 = "3"; $res = bcsub($num_str1, $num_str2) echo $res; ?>
Result:
5
Related
I am trying to create a mathematical function (in PHP) that will take a given number of Kg, and convert them to a readable form or better yet, return the unit best suited for that amount. The input will always be kg. Preferably log.
For example:
5 kg = (5) kg
0.5 kg = (500) gm
1000 kg = (1) tonne
0.001 kg = (1) gm
0.0001 kg = (100) mg
I know there is a way to do it using log or log10 functions but I cannot figure it out.
How could it be done?
I think something like this should work, but I'm sure there are people who can make a much better solution.
function outputWeight($kg)
{
$power = floor(log($kg, 10));
switch($power) {
case 5 :
case 4 :
case 3 : $unit = 'ton';
$power = 3;
break;
case 2 :
case 1 :
case 0 : $unit = 'kilogram';
$power = 0;
break;
case -1 :
case -2 :
case -3 : $unit = 'gram';
$power = -3;
break;
case -4 :
case -5 :
case -6 : $unit = 'milligram';
$power = -6;
break;
default : return 'out of range';
}
return ($kg / pow(10, $power)) . ' ' . $unit;
}
echo outputWeight(0.015) . '<br>';
echo outputWeight(0.15) . '<br>';
echo outputWeight(1.5) . '<br>';
echo outputWeight(15) . '<br>';
echo outputWeight(150) . '<br>';
The idea is that you can easily extend the range. This will output
15 gram
150 gram
1.5 kilogram
15 kilogram
150 kilogram
I did not thoroughly test this code!
After playing with it for a while, here is what I came up with
function readableMetric($kg)
{
$amt = $kg * pow(1000, 3);
$s = array('mcg', 'mg', 'gm', 'kg','tonne');
$e = floor(log10($amt)/log10(1000));
return [
"amount" => $amt/pow(1000, $e),
"unit" => $s[$e]
];
}
The following function internally uses an array with the assignments unit => conversion-factor. This array can easily be expanded or modified to meet your own requirements. A fixed limit of 1000 is used in the function. This means that the output value is always less than 1000, with the exception of tons. With an additional argument which is preset to 2, the number of maximum decimal places can be changed.
function scaleKg(float $kg, int $decimalPlaces = 2) : string {
$scale = [
'micrograms' => 1.E9,
'milligram' => 1.E6,
'gram' => 1.E3,
'kilogram' => 1,
'ton' => 1.E-3,
];
foreach($scale as $unit => $factor){
$mass = $kg * $factor;
if($mass < 1000) {
return round($mass,$decimalPlaces).' '.$unit;
}
}
return round($mass,$decimalPlaces).' '.$unit;
}
Some examples:
$kg = 1.212345;
echo scaleKg($kg); //1.21 kilogram
$kg = 455;
echo scaleKg($kg); //455 kilogram
$kg = 0.0456;
echo scaleKg($kg); //45.6 gram
$kg = 23456;
echo scaleKg($kg); //23.46 ton
$kg = 23489000;
echo scaleKg($kg); //23489 ton
$kg = 167E-6;
echo scaleKg($kg); //167 milligram
I find myself agreeing with Kiko that the shortest code isn't always the best or most readable.
Bearing that in mind bringing log into it seems like unnecessary complication. So I suggest a condensed version of my original code:
function readableMetric($mass)
{
$units = [
-3 => "tonne",
0 => "kg",
3 => "g",
6 => "mg",
];
foreach ($units as $x => $unit) {
if ( ($newMass = $mass * 10 ** $x) >= 1 ) {
return "{$newMass} {$unit}";
}
}
}
This is more extensible (e.g. you could easily add centigram)
The math is intuitive to most (powers of 10)
If you really want to golf it you can shrink it down to a one liner:
function readableMetric($m)
{
foreach([-3=>"tonne",0=>"kg",3=>"g",6 =>"mg"] as $x=>$g)if(($n=$m*10**$x)>=1)return"$n $g";
}
Original Answer
You could just do it with a series of if / else statements?
$values = [
5, 0.5, 1000, 0.001, 0.0001
];
function convertmass($mass) : string
{
if ($mass >= 1000) {
return ($mass / 1000) . " ton";
}
elseif ($mass < 0.001) {
return ($mass * 1000000) . " mg";
}
elseif ($mass < 1) {
return ($mass * 1000 ) . " g";
}
else {
return $mass . " kg";
}
}
foreach ($values as $mass) {
echo convertmass($mass), PHP_EOL;
}
Output:
5 kg
500 g
1 ton
1 g
100 mg
If you want slightly easier/more readable/more user friendly updating to add new measurements in then you can do something like this instead:
$values = [
5, 0.5, 1000, 0.001, 0.0001
];
function convertmass($mass) : string
{
$massLookup = [
[ "unit" => "kg", "min" => 1, "max" => 1000, "multiple" => 1],
[ "unit" => "tonne", "min" => 1000, "max" => 1000000, "multiple" => 0.001],
[ "unit" => "g", "min" => 0.001, "max" => 1, "multiple" => 1000],
[ "unit" => "mg", "min" => 0.000001, "max" => 0.001, "multiple" => 1000000],
];
foreach ($massLookup as $unit) {
if ($mass >= $unit["min"] && $mass < $unit["max"]) {
return ($mass * $unit["multiple"]) . " {$unit["unit"]}";
}
}
return "Measurement {$mass} kg is out of range";
}
foreach ($values as $mass) {
echo convertmass($mass), PHP_EOL;
}
I did some research about division by zero error in PHP. I found a couple of most of them said to do an if statement like I did with my code below, but I am still getting an error, can someone tell me what's wrong with my code?
<?php
$result = array(
'price' => 0,
'special' => 80
);
$product = array(
'savings' => round((($result['price'] - $result['special'])/$result['price'])*100, 0)
);
if ($result['price'] == 0) {
echo $result['price'];
} else {
echo "You will save " . $product['savings'] ."%";
}
?>
I tried both
If == 0 and if != 0
as I am fairly new to PHP, won't my if statement mean if the price of product = 0 than echo out the price and if it's not than echo out the special price?
I'm not allowed to move the $product array.
This seems to make more sense, money-wise: check first if the price is higher than the special, and if so, perform the calculation.
$product = array(
'savings' => ($result['price'] > $result['special'] ? (round((($result['price'] - $result['special'])/$result['price'])*100, 0)) : 0)
);
You have to move the division inside your if/else statement.
$result = array(
'price' => 100,
'special' => 80
);
if ($result['price'] == 0) {
echo $result['price'];
} else {
$product = array('savings' => round((($result['price'] - result['special'])/$result['price'])*100, 0)
);
echo "You will save " . $product['savings'] ."%";
}
I have a script that generates sitemaps based on url index http://example.com/sitemap.index.xml where index is a number >0 that defines what results should be included in each chunk.
$chunk = 10000;
$counter = 0;
$scroll = $es->search(array(
"index" => "index",
"type" => "type",
"scroll" => "1m",
"search_type" => "scan",
"size" => 10,
"from" => $chunk * ($index - 1)
));
$sid = $scroll['_scroll_id'];
while($counter < $chunk){
$docs = $es->scroll(array(
"scroll_id" => $sid,
"scroll" => "1m"
));
$sid = $docs['_scroll_id'];
$counter += count($docs['hits']['hits']);
}
// ...
Now each time I access http://example.com/sitemap.1.xml or http://example.com/sitemap.2.xml the results returned from ES are exactly the same. It returns 50 results (10 per each shard) but does not seem to take count of from = 0, from = 10000.
I'm using elasticsearch-php as ES library.
Any ideas?
In Java, it can be done as follows
QueryBuilder query = QueryBuilders.matchAllQuery();
SearchResponse scrollResp = Constants.client.prepareSearch(index)
.setTypes(type).setSearchType(SearchType.SCAN)
.setScroll(new TimeValue(600000)).setQuery(query)
.setSize(500).execute().actionGet();
while (true) {
scrollResp = Constants.client
.prepareSearchScroll(scrollResp.getScrollId())
.setScroll(new TimeValue(600000)).execute().actionGet();
System.out.println("Record count :"
+ scrollResp.getHits().getHits().length);
total = total + scrollResp.getHits().getHits().length;
System.out.println("Total record count: " + total);
for (SearchHit hit : scrollResp.getHits()) {
//handle the hit
}
// Break condition: No hits are returned
if (scrollResp.getHits().getHits().length == 0) {
System.out.println("All records are fetched");
break;
}
}
Hope it helps.
I faced an interview question which i felt was very good. Couldn't achieve the complete answer, however, felt sharing and asking the right method to code it in PHP.
The question goes as :
Given the Japanese numeral reading system, write a program that converts an integer into the equivalent Japanese reading.
Basic numeral readings:
1: ichi
2: ni
3: san
4: yon
5: go
6: roku
7: nana
8: hachi
9: kyuu
10: juu
20: ni-juu
30: san-juu
100: hyaku
1000 : sen
10,000: man
100,000,000: oku
1,000,000,000,000: chou
10,000,000,000,000,000: kei
Exceptions due to voice rounding in Japanese reading:
300: sanbyaku
600: roppyaku
800: happyaku
3000: sanzen
8000: hassen
1,000,000,000,000: itchou
8,000,000,000,000: hatchou
10,000,000,000,000: jutchou (also applies to multiplies of 10,000,000,000,000)
10,000,000,000,000,000: ikkei
60,000,000,000,000,000: rokkei
80,000,000,000,000,000: hakkei
100,000,000,000,000,000: jukkei (also applies to multiplies of 10,000,000,000,000,000)
1,000,000,000,000,000,000: hyakkei (also applies to multiplies of 1,000,000,000,000,000,000)
Starting at 10,000, numbers begin with ichi if no digit would otherwise precede, e.g. 1,000 is sen but 10,000 is ichi-man.
Examples:
11: juu ichi
17: juu nana
151: hyaku go-juu ichi
302: san-byaku ni
469: yon-hyaku roku-juu kyuu
2025 : ni-sen ni-juu go
10,403: ichi-man yon-byaku san
41,892: yon-juu ichi-man happyaku kyuu-juu ni
80,000,000,000,000: hachi-jutchou
The code that i have tried is :
$inputNumber = 2025;
$inputString = (String)$inputNumber;
$numeralReadings = array(
1 => 'ichi',
2 => 'ni',
3 => 'san',
4 => 'yon',
5 => 'go',
6 => 'roku',
7 => 'nana',
8 => 'hachi',
9 => 'kyuu',
10 => 'juu',
20 => 'ni-juu',
30 => 'san-juu',
100 => 'hyaku',
1000 => 'sen',
10000 => 'man',
100000000 => 'oku',
1000000000000 => 'chou',
10000000000000000 => 'kei'
);
$numeralExceptions = array(
300 => 'sanbyaku',
600 => 'roppyaku',
800 => 'happyaku',
3000 => 'sanzen',
8000 => 'hassen',
1000000000000 => 'itchou',
8000000000000 => 'hatchou',
10000000000000 => 'jutchou',
10000000000000000 => 'ikkei',
60000000000000000 => 'rokkei',
80000000000000000 => 'hakkei',
100000000000000000 => 'jukkei',
1000000000000000000 => 'hyakkei'
);
if ($inputString > 10000) {
$inp1 = floor($inputString / 1000);
$inp = $inputString - ($inp1 * 1000);
if($inp !== 0) {
read($inp1, $numeralReadings, $numeralExceptions, false);
read($inp, $numeralReadings, $numeralExceptions);
} else {
read($inputString, $numeralReadings, $numeralExceptions);
}
} else {
read($inputString, $numeralReadings, $numeralExceptions);
}
function read($inputStr, $numeralReadings, $numeralExceptions, $parse1 = true)
{
$splitString = str_split($inputStr);
$returnString = '';
$appendIchi = false;
$firstNumber = null;
foreach ($splitString as $key => $number) {
if ($firstNumber == null) {
$firstNumber = $number;
}
if ($number !== 0) {
$int = 1;
$a = count($splitString) - 1 - $key;
for ($i = 0; $i < $a; $i++) {
$int = $int * 10;
}
$tempNumber = (int)$number * $int;
if (isset($numeralExceptions[$tempNumber])) {
$returnString .= $numeralExceptions[$tempNumber] . ' ';
continue;
}
if (isset($numeralReadings[$tempNumber])) {
if ($parse1 == false && $tempNumber == 1) {
continue;
}
$returnString .= $numeralReadings[$tempNumber] . ' ';
continue;
}
if (isset($numeralReadings[(int)$number])) {
if ($parse1 == false && $tempNumber == 1) {
continue;
}
$returnString .= $numeralReadings[(int)$number];
if ($int !== 1) {
$returnString .= '-' . $numeralReadings[$int];
}
$returnString .= ' ';
}
}
}
echo $returnString;
}
here is a fiddle that shows the code in running. You might want to try it online. Link
With the code above, i was able to achieve all the examples stated above other than the last 2.
Anyone who can solve this in a better way?
I guess you can simply use array_key_exists() here
function read($inputStr, $numeralReadings, $numeralExceptions)
{
if(array_key_exists($inputStr, $numeralReadings))
{
return $numeralReadings[$inputStr];
}
else if(array_key_exists($inputStr, $numeralExceptions))
{
return $numeralExceptions[$inputStr];
}
else
{
return "not found";
}
}
I've been able to use the store function of this caching class but I am unable to retrieve a certain item out of the stored array.
$c->setCache("$appid")
->store("$appid", array(
'name' => "$gname",
'price' => "$price",
'logo' => "$glogo",
'link' => "$glink"
)
);
$gcache = $c->retrieve("$appid");
$cprice = $gcache->price; //not sure how to retrieve the price value only
https://github.com/cosenary/Simple-PHP-Cache
Here is the full code where I cannot seem to get the price from the stored array:
<?php
require_once 'cache.class.php';
// Setup 'default' Cache
$c = new Cache();
$glist = "http://steamcommunity.com/id/aksn1p3r/games?tab=all&xml=1";
$gxml = simplexml_load_file($glist);
$tprice = 0;
$i = 0; //just used to exit loop after 2 executions
foreach($gxml->games->game as $game) {
if($i >= 2) {
break;
}
$appid = $game->appID;
$glink = $game->storeLink;
$gname = $game->name;
$glogo = $game->logo;
$gjson = 'http://store.steampowered.com/api/appdetails/?appids=' . $appid;
$fgc = file_get_contents($gjson);
$jd = json_decode($fgc, true);
$gdata = $jd[intval($appid)]['data']; //$appid needs to be integer here, not string
$gdesc = $gdata['about_the_game'];
$gprice = $gdata['price_overview']['final'];
$price = number_format($gprice / 100, 2);
$c->setCache("$appid")
->store("$appid", array(
'name' => "$gname",
'price' => "$price",
'logo' => "$glogo",
'link' => "$glink"
)
);
// this part is where I dont understand how to get the price I stored.
$cprice = $c->retrieve["$appid"]['price'];
$tprice += $cprice;
$i++;
}
echo 'Total games: ' .$i. ' <br>';
echo 'Total Price: ' .$tprice;
?>
You are storing array expecting to get object
Replace
$gcache->price;
with
$gcache['price'];