How to convert imperial units of length into metric? - php

Here I am faced with an issue that I believe(or at least hope) was solved 1 million times already.
What I got as the input is a string that represents a length of an object in imperial units. It can go like this:
$length = "3' 2 1/2\"";
or like this:
$length = "1/2\"";
or in fact in any other way we normally would write it.
In effort to reduce global wheel invention, I wonder if there is some function, class, or regexp-ish thing that will allow me to convert Imperial length into Metric length?

The Zend Framework has a measurement component for just that purpose. I suggest you check it out - here.
$unit = new Zend_Measure_Length($length,Zend_Measure_Length::YARD);
$unit -> convertTo(Zend_Measure_Length::METER);

Here is my solution. It uses eval() to evaluate the expression, but don't worry, the regex check at the end makes it completely safe.
function imperial2metric($number) {
// Get rid of whitespace on both ends of the string.
$number = trim($number);
// This results in the number of feet getting multiplied by 12 when eval'd
// which converts them to inches.
$number = str_replace("'", '*12', $number);
// We don't need the double quote.
$number = str_replace('"', '', $number);
// Convert other whitespace into a plus sign.
$number = preg_replace('/\s+/', '+', $number);
// Make sure they aren't making us eval() evil PHP code.
if (preg_match('/[^0-9\/\.\+\*\-]/', $number)) {
return false;
} else {
// Evaluate the expression we've built to get the number of inches.
$inches = eval("return ($number);");
// This is how you convert inches to meters according to Google calculator.
$meters = $inches * 0.0254;
// Returns it in meters. You may then convert to centimeters by
// multiplying by 100, kilometers by dividing by 1000, etc.
return $meters;
}
}
So for example, the string
3' 2 1/2"
gets converted to the expression
3*12+2+1/2
which gets evaluated to
38.5
which finally gets converted to 0.9779 meters.

