格式化具有最少所需小数位数的浮点数
Format a float with minimum required number of decimal places
我想用重现它所需的最少小数位数格式化一个浮点数。
PHP 有一个 number_format()
函数,用于呈现具有指定小数位数的数字。但是,如果我用它来格式化小数位数非常多的 0.1,我会得到:
print rtrim(number_format(0.1, 1000, '.', ''), '0');
// 0.1000000000000000055511151231257827021181583404541015625
从(float)"0.1" === 0.1
开始,第16位后的那些额外的55511151...
位小数就没用了。
我可以像这样使用循环:
function format_float($float) {
$decimals = 1;
do {
$result = number_format($float, $decimals, '.', '');
$decimals++;
} while ((float)$result !== $float);
return $result;
}
print format_float(0.1) . "\n"; // 0.1
print format_float(1/3) . "\n"; // 0.3333333333333333
print format_float(1E-50) . "\n"; // 0.00000000000000000000000000000000000000000000000001
但肯定有更简单、更有效的方法吗?
正确打印二进制浮点数的最小小数位数是一项非常复杂的工作。当前最先进的是 grisu family of algorithms. For a good explanation of the problems involved, see the classic paper by Steele and White.
这是我想出的:
function format_float($num) {
$dec = $num == 0 ? 0 : ceil(-log10(abs($num)));
$dec = max(1, $dec + 15 /* magic number */);
$res = number_format($num, $dec, '.', '');
// sometimes we need one more decimal
if ((float)$res !== $num) {
$res = number_format($num, $dec + 1, '.', '');
}
list($l, $r) = explode('.', $res, 2);
return "$l." . (rtrim($r) ?: '0');
}
它假定所需的小数位数为 15 - log10($num)
或 16 - log10($num)
,根据我的测试,这似乎在实践中成立。它至少比我的强力循环更有效。
来自@Jesse 的代码对我不起作用,所以构建这个:
function formatFloat(float $num, int $dec = 0) : string {
// format to maximum decimal places, and make positive
$abs = number_format(abs($num), 17, '.', '');
// is it less than 0?
if (!floor($abs)) {
// look through each number until we find one that is not 0
foreach (str_split(substr($abs, 2)) AS $i => $item) {
if ($item) {
break;
}
}
// add defined decimal places
$dec += $i + 1;
}
// format the output
return number_format($num, $dec);
}
它可以处理大于一的数字、负数,并使您能够指定在找到第一个有效数字后将浮点数格式化为多少小数位。它的性能也更好。
我想用重现它所需的最少小数位数格式化一个浮点数。
PHP 有一个 number_format()
函数,用于呈现具有指定小数位数的数字。但是,如果我用它来格式化小数位数非常多的 0.1,我会得到:
print rtrim(number_format(0.1, 1000, '.', ''), '0');
// 0.1000000000000000055511151231257827021181583404541015625
从(float)"0.1" === 0.1
开始,第16位后的那些额外的55511151...
位小数就没用了。
我可以像这样使用循环:
function format_float($float) {
$decimals = 1;
do {
$result = number_format($float, $decimals, '.', '');
$decimals++;
} while ((float)$result !== $float);
return $result;
}
print format_float(0.1) . "\n"; // 0.1
print format_float(1/3) . "\n"; // 0.3333333333333333
print format_float(1E-50) . "\n"; // 0.00000000000000000000000000000000000000000000000001
但肯定有更简单、更有效的方法吗?
正确打印二进制浮点数的最小小数位数是一项非常复杂的工作。当前最先进的是 grisu family of algorithms. For a good explanation of the problems involved, see the classic paper by Steele and White.
这是我想出的:
function format_float($num) {
$dec = $num == 0 ? 0 : ceil(-log10(abs($num)));
$dec = max(1, $dec + 15 /* magic number */);
$res = number_format($num, $dec, '.', '');
// sometimes we need one more decimal
if ((float)$res !== $num) {
$res = number_format($num, $dec + 1, '.', '');
}
list($l, $r) = explode('.', $res, 2);
return "$l." . (rtrim($r) ?: '0');
}
它假定所需的小数位数为 15 - log10($num)
或 16 - log10($num)
,根据我的测试,这似乎在实践中成立。它至少比我的强力循环更有效。
来自@Jesse 的代码对我不起作用,所以构建这个:
function formatFloat(float $num, int $dec = 0) : string {
// format to maximum decimal places, and make positive
$abs = number_format(abs($num), 17, '.', '');
// is it less than 0?
if (!floor($abs)) {
// look through each number until we find one that is not 0
foreach (str_split(substr($abs, 2)) AS $i => $item) {
if ($item) {
break;
}
}
// add defined decimal places
$dec += $i + 1;
}
// format the output
return number_format($num, $dec);
}
它可以处理大于一的数字、负数,并使您能够指定在找到第一个有效数字后将浮点数格式化为多少小数位。它的性能也更好。