PHP7.1 json_encode () 플로트 문제


92

이것은 더 잘 알고 있기 때문에 질문이 아닙니다. json_encode()PHP7.1.1 을 사용하는 응용 프로그램을 업데이트 했는데 수레가 때때로 17 자리를 확장하도록 변경되는 문제를 확인했습니다. 문서에 따르면 PHP 7.1.x는 serialize_precisiondouble 값을 인코딩 할 때 정밀도 대신 사용하기 시작했습니다 . 이로 인해 예제 값이 발생했다고 생각합니다.

472.185

472.18500000000006

그 값이 지나간 후에 json_encode(). 내 발견 이후로 PHP 7.0.16으로 되 돌렸고 더 이상 json_encode(). 또한 PHP 7.0.16으로 되돌리기 전에 PHP 7.1.2로 업데이트하려고했습니다.

이 질문에 대한 이유는 PHP 에서 비롯된 것입니다. 것이지만, 결국 모든 이유는 json_encode().

이 문제에 대한 해결책을 아는 사람이 있다면 추론 / 수정에 대해 기꺼이 경청 할 것입니다.

다차원 배열에서 발췌 (이전) :

[staticYaxisInfo] => Array
                    (
                        [17] => stdClass Object
                            (
                                [variable_id] => 17
                                [static] => 1
                                [min] => 0
                                [max] => 472.185
                                [locked_static] => 1
                            )

                    )

그리고 통과 후 json_encode()...

"staticYaxisInfo":
            {
                "17":
                {
                    "variable_id": "17",
                    "static": "1",
                    "min": 0,
                    "max": 472.18500000000006,
                    "locked_static": "1"
                }
            },

6
ini_set('serialize_precision', 14); ini_set('precision', 14);아마도 예전처럼 직렬화 될 것입니다. 그러나 당신이 정말로 당신의 float에 특정한 정밀도에 의존한다면 당신은 뭔가 잘못하고있는 것입니다.
apokryfos

1
"누군가가이 문제에 대한 해결책을 알고 있다면" -어떤 문제? 여기서 문제가 보이지 않습니다. PHP를 사용하여 JSON을 디코딩하면 인코딩 한 값이 반환됩니다. 그리고 다른 언어를 사용하여 디코딩하면 아마도 동일한 값을 얻을 수 있습니다. 어느 쪽이든 12 자리 숫자로 값을 인쇄하면 원래 ( "올바른") 값이 반환됩니다. 애플리케이션에서 사용하는 부동 소수점에 대해 12 자리 이상의 정밀도가 필요합니까?
axiac

12
@axiac 472.185! = 472.18500000000006. 이전과 이후의 명확한 차이가 있습니다. 이것은 브라우저에 대한 AJAX 요청의 일부이며 값은 원래 상태를 유지해야합니다.
Gwi7d31

4
최종 제품이 Highcharts이고 문자열을 허용하지 않으므로 문자열 변환을 사용하지 않으려 고합니다. 나는 당신이 float 값을 취하고 그것을 문자열로 캐스트하고 그것을 보낸 다음 자바 스크립트가 parseFloat ()를 사용하여 문자열을 다시 float로 해석하도록하면 매우 비효율적이고 조잡하다고 생각할 것이라고 생각합니다. 당신은?
Gwi7d31

1
@axiac 나는 당신이 PHP라는 점에 주목합니다. json_decode ()는 원래 float 값을 다시 가져옵니다. 그러나 자바 스크립트가 JSON 문자열을 객체로 되돌릴 때 잠재적으로 암시 한 것처럼 값을 472.185로 다시 변환하지 않으므로 문제가 발생합니다. 나는 내가 가고있는 것을 고수 할 것이다.
Gwi7d31 2017 년

답변:


97

RFC 를 가리키는 이 버그 를 마침내 발견 할 때까지 이로 인해 잠시 미쳐 버렸습니다.

현재 json_encode()는 14로 설정된 EG (정밀도)를 사용하고 있습니다. 이는 최대 14 자리 숫자가 숫자를 표시 (인쇄)하는 데 사용된다는 의미입니다. IEEE 754 double은 더 높은 정밀도를 지원하고 serialize()/ var_export()는 더 정확한 17을 기본값으로 설정하는 PG (serialize_precision)를 사용합니다. json_encode()EG (precision)를 사용 하기 때문에 json_encode()PHP의 float가 더 정확한 float 값을 가질 수 있더라도 분수 부분의 하위 자릿수를 제거하고 원래 값을 파괴합니다.

