Convert float to plain string representation
Asked Answered
S

4

17

SO,

The problem

My question is about trivial thing: how to convert numeric string to it's plain ("native") representation. That means: if numeric string is already in plain view, leave it as it is, but if it is in scientific notation, convert it. Sample:

"3"          -->  "3"
"1.5"        -->  "1.5"
"-15.482E-2" -->  "-0.15482"

Numeric string supposed to be valid, and if it's not - then it's not a case for conversion (we're free to return null or empty string, for example).

Use-case

That is needed for bcmath because it can't work with scientific floats. Thus, they need to be converted to plain strings (asked here). So important consequence from this use-case is that numeric string can be something like 1E-300 or 1E+500. Since we're working with bcmath - it's intention is to handle such things.

My approach

For now, I've implemented that with , like:

function parseFloat($string)
{
   $string = (string)$string;
   if(preg_match('/^[+-]?(\d+|\d+\.\d*)[Ee]([+-]?)(\d+)$/', $string, $matches))
   {
      $precision = false!==($dot=strpos($matches[1], '.'))
                   ?strlen($matches[1])-$dot-1
                   :0;
      $precision = $matches[2]=='-'
                   ?$precision + (int)$matches[3]
                   :$precision - (int)$matches[3];
      return number_format($string, $precision<0?0:$precision, '', '');
   }
   if(preg_match('/^[+-]?(\d+|\d+\.\d+)$/', $string))
   {
      return $string;
   }
}

The question

I feel that there should be more simple and wise way to do that. How to achieve that in more simple way in PHP? May be some tricky sprintf() format?

Important note: I don't want to deal with precision. I want black box. Pass something numeric there - got string as output. That's all. Don't want to deal with anything else. In fact, all my regex are about calculating length & precision - so, sure, if pass them explicitly (as parameters, for example) - we cat get rid of regex. But - no, that's not what I want.

Silurian answered 20/2, 2014 at 9:0 Comment(9)
this ?Currant
@vlzvl solution there needs to define precision, while mine will evaluate it dynamically. If not this - can't see any differenceSilurian
My answer should sort youPyrogallol
echo sprintf("%s", (float)"-15.482E-5"); ?? just for funCurrant
@vlzvl fail as anything else on "1E-8"Silurian
(im working on it, find it quite a challenge)Currant
The thing is - I already have working solution (more or less, didn't tested properly) and I want simpler one. But if that will be hard or complicated code I'd prefer what I have nowSilurian
My answer works with all the test cases you've provided, including 1E-300 and 1E+500, I'm not sure what more you want from it. Your current regex, although fixed from the E-40 limitation, still is incorrect for E-300Snubnosed
Well, that wasn't my dv for your answer. I've noticed that your current code uses similar idea (i.e. with bc) to my non-regex solution - so it definitely is good. I'll wait for other opinions, howeverSilurian
L
13

Because sprintf() with "%.f" has trouble with expressions such as "1e-8", some text processing may be required:

function convertFloat($floatAsString)
{
    $norm = strval(floatval($floatAsString));

    if (($e = strrchr($norm, 'E')) === false) {
        return $norm;
    }

    return number_format($norm, -intval(substr($e, 1)));
}

Tested with:

3          3
1.5        1.5
-15.482e-2 -0.15482
1e-8       0.00000001
1e+3       1000
-4.66E-2   -0.0466
3e-3       0.003
Lining answered 20/2, 2014 at 9:4 Comment(2)
Does not work for strings representing numbers not representable with PHP's floats. Examples: "1E-500", "0.100000000000009".Beachcomber
@Ali I think all the sprintf answers have that issue, see my answer at the bottom, although I didn't test beyond 1E-300 in theory -500 should workSnubnosed
S
4

(Updated to use non-depreciated functions as suggested by andufo; I chose explode, but you could use preg_split if you wanted. As a side note if anyone still reads down this far, the accepted answer fails - try it with my first and last test case.)

I dug up a little gem from the PHP boards posted by benjcarson in 2002 who noted your exact problem with bcmath and scientific notation

It needed some adjustment (his function didn't set the right scale, and failed on regular decimals, and as pointed out it did not account for the length of decimal places in the scale)

function exp2int($exp) {
  list($mantissa, $exponent) = explode("e", strtolower($exp));
  if($exponent=='') return $exp;
  list($int, $dec) = explode(".", $mantissa);
  bcscale (abs($exponent-strlen($dec)));
  return bcmul($mantissa, bcpow("10", $exponent));
}

As a side note, your original code fails on any numbers smaller than 1E-40

(As do all the current answers using sprintf)

It would have been easier to debug if you posted more of your test cases, but this works for everything you've posted so far

Test cases:

echo exp2int("-1.82235135978667123456789E5"); \\-182235.135978667123456789
echo exp2int("1.1350865232E-60"); \\0.0000000000000000000000000000000000000000000000000000000000011350865232
echo exp2int("-15.482E-2"); \\-0.15482
echo exp2int("1.5"); \\1.5
echo exp2int("3"); \\3
echo exp2int("123.123e10"); \\1231230000000.000 - you mentioned trailing 0's aren't a problem
echo exp2int("123.123e-10"); \\0.0000000123123
echo exp2int("123456789E-9"); \\0.123456789
echo exp2int("12345.6789E-5"); \\0.123456789
echo exp2int("1E-300"); \\0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
Snubnosed answered 20/2, 2014 at 9:14 Comment(6)
Will fail: echo (float)"1E-8";//1.0E-8 Silurian
Fail on -4.66E-2 for example (your result will be -0.04)Silurian
Just needed an adjustment for the length of the decimal placeSnubnosed
split and spliti are deprecated.Gondi
@andufo thanks, updated answer. I'll never know why this wasn't the accepted answer though - it's the only one that fully works (including against the OP's own solution)Snubnosed
@Snubnosed agreed, OP should receive an update of your answer. Thanks for your answer!Gondi
U
3

For those who just wants to convert float to string.

function float_to_string($float)
{
    $string = (string)$float;

    if (preg_match('~\.(\d+)E([+-])?(\d+)~', $string, $matches)) {
        $decimals = $matches[2] === '-' ? strlen($matches[1]) + $matches[3] : 0;
        $string = number_format($float, $decimals,'.','');
    }

    return $string;
}

$float = 0.00000000020001;

echo $float; // 2.0001E-10
echo PHP_EOL;
echo float_to_string($float); // 0.00000000020001
echo PHP_EOL;

$float = 10000000000000000000000;

echo $float; // 1.0E+22
echo PHP_EOL;
echo float_to_string($float); // 10000000000000000000000
echo PHP_EOL;
Unthinking answered 20/10, 2017 at 6:45 Comment(0)
P
2
# convert output of used php-math functions like sin in scientific notation to decimal notation
function xpnd($scientific, $precision){ # expand from scientific notation
  if(is_int($scientific)){ #don't convert integers
    return $scientific; 
  }
  return sprintf("%.".$precision."F", $scientific);
}

Where $precision is the desired number of fractional digits.

Pyrogallol answered 20/2, 2014 at 9:6 Comment(2)
No, I don't want to deal with precision. I want black box. Pass something numeric there - got string as output. That's all. Don't want to deal with anything elseSilurian
Function has problems, cuts floats like 0.0000123456Paredes

© 2022 - 2024 — McMap. All rights reserved.