PHP:将任何浮点设置为十进制扩展格式

PHP:将任何浮点设置为十进制扩展格式,php,floating-point,floating-point-conversion,Php,Floating Point,Floating Point Conversion,我想创建一个函数formatFloat(),它接受任何浮点并将其格式化为十进制扩展字符串。例如: formatFloat(1.0E+25); // "10,000,000,000,000,000,000,000,000" formatFloat(1.0E+24); // "1,000,000,000,000,000,000,000,000" formatFloat(1.000001); // "1.000001" formatFloat(1.000001E-10); // "0

我想创建一个函数
formatFloat()
,它接受任何浮点并将其格式化为十进制扩展字符串。例如:

formatFloat(1.0E+25);  // "10,000,000,000,000,000,000,000,000"
formatFloat(1.0E+24);  // "1,000,000,000,000,000,000,000,000"

formatFloat(1.000001);      // "1.000001"
formatFloat(1.000001E-10);  // "0.0000000001000001"
formatFloat(1.000001E-11);  // "0.00000000001000001"
最初的想法 简单地将浮点值转换为字符串是不起作用的,因为对于大于大约
1.0E+14
,或小于大约
1.0E-4
,PHP将它们呈现为而不是

显然是要尝试的PHP函数。但是,大型浮动会出现此问题:

number_format(1.0E+25);  // "10,000,000,000,000,000,905,969,664"
number_format(1.0E+24);  // "999,999,999,999,999,983,222,784"
对于小浮点数,困难在于选择要求的小数位数。一种方法是要求输入大量十进制数字,然后输入多余的
0
s。但是,这种想法是有缺陷的,因为十进制扩展通常不会以
0
s结尾:

number_format(1.000001,     30);  // "1.000000999999999917733362053696"
number_format(1.000001E-10, 30);  // "0.000000000100000099999999996746"
number_format(1.000001E-11, 30);  // "0.000000000010000010000000000321"
function formatFloat($value)
{
    if ($value == 0.0)  return '0.0';

    $decimalDigits = max(
        13 - floor(log10(abs($value))),
        0
    );

    $formatted = number_format($value, $decimalDigits);

    // Trim excess 0's
    $formatted = preg_replace('/(\.[0-9]+?)0*$/', '$1', $formatted);

    return $formatted;
}
问题是浮点数具有,并且通常无法存储文字的精确值(例如:
1.0E+25
)。相反,它存储可以表示的最接近的可能值<代码>数字格式()揭示了这些“最接近的近似值”

Timo-Frenay解 我在页面的深处发现,令人惊讶的是没有投票:

以下是如何打印16位有效数字的浮点数,而不考虑大小:

关键部分是使用来确定浮点数,然后计算所需的小数位数

有几个bug需要修复:

  • 该代码不适用于负浮动
  • 该代码不适用于非常小的浮动(例如:
    1.0E-100
    )。PHP报告此通知:“
    sprintf()
    :请求的116位精度被截断为PHP最大53位”
  • 如果
    $value
    0.0
    ,则
    log10($value)
    -INF
  • 由于a的精度是“大约14位十进制数字”,我认为应该显示14位有效数字,而不是16位
我最好的尝试 这是我想出的最好的解决办法。它基于Timo Frenay的解决方案,修复了缺陷,并用于修剪多余的
0
s:

number_format(1.000001,     30);  // "1.000000999999999917733362053696"
number_format(1.000001E-10, 30);  // "0.000000000100000099999999996746"
number_format(1.000001E-11, 30);  // "0.000000000010000010000000000321"
function formatFloat($value)
{
    if ($value == 0.0)  return '0.0';

    $decimalDigits = max(
        13 - floor(log10(abs($value))),
        0
    );

    $formatted = number_format($value, $decimalDigits);

    // Trim excess 0's
    $formatted = preg_replace('/(\.[0-9]+?)0*$/', '$1', $formatted);

    return $formatted;
}
有200个随机浮动。对于小于约
1.0E+15
的所有浮动,该代码似乎都能正常工作

有趣的是,
number\u format()

formatFloat(1.000001E-250);  // "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000001"
问题 我在
formatFloat()
上的最佳尝试仍然存在以下问题:

formatFloat(1.0E+25);  // "10,000,000,000,000,000,905,969,664"
formatFloat(1.0E+24);  // "999,999,999,999,999,983,222,784"
是否有一种优雅的方法来改进代码以解决此问题?

这段代码。我不认为我能使它比你的更优雅,但我花了太多时间在它上面,我不能把它扔掉:)

函数格式浮点(
$value,
$noOfDigits=14,
$separator=',',
$decimal='.'
) {
$exponent=floor(log10(abs$value));
$量级=功率(10$指数);
//提取有效数字
$尾数=(字符串)绝对值(圆形($value/pow(10,$index-$noOfDigits+1));
$formattedNum='';
如果($index>=0){//if($value>=1)
//只是为了预先格式化
$formattedNum=number\u格式($value,$noOfDigits-1,$decimal,$separator);
//然后将$尾数中的数字报告到$formattedNum中
$formattedLen=strlen($formattedNum);
$尾数=strlen($尾数);
对于($fnPos=0,$mPos=0;$fnPos<$formattedLen;$fnPos++,$mPos++){
//跳过非数字
而($formattedNum[$fnPos]==$separator | |$formattedNum[$fnPos]===$decimal | | |$formattedNum[$fnPos]===='-')){
$fnPos++;
}
$formattedNum[$fnPos]=$mPos<$尾数?$尾数[$mPos]:“0”;
}
}else{//if($value<1)
//如有必要,请预先添加减号
如果($value<0){
$formattedNum='-';
}
$formattedNum.=“0”。$decimal.str_repeat('0',abs($exponent)-1)。$尾数;
}
//带尾随的十进制零
$formattedNum=preg\u replace('/\.?0*$/',''$formattedNum);
返回$formattedNum;
}