그리고 (내 강조)

이 RFC 는 zend_dtoa ()의 모드 0을 사용 하는 새로운 설정 EG (precision) =-1 및 PG (serialize_precision) = -1을 도입 할 것을 제안합니다.이 모드는 부동 소수점 수를 반올림하는 데 더 나은 알고리즘을 사용합니다 (-1은 0 모드를 나타내는 데 사용됨). .

요컨대, PHP 7.1 json_encode이 새롭고 향상된 정밀 엔진을 사용 하도록 만드는 새로운 방법이 있습니다. 에서 php.ini의 당신은 변화를 필요 serialize_precision

serialize_precision = -1

이 명령 줄에서 작동하는지 확인할 수 있습니다.

php -r '$price = ["price" => round("45.99", 2)]; echo json_encode($price);'

당신은 얻어야한다

{"price":45.99}

G(precision)=-1그리고 PG(serialize_precision)=-1 또한 5.4 PHP에서 사용할 수 있습니다
kittygirl

1
조심하세요 serialize_precision = -1. -1을 사용하면이 코드가 echo json_encode([528.56 * 100]);인쇄됩니다[52855.99999999999]
vl.lapikov

3
@ vl.lapikov 그래도 일반적인 부동 소수점 오류 처럼 들립니다 . 여기 데모 당신이 명확하게 단지 아닙니다 볼 수 있습니다 json_encode문제
Machavity

38

플러그인 개발자로서 저는 서버의 php.ini 설정에 대한 일반적인 액세스 권한이 없습니다. 그래서 Machavity의 답변에 따라 PHP 스크립트에서 사용할 수있는이 작은 코드를 작성했습니다. 스크립트 위에 올려 놓기 만하면 json_encode가 평소와 같이 계속 작동합니다.

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'serialize_precision', -1 );
}

어떤 경우에는 변수를 하나 더 설정해야합니다. 두 번째 솔루션이 첫 번째 솔루션이 작동하는 것으로 입증 된 모든 경우에서 제대로 작동하는지 확실하지 않기 때문에 두 번째 솔루션으로 추가하고 있습니다.

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'precision', 17 );
    ini_set( 'serialize_precision', -1 );
}

3
플러그인이 나머지 개발자 애플리케이션에 대해 예상치 못한 설정을 변경할 수 있으므로주의하십시오. 하지만, IMO,이 옵션이 얼마나 파괴적인지 잘 모르겠습니다 ... lol
igorsantos07

정밀도 값 변경 (두 번째 예)은 다른 수학적 연산에 더 큰 영향을 미칠 수 있습니다. php.net/manual/en/ini.core.php#ini.precision
Ricardo Martins

@RicardoMartins : 문서에 따라 기본 정밀도는 14입니다. 위의 수정은 이것을 17로 증가시킵니다. 따라서 더 정확해야합니다. 동의하십니까?
alev

@alev 내가 말한 것은 serialize_precision 만 변경하면 충분하며 응용 프로그램에서 경험할 수있는 다른 PHP 동작을 손상시키지 않는다는 것입니다
Ricardo Martins

4

나는 금전적 가치를 330.46인코딩하고 330.4600000000000363797880709171295166015625. PHP 설정을 변경하고 싶지 않거나 변경할 수없는 경우 데이터 구조를 미리 알고있는 경우 저에게 도움이되는 매우 간단한 솔루션이 있습니다. 단순히 문자열로 캐스트하십시오 (다음 두 가지 모두 동일한 작업을 수행함).

$data['discount'] = (string) $data['discount'];
$data['discount'] = '' . $data['discount'];

제 사용 사례에있어서 이것은 빠르고 효과적인 솔루션이었습니다. 이것은 JSON에서 다시 디코딩 할 때 큰 따옴표로 묶여 있기 때문에 문자열이됨을 의미합니다.


4

정밀도와 serialize_precision을 동일한 값 (10)으로 설정하여이 문제를 해결했습니다.

ini_set('precision', 10);
ini_set('serialize_precision', 10);

