PHP Convert any kg amount to a readable metric unit

Viewed 2262

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?

4 Answers

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;
}
Related