The imperial string values are a little bit more complicated, so I used following expression:
string pattern = "(([0-9]+)')*\\s*-*\\s*(([0-9])*\\s*([0-9]/[0-9])*\")*";
Regex regex = new Regex( pattern );
Match match = regex.Match(sourceValue);
if( match.Success )
{
int feet = 0;
int.TryParse(match.Groups[2].Value, out feet);
int inch = 0;
int.TryParse(match.Groups[4].Value, out inch);
double fracturalInch = 0.0;
if (match.Groups[5].Value.Length == 3)
fracturalInch = (double)(match.Groups[5].Value[0] - '0') / (double)(match.Groups[5].Value[2] - '0');
resultValue = (feet * 12) + inch + fracturalInch;

The regexp would look something like this:
"([0-9]+)'\s*([0-9]+)\""
(where \s represents whitespace - I'm not sure how it works in php). Then you extract the first + second group and do
(int(grp1)*12+int(grp2))*2.54
to convert to centimeters.

Related

How to get number of digits in both right, left sides of a decimal number

I wonder if is there a good way to get the number of digits in right/left side of a decimal number PHP. For example:
12345.789 -> RIGHT SIDE LENGTH IS 3 / LEFT SIDE LENGTH IS 5
I know it is readily attainable by helping string functions and exploding the number. I mean is there a mathematically or programmatically way to perform it better than string manipulations.
Your answers would be greatly appreciated.
Update
The best solution for left side till now was:
$left = floor(log10($x))+1;
but still no sufficient for right side.
Still waiting ...
To get the digits on the left side you can do this:
$left = floor(log10($x))+1;
This uses the base 10 logarithm to get the number of digits.
The right side is harder. A simple approach would look like this, but due to floating point numbers, it would often fail:
$decimal = $x - floor($x);
$right = 0;
while (floor($decimal) != $decimal) {
$right++;
$decimal *= 10; //will bring in floating point 'noise' over time
}
This will loop through multiplying by 10 until there are no digits past the decimal. That is tested with floor($decimal) != $decimal.
However, as Ali points out, giving it the number 155.11 (a hard to represent digit in binary) results in a answer of 14. This is because as the number is stored as something like 155.11000000000001 with the 32 bits of floating precision we have.
So instead, a more robust solution is needed. (PoPoFibo's solutions above is particularly elegant, and uses PHPs inherit float comparison functions well).
The fact is, we can never distinguish between input of 155.11 and 155.11000000000001. We will never know which number was originally given. They will both be represented the same. However, if we define the number of zeroes that we can see in a row before we just decide the decimal is 'done' than we can come up with a solution:
$x = 155.11; //the number we are testing
$LIMIT = 10; //number of zeroes in a row until we say 'enough'
$right = 0; //number of digits we've checked
$empty = 0; //number of zeroes we've seen in a row
while (floor($x) != $x) {
$right++;
$base = floor($x); //so we can see what the next digit is;
$x *= 10;
$base *= 10;
$digit = floor($x) - $base; //the digit we are dealing with
if ($digit == 0) {
$empty += 1;
if ($empty == $LIMIT) {
$right -= $empty; //don't count all those zeroes
break; // exit the loop, we're done
}
} else {
$zeros = 0;
}
}
This should find the solution given the reasonable assumption that 10 zeroes in a row means any other digits just don't matter.
However, I still like PopoFibo's solution better, as without any multiplication, PHPs default comparison functions effectively do the same thing, without the messiness.
I am lost on PHP semantics big time but I guess the following would serve your purpose without the String usage (that is at least how I would do in Java but hopefully cleaner):
Working code here: http://ideone.com/7BnsR3
Non-string solution (only Math)
Left side is resolved hence taking the cue from your question update:
$value = 12343525.34541;
$left = floor(log10($value))+1;
echo($left);
$num = floatval($value);
$right = 0;
while($num != round($num, $right)) {
$right++;
}
echo($right);
Prints
85
8 for the LHS and 5 for the RHS.
Since I'm taking a floatval that would make 155.0 as 0 RHS which I think is valid and can be resolved by String functions.
php > $num = 12345.789;
php > $left = strlen(floor($num));
php > $right = strlen($num - floor($num));
php > echo "$left / $right\n";
5 / 16 <--- 16 digits, huh?
php > $parts = explode('.', $num);
php > var_dump($parts);
array(2) {
[0]=>
string(5) "12345"
[1]=>
string(3) "789"
As you can see, floats aren't the easiest to deal with... Doing it "mathematically" leads to bad results. Doing it by strings works, but makes you feel dirty.
$number = 12345.789;
list($whole, $fraction) = sscanf($number, "%d.%d");
This will always work, even if $number is an integer and you’ll get two real integers returned. Length is best done with strlen() even for integer values. The proposed log10() approach won't work for 10, 100, 1000, … as you might expect.
// 5 - 3
echo strlen($whole) , " - " , strlen($fraction);
If you really, really want to get the length without calling any string function here you go. But it's totally not efficient at all compared to strlen().
/**
* Get integer length.
*
* #param integer $integer
* The integer to count.
* #param boolean $count_zero [optional]
* Whether 0 is to be counted or not, defaults to FALSE.
* #return integer
* The integer's length.
*/
function get_int_length($integer, $count_zero = false) {
// 0 would be 1 in string mode! Highly depends on use case.
if ($count_zero === false && $integer === 0) {
return 0;
}
return floor(log10(abs($integer))) + 1;
}
// 5 - 3
echo get_int_length($whole) , " - " , get_int_length($fraction);
The above will correctly count the result of 1 / 3, but be aware that the precision is important.
$number = 1 / 3;
// Above code outputs
// string : 1 - 10
// math : 0 - 10
$number = bcdiv(1, 3);
// Above code outputs
// string : 1 - 0 <-- oops
// math : 0 - INF <-- 8-)
No problem there.
I would like to apply a simple logic.
<?php
$num=12345.789;
$num_str="".$num; // Converting number to string
$array=explode('.',$num_str); //Explode number (String) with .
echo "Left side length : ".intval(strlen($array[0])); // $array[0] contains left hand side then check the string length
echo "<br>";
if(sizeof($array)>1)
{
echo "Left side length : ".intval(strlen($array[1]));// $array[1] contains left hand check the string length side
}
?>

Converting large numbers into letters (and back again)

Is there a term for the idea of storing large numbers as letters? For example let's say I have the (relatively small) number 138201162401719 and I want to shrink the number of characters (I know this does not help with saving disk space) to the fewest possible number of characters. There are 26 letters in the English alphabet (but i count them as 25 since we need a zero letter). If I start splitting up my large number into pieces that are each 25 or less I get:
13, 8, 20, 11, 6, 24, 0, 17, 19
If I then count the numbers of the alphabet a=0, b=1, c=2, d=3... I can convert this to:
NIULGYART
So I went from 15 digits long (138201162401719) to 9 characters long (NIULGYART). This could of course be easily converted back to the original number as well.
So...my first question is "Does this have a name" and my second "Does anyone have PHP code that will do the conversion (in both directions)?"
I am looking for proper terminology so that I can do my own research in Google...though working code examples are cool too.
This only possible if you're considering to store your number before processing as a string. Because you can't store huge number as integers. You will lost the precision (13820116240171986468445 will be stored as 1.3820116240172E+22) so the alot of digits are lost.
If you're considering storing the number as a string this will be your answer:
Functions used: intval, chr and preg_match_all.
<?php
$regex = '/(2[0-5])|(1[0-9])|([0-9])/';
$numberString = '138201162401719';
preg_match_all($regex, $numberString, $numberArray, PREG_SET_ORDER);
echo($numberString . " -> ");
foreach($numberArray as $value){
$character = chr (intval($value[0]) + 65);
echo($character);
}
?>
Demo
This is the result:
138201162401719 -> NIULGYART
Here's how I would do it:
Store the big number as a string and split it into an array of numbers containing one digit each
Loop through the array extract 2-digit chunks using substr()
Check if the number is less than 26 (in which case, it is an alphabet) and add them to an array
Use array_map() with chr() to create a new array of characters from the above array
Implode the resulting array to get the cipher
In code:
$str = '138201162401719';
$arr = str_split($str);
$i = 0; // starting from the left
while ($i < count($arr)) {
$n = substr($str, $i, 2);
$firstchar = substr($n, 0, 1);
if ($n < 26 && $firstchar != 0) {
$result[] = substr($str, $i, 2);
$i += 2; // advance two characters
} else {
$result[] = substr($str, $i, 1);
$i++; // advance one character
}
}
$output = array_map(function($n) {
return chr($n+65);
}, $result);
echo implode($output); // => NIULGYART
Demo.
As an alternative, you could convert the input integer to express it in base 26, instead of base 10. Something like (pseudocode):
func convertBase26(num)
if (num < 0)
return "-" & convertBase26(-num) // '&' is concatenate.
else if (num = 0)
return "A"
endif
output = "";
while (num > 0)
output <- ('A' + num MOD 26) & output // Modulus operator.
num <- num DIV 26 // Integer division.
endwhile
return output
endfunc
This uses A = 0, B = 1, up to Z = 25 and standard place notation: 26 = BA. Obviously a base conversion is easily reversible.
strtr() is a magnificent tool for this task! It replaces the longest match as is traverses the string.
Code: (Demo)
function toAlpha ($num) {
return strtr($num, range("A", "Z"));
}
$string = toAlpha("138201162401719");
echo "$string\n";
$string = toAlpha("123456789012345");
echo "$string\n";
$string = toAlpha("101112131415161");
echo "$string\n";
$string = toAlpha("2625242322212019");
echo "$string";
Output:
NIULGYART
MDEFGHIJAMDEF
KLMNOPQB
CGZYXWVUT
Just flip the lookup array to reverse the conversion: https://3v4l.org/YsFZu
Merged: https://3v4l.org/u3NQ5
Of course, I must mention that there is a vulnerability with converting a sequence of letters to numbers and back to letters. Consider BB becomes 11 then is mistaken for eleven which would traslate to L when converted again.
There are ways to mitigate this by adjusting the lookup array, but that may not be necessary/favorable depending on program requirements.
And here is another consideration from CodeReview.
I have been trying to do the same thing in PHP without success.
Assuming I'm using the 26 letters of the English alphabet, starting with A = 0 down to Z as 25:
I find the highest power of 26 lower than the number I am encoding. I divide it by the best power of 26 I found. Of the result I take away the integer, convert it to a letter and multiply the decimals by 26. I keep doing that until I get a whole number. It's ok to get a zero as it's an A, but if it has decimals it must be multiplied.
For 1 billion which is DGEHTYM and it's done in 6 loops obviously. Although my answer demonstrates how to encode, I'm afraid it does not help doing so on PHP which is what I'm trying to do myself. I hope the algorithm helps people out there though.

PHP: Max and Min returning incorrect results

I am sure this is because of the "g" on the end but this is the scenario and results when I try and work out a ratio percent. I always want to divide the highest of 2 numbers by the lowest.
$item1 = "200.00g";
$item2 = "50.00g";
$calc = round((max($item1,$item2) / min($item1,$item2))*100) . "%";
// result: $calc = "400%"
$item1 = "100.00g";
$item2 = "5.00g";
$calc = round((max($item1,$item2) / min($item1,$item2))*100) . "%";
// result: $calc = "2000%"
PROBLEM RESULT:
$item1 = "8.00g";
$item2 = "14.00g";
$calc = round((max($item1,$item2) / min($item1,$item2))*100) . "%";
// result: $calc = "57%"
// I am expecting (14.00g / 8.00g)*100 = "175%"
It's type casting;
$item1 = "8.00";
$item2 = "14.00";
$calc = round((max($item1,$item2) / min($item1,$item2))*100) . "%";
result will be 175%
When you want to use your strings in mathematical operations, and you know that the unit is placed at the end as it is in your example, you can cast your variables to floats:
$item1_numeric = (float) $item1;
But obviously it is better to have the values and the units separated in your variables / database.
Use: substr($item1, 0, -1) instade of $item1, substr($item2, 0, -1) instade of $item2 when you do round.
You can't compare 2 strings with round().
Edit : If $item1 = "200g", ma solution is ok, but if if $item1 = "200.00g" you need to remove "." before round() with for example pregreplace.
Oh, YAPHPB - and one of my favorite ones. Even though it's written in the Doc:
When [max()] given a string it will be cast as an integer when comparing.
... it's only a partial truth: if at least one of compared values is a number, or a numeric string.
Otherwise all the strings will be compared as strings: first {0} characters of each strings will be compared, then {1}, then {2}... etc.
So basically that's what happens here:
echo max("200.00g", "50.00g"); // 50.00g, as '5' > '2'
echo max("200.00g", 50); // "200.00g", as it gets converted to int (become 200)
And that's even more crazy:
echo max("200.00g", "1000.00"); // "200.00g", as '2' > '1'
echo max("200.00", "1000.00"); // "1000.00", as we tried to help you, no, really!
The latter result can actually be predicted by someone knowing of numeric concept: when both strings are pure numbers, they got converted to numbers when compared. Still, I found this behavior unreliable, to say the least.
The bottom line: if you need to compare numbers, compare numbers, period. Type conversion in PHP can get real messy - and bite you in the bottom real hard when you least expect it. )

PHP Leftmost digit

let's say I have a variable containing an integer or a float (since integers might overflow into a float in PHP).
I want to run some operation to get the leftmost digit and the rest of the remaining digits.
To explain better:
<?php
$x = NULL; //this will hold first digit
$num = 12345; //int
/// run operation
//outputs
//$x = 1;
//$num = 2345;
var_dump($x, $num);
?>
Now, I know there's multitudes of ways to do this if you represent the number as a string, but I'm trying to avoid type casting it into a string.
I'm probably looking for a solution which includes bitwise operations, but I'm pretty weak in that topic so I'm hoping someone who usually works low-level might be able to answer this!
Thanks a bunch.
I'm sure there is a way to do this without casting it to a string, but why? The string detour is so easy:
$x = (int)substr($num, 0, 1);
It'll give you a nice, proper integer.
Obviously, this does no extended checking for faulty input, and requires $num to be a valid number.
Avoids using any string manipulation, but no guarantees for float or even negative values
$x = NULL; //this will hold first digit
$num = 12345; //int
$m = 1;
while(true) {
$m *= 10;
if ($m > $num)
break;
}
$m /= 10;
$x = (int) floor($num / $m);
$num = $num % $m;
//outputs
//$x = 1;
//$num = 2345;
var_dump($x, $num);
Math-only method:
function leftMost($num) {
return floor($num/pow(10,(floor((log10($num))))));
}
explained I guess...
1+ log10 of num calculates the number of digits a number is, we floor it to remove any decimal values, put it as the exponent so for a 1 digit number we get 10^0=1, or a 8 digit number we get 10^8. We then are just divding 12345678/10000000 = 1.2345678, which gets floor'd and is just 1.
note: this works for numbers between zero and one also, where it will return the 2 in 0.02, and a string transform will fail.
If you want to work with negative numbers, make $num = abs($num) first.
To get the rest of the digits
$remainingnum = (int)substr((string)$num, 1, strlen($num));
If you typcast the value to a string you can use the array type selector.
For example:
$n = (string)12345676543.876543;
echo (int)$n[0];
#Mark Baker offered the best solution, though you should do abs(floor($num)) before applying the algorithm.
I know you stated you wanted to avoid casting to a string, but if you want to loop over the digits in PHP, this will be the fastest way:
$len = strlen($n);
for ($i = 0; $i < $len; ++$i)
$d = $n[$i];
In a quick-and-dirty benchmark, it was around 50% faster than the equivalent set of mathematical expressions, even when minimizing the calls to log and exp.

Whats the cleanest way to convert a 5-7 digit number into xxx/xxx/xxx format in php?

I have sets of 5, 6 and 7 digit numbers. I need them to be displayed in the 000/000/000 format. So for example:
12345 would be displayed as 000/012/345
and
9876543 would be displayed as 009/876/543
I know how to do this in a messy way, involving a series of if/else statements, and strlen functions, but there has to be a cleaner way involving regex that Im not seeing.
sprintf and modulo is one option
function formatMyNumber($num)
{
return sprintf('%03d/%03d/%03d',
$num / 1000000,
($num / 1000) % 1000,
$num % 1000);
}
$padded = str_pad($number, 9, '0', STR_PAD_LEFT);
$split = str_split($padded, 3);
$formatted = implode('/', $split);
You asked for a regex solution, and I love playing with them, so here is a regex solution!
I show it for educational (and fun) purpose only, just use Adam's solution, clean, readable and fast.
function FormatWithSlashes($number)
{
return substr(preg_replace('/(\d{3})?(\d{3})?(\d{3})$/', '$1/$2/$3',
'0000' . $number),
-11, 11);
}
$numbers = Array(12345, 345678, 9876543);
foreach ($numbers as $val)
{
$r = FormatWithSlashes($val);
echo "<p>$r</p>";
}
OK, people are throwing stuff out, so I will too!
number_format would be great, because it accepts a thousands separator, but it doesn't do padding zeroes like sprintf and the like. So here's what I came up with for a one-liner:
function fmt($x) {
return substr(number_format($x+1000000000, 0, ".", "/"), 2);
}
Minor improvement to PhiLho's suggestion:
You can avoid the substr by changing the regex to:
function FormatWithSlashes($number)
{
return preg_replace('/^0*(\d{3})(\d{3})(\d{3})$/', '$1/$2/$3',
'0000' . $number);
}
I also removed the ? after each of the first two capture groups because, when given a 5, 6, or 7 digit number (as specified in the question), this will always have at least 9 digits to work with. If you want to guard against the possibility of receiving a smaller input number, run the regex against '000000000' . $number instead.
Alternately, you could use
substr('0000' . $number, -9, 9);
and then splice the slashes in at the appropriate places with substr_replace, which I suspect may be the fastest way to do this (no need to run regexes or do division), but that's really just getting into pointless optimization, as any of the solutions presented will still be much faster than establishing a network connection to the server.
This would be how I would write it if using Perl 5.10 .
use 5.010;
sub myformat(_;$){
# prepend with zeros
my $_ = 0 x ( 9-length($_[0]) ) . $_[0];
my $join = $_[1] // '/'; # using the 'defined or' operator `//`
# m// in a list context returns ($1,$2,$3,...)
join $join, m/ ^ (\d{3}) (\d{3}) (\d{3}) $ /x;
}
Tested with:
$_ = 11111;
say myformat;
say myformat(2222);
say myformat(33333,';');
say $_;
returns:
000/011/111
000/002/222
000;033;333
11111
Back-ported to Perl 5.8 :
sub myformat(;$$){
local $_ = #_ ? $_[0] : $_
# prepend with zeros
$_ = 0 x ( 9-length($_) ) . $_;
my $join = defined($_[1]) ? $_[1] :'/';
# m// in a list context returns ($1,$2,$3,...)
join $join, m/ ^ (\d{3}) (\d{3}) (\d{3}) $ /x;
}
Here's how I'd do it in python (sorry I don't know PHP as well). I'm sure you can convert it.
def convert(num): #num is an integer
a = str(num)
s = "0"*(9-len(a)) + a
return "%s/%s/%s" % (s[:3], s[3:6], s[6:9])
This just pads the number to have length 9, then splits the substrings.
That being said, it seems the modulo answer is a bit better.

Categories