php.ini에서 설정할 수도 있습니다.


3

나는 같은 문제가 있었지만 serialize_precision = -1만이 문제를 해결하지 못했습니다. 정밀도 값을 14에서 17로 업데이트하기 위해 한 단계 더 수행해야했습니다 (내 PHP7.0 ini 파일에 설정 됨). 분명히 그 숫자의 값을 변경하면 계산 된 부동 소수점의 값이 변경됩니다.


3

다른 솔루션은 저에게 효과적이지 않았습니다. 코드 실행을 시작할 때 추가해야 할 사항은 다음과 같습니다.

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'precision', 17 );
    ini_set( 'serialize_precision', -1 );
}

이것은 기본적으로 Alin Pop의 답변과 동일하지 않습니까?
igorsantos07

1

나에게 문제는 json_encode ()의 두 번째 인수로 JSON_NUMERIC_CHECK가 전달되었을 때 모든 숫자 유형을 int (정수뿐만 아니라)로 캐스팅했습니다.


1

를 사용하여 필요한 정확한 정밀도로 문자열로 저장 number_format한 다음 옵션을 json_encode사용하여 저장합니다 JSON_NUMERIC_CHECK.

$foo = array('max' => number_format(472.185, 3, '.', ''));
print_r(json_encode($foo, JSON_NUMERIC_CHECK));

당신은 얻을 :

{"max": 472.185}

이렇게하면 소스 개체의 모든 숫자 문자열이 결과 JSON에서 숫자로 인코딩됩니다.


1
PHP 7.3에서 테스트했지만 작동하지 않습니다 (출력의 정밀도가 여전히 너무 높습니다). PHP 7.1-php.net/manual/de/json.constants.php#123167
Philipp

0
$val1 = 5.5;
$val2 = (1.055 - 1) * 100;
$val3 = (float)(string) ((1.055 - 1) * 100);
var_dump(json_encode(['val1' => $val1, 'val2' => $val2, 'val3' => $val3]));
{
  "val1": 5.5,
  "val2": 5.499999999999994,
  "val3": 5.5
}

0

문제가 발생했을 것 같습니다 serializeserialize_precision다른 값으로 설정됩니다. 제 경우에는 각각 14와 17입니다. 둘 다 14로 설정하면 설정과 마찬가지로 문제가 해결되었습니다.serialize_precision 하면 -1로 .

기본값 serialize_precision 은 PHP 7.1.0부터 -1로 변경되었습니다. 이는 "이러한 숫자를 반올림하기위한 향상된 알고리즘이 사용됩니다"를 의미합니다. 그러나이 문제가 계속 발생한다면 이전 버전의 PHP 구성 파일이 있기 때문일 수 있습니다. (업그레이드 할 때 구성 파일을 유지 했습니까?)

고려해야 할 또 다른 사항은 귀하의 경우에 float 값을 사용하는 것이 합리적인지 여부입니다. 숫자가 포함 된 문자열 값을 사용하여 적절한 소수 자릿수가 항상 JSON에 유지되도록하는 것이 합리적 일 수도 있고 그렇지 않을 수도 있습니다.


-1

json_encode () 전에 [max] => 472.185를 float에서 문자열 ([max] => '472.185')로 변경할 수 있습니다. 어쨌든 json은 문자열이므로 json_encode () 전에 float 값을 문자열로 변환하면 원하는 값이 유지됩니다.


이것은 기술적으로 어느 정도 사실이지만 매우 비효율적입니다. JSON 문자열의 Int / Float가 인용되지 않은 경우 Javascript는이를 실제 Int / Float로 볼 수 있습니다. 변환을 수행하면 브라우저 측에서 모든 단일 값을 Int / Float로 다시 캐스팅해야합니다. 저는이 프로젝트를 요청에 따라 작업 할 때 종종 10,000 개 이상의 값을 다루었습니다. 많은 부풀림 처리가 발생했을 것입니다.
Gwi7d31

JSON을 사용하여 데이터를 어딘가에 보내고 숫자가 예상되지만 문자열을 보내는 경우 작동이 보장되지 않습니다. 보내는 응용 프로그램의 개발자가받는 응용 프로그램을 제어 할 수없는 상황에서는 이것은 해결책이 아닙니다.
osullic
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.