我成功地创建了这个(相当不雅观的)解决方案

function formatFloat($value)
{
    $phpPrecision = 14;

    if ($value == 0.0)  return '0.0';

    if (log10(abs($value)) < $phpPrecision) {

        $decimalDigits = max(
            ($phpPrecision - 1) - floor(log10(abs($value))),
            0
        );

        $formatted = number_format($value, $decimalDigits);

        // Trim excess 0's
        $formatted = preg_replace('/(\.[0-9]+?)0*$/', '$1', $formatted);

        return $formatted;

    }

    $formattedWithoutCommas = number_format($value, 0, '.', '');

    $sign = (strpos($formattedWithoutCommas, '-') === 0) ? '-' : '';

    // Extract the unsigned integer part of the number
    preg_match('/^-?(\d+)(\.\d+)?$/', $formattedWithoutCommas, $components);
    $integerPart = $components[1];

    // Split into significant and insignificant digits
    $significantDigits   = substr($integerPart, 0, $phpPrecision);
    $insignificantDigits = substr($integerPart, $phpPrecision);

    // Round the significant digits (using the insignificant digits)
    $fractionForRounding = (float) ('0.' . $insignificantDigits);
    $rounding            = (int) round($fractionForRounding);  // Either 0 or 1
    $rounded             = $significantDigits + $rounding;

    // Pad on the right with zeros
    $formattingString = '%0-' . strlen($integerPart) . 's';
    $formatted        = sprintf($formattingString, $rounded);

    // Insert a comma between every group of thousands
    $formattedWithCommas = strrev(
        rtrim(
            chunk_split(
                strrev($formatted), 3, ','
            ),
            ','
        )
    );

    return $sign . $formattedWithCommas;
}
如果浮点值小于
1.0E+14
,则它使用我问题中的“最佳尝试”代码。否则,它将整数部分舍入为14位有效数字

有500个随机浮点数,代码似乎对所有浮点数都正常工作

正如我所说,这不是一个非常优雅的实现,所以我仍然非常有兴趣知道是否有人能够设计出更好的解决方案

function formatFloat($value)
{
    $phpPrecision = 14;

    if ($value == 0.0)  return '0.0';

    if (log10(abs($value)) < $phpPrecision) {

        $decimalDigits = max(
            ($phpPrecision - 1) - floor(log10(abs($value))),
            0
        );

        $formatted = number_format($value, $decimalDigits);

        // Trim excess 0's
        $formatted = preg_replace('/(\.[0-9]+?)0*$/', '$1', $formatted);

        return $formatted;

    }

    $formattedWithoutCommas = number_format($value, 0, '.', '');

    $sign = (strpos($formattedWithoutCommas, '-') === 0) ? '-' : '';

    // Extract the unsigned integer part of the number
    preg_match('/^-?(\d+)(\.\d+)?$/', $formattedWithoutCommas, $components);
    $integerPart = $components[1];

    // Split into significant and insignificant digits
    $significantDigits   = substr($integerPart, 0, $phpPrecision);
    $insignificantDigits = substr($integerPart, $phpPrecision);

    // Round the significant digits (using the insignificant digits)
    $fractionForRounding = (float) ('0.' . $insignificantDigits);
    $rounding            = (int) round($fractionForRounding);  // Either 0 or 1
    $rounded             = $significantDigits + $rounding;

    // Pad on the right with zeros
    $formattingString = '%0-' . strlen($integerPart) . 's';
    $formatted        = sprintf($formattingString, $rounded);

    // Insert a comma between every group of thousands
    $formattedWithCommas = strrev(
        rtrim(
            chunk_split(
                strrev($formatted), 3, ','
            ),
            ','
        )
    );

    return $sign . $formattedWithCommas;
}
函数formatFloat($value)
{
$phpPrecision=14;
如果($value==0.0),则返回“0.0”;
if(对数10(绝对值($value))<$phpPrecision){
$decimalDigits=最大值(
($phpPrecision-1)-地板(log10(abs($value)),
0
);
$formatted=数字\格式($value,$decimalDigits);
//修剪多余的0
$formatted=preg_replace('/(\.[0-9]+?)0*$/','$1',$formatted);
返回$formatted;
}
$formattedWithoutCommas=number_格式($value,0,'.'','');
$sign=(strpos($formattedwithout逗号,'-')==0)?'-':'';
//提取数字的无符号整数部分
预匹配('/^-?(\d+)(\.\d+?$/'),不带逗号的$formatted,$components);
$integerPart=$components[1];
//分成有意义和无意义的数字
$significtdigits=substr($integerPart,0,$phpPrecision);
$insignificantDigits=substr($integerPart,$phpPrecision);
//将有效数字四舍五入(使用不重要的数字)
$fractionForRounding=(浮点)('0..$insignificantDigits);
$rounding=(int)round($fractionForRounding);//0或1
$rounded=$significtdigits+$rounding;
//在右边用零填充
$formattingString='%0-'。strlen($integerPart)。's';
$formatted=sprintf($formattingString,$rounded);
//在每组数千人之间插入逗号
$forma