Преобразование float в представление простой строки
SO,
Проблема
Мой вопрос о тривиальной вещи: как преобразовать числовую строку в это простое ( "родное" ) представление. Это означает: если числовая строка уже находится в открытом виде, оставьте ее как есть, но если она находится в научной нотации, преобразуйте ее. Пример:
"3" --> "3"
"1.5" --> "1.5"
"-15.482E-2" --> "-0.15482"
Числовая строка должна быть действительной, а если нет - тогда это не случай преобразования (мы можем, например, возвращать пустую или пустую строку).
Использование случая
Это необходимо для bcmath
, потому что он не может работать с научными поплавками. Таким образом, они должны быть преобразованы в простые строки (задано здесь). Поэтому важным следствием этого варианта использования является то, что числовая строка может быть чем-то вроде 1E-300
или 1E+500
. Поскольку мы работаем с bcmath
, он намерен обрабатывать такие вещи.
Мой подход
В настоящее время я реализовал это с помощью regex-almighty, например:
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;
}
}
Вопрос
Я чувствую, что должен быть более простой и мудрый способ сделать это. Как добиться этого более простым способом в PHP? Может быть какой-то сложный sprintf()
формат?
Важное замечание: я не хочу иметь дело с точностью. Я хочу черный ящик. Передайте что-то числовое - получите строку в качестве вывода. Все это. Не хочу иметь дело ни с чем другим. На самом деле, все мое регулярное выражение - это вычисление длины и точности - так что, конечно, если передать их явно (как параметры, например), мы избавимся от регулярного выражения. Но - нет, это не то, что я хочу.
Ответы
Ответ 1
Поскольку sprintf()
с "%.f"
имеет проблемы с выражениями, такими как "1e-8"
, может потребоваться некоторая обработка текста:
function convertFloat($floatAsString)
{
$norm = strval(floatval($floatAsString));
if (($e = strrchr($norm, 'E')) === false) {
return $norm;
}
return number_format($norm, -intval(substr($e, 1)));
}
Протестировано с помощью
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
Ответ 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);
}
Где $precision - желаемое количество дробных цифр.
Ответ 3
Для тех, кто просто хочет преобразовать float в строку.
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;
Ответ 4
(Обновлено для использования неисчерпаемых функций, предложенных andufo, я выбрал explode
, но вы могли бы использовать preg_split
, если хотите. В качестве побочного примечания, если кто-то все еще читает это до конца, принятый ответ терпит неудачу - попробуйте его с моим первым и последним тестовым случаем.)
Я выкопал немного драгоценного камня из досок PHP, опубликованных benjcarson в 2002 году, который отметил вашу точную проблему с bcmath и научной нотацией
Он нуждался в некоторой корректировке (его функция не установила правильную шкалу и не сработала с правильными десятичными знаками, и, как указано, она не учитывала длину десятичных знаков в шкале)
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));
}
В качестве побочного примечания ваш исходный код выходит из строя на любых номерах меньше 1E-40
(Как и все текущие ответы, используя sprintf)
Было бы легче отладить, если бы вы разместили больше своих тестовых примеров, но это работает для всего, что вы уже опубликовали
Тестовые случаи:
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 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