如何在 PHP (json_encode) 中禁用 0.000005 等值的科学记数法?

How to disable scientific notation for values like 0.000005 in PHP (json_encode)?

提问人:A1t 提问时间:8/17/2022 更新时间:8/17/2022 访问量:429

问:

我正在尝试与某些合作伙伴 API 集成。

他们只接受金额为 float 类型的 json。

例:

  • 还行
    • {"amount":0.0000005}
  • 错误
    • {"amount":"0.0000005"}
    • {"amount":5.0E-7}

如果该值大于或等于 1,则始终是 OK 方案。但就我而言,我的值> 0,< 1。

代码示例:

$arr = ['amount' => 0.0000005];
$str = json_encode($arr);

echo $str;

输出:

{"amount":5.0e-7}

我希望输出如下所示:

{"amount":0.0000005}

在php中可能吗?可能是一些技巧和窍门?

PHP JSON 浮点 科学记数法

评论

1赞 IMSoP 8/17/2022
作为参考,根据 IETF 的 JSON 规范以及 Crockford 的原始定义,科学记数法似乎是一个有效的“数字”;所以,这不是一个PHP错误,也不是一个不同的数据类型。另一方面,如果系统不符合这些规范,并且不是您有影响力的人,那对您并没有真正的帮助。
0赞 A1t 8/17/2022
@Uwe 没有。这是不同的情况。我特别指出了哪些值出现错误!
0赞 Uwe 8/17/2022
对不起@A1t - 我的错误。我在解码而不是编码。

答:

0赞 Markus Zeller 8/17/2022 #1

保持它的唯一方法是将其转换为字符串。但是,它不再是一个数字了!

$arr = ['amount' => number_format(0.0000005, 7)];
$str = json_encode($arr);

{"amount":"0.0000005"}

Javascript 本身会使用科学记数法:

j = {"amount":"0.0000005"};
parseFloat(j.amount);
5e-7

一个技巧是删除引号。

$quoteless = preg_replace('/:"(\d+.\d+)"/', ':$1', $str);
echo $quoteless;

会给

{"amount":0.0000005}
1赞 IMSoP 8/17/2022 #2

我能想到的最干净的方法是遍历数据,递归地用占位符替换小数字;然后,在 JSON 编码后,将最终 JSON 字符串中的占位符替换为所需格式的数字。

令人惊讶的困难部分是格式化浮点数本身;我发现这个关于如何通过一些工作但不是很优雅的实现来做到这一点的现有问题。为简洁起见,我将该部分作为待办事项保留在下面。

class JsonMangler
{
    private const THRESHOLD = 0.0001;
    private const PLACEHOLDER = '__PLACEHOLDER__';

    private array $mangledData = [];
    private array $substitutions = [];
    private int $placeholderIncrement = 0;

    public function __construct(array $realData) {
        // Start the recursive function
        $this->mangledData = $this->mangle($realData);
    }

    private function mangle(array $realData): array {
        $mangledData = [];

        foreach ( $realData as $key => $realValue ) {
            if ( is_float($realValue) && $realValue < self::THRESHOLD) {
                // Substitute small floats with a placeholder
                $substituteValue = self::PLACEHOLDER . ($this->placeholderIncrement++);
                $mangledData[$key] = $substituteValue;
                // Placeholder will appear in quotes in the JSON, which we want to replace away
                $this->substitutions["\"$substituteValue\""] = $this->formatFloat($realValue);
            }
            elseif ( is_array($realValue) ) {
                // Recurse through the data
                $mangledData[$key] = $this->mangle($realValue);
            }
            else {
                // Retain everything else
                $mangledData[$key] = $realValue;
            }
        }

        return $mangledData;
    }

    /**
     * Format a float into a string without any exponential notation
     */
    private function formatFloat(float $value): string
    {
        // This is surprisingly hard to do; see https://stackoverflow.com/q/22274437/157957
        return 'TODO';
    }

    public function getJson(int $jsonEncodeFlags = 0): string
    {
        $mangledJson = json_encode($this->mangledData, $jsonEncodeFlags);
        return str_replace(array_keys($this->substitutions), array_values($this->substitutions), $mangledJson);
    }
}

将此实现用于 formatFloat,进行以下测试:

$example = [
    'amount' => 1.5,
    'small_amount' => 0.0001,
    'tiny_amount' => 0.0000005,
    'subobject' => [
        'sub_value' => 42.5,
        'tiny_sub_value' => 0.0000425,
        'array' => [
            1.23,
            0.0000123
        ]
    ]
];
echo (new JsonMangler($example))->getJson(JSON_PRETTY_PRINT);

结果如下:

{
    "amount": 1.5,
    "small_amount": 0.0001,
    "tiny_amount": 0.0000005,
    "subobject": {
        "sub_value": 42.5,
        "tiny_sub_value": 0.0000425,
        "array": [
            1.23,
            0.0000123
        ]
    }
}