PHP7.1 json_encode () Випуск Float


93

Це не питання, оскільки це більше про те, щоб бути в курсі. Я оновив додаток, який використовує json_encode()PHP7.1.1, і я бачив проблему зі зміною поплавків, які іноді розширювали 17 цифр. Згідно з документацією, PHP 7.1.x почав використовувати serialize_precisionзамість точності при кодуванні подвійних значень. Я здогадуюсь, це спричинило приклад значення

472,185

ставати

472,18500000000006

після цього значення пройшло json_encode(). З мого відкриття я повернувся до PHP 7.0.16 і у мене більше немає проблеми з json_encode(). Я також спробував оновити до PHP 7.1.2, перш ніж повернутися до PHP 7.0.16.

Обґрунтування цього питання випливає з PHP - Точність плаваючого числа , однак в кінці всі причини цього полягають у зміні від точності до використання serialize_precision у 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);можливо, змусило б його серіалізуватися, як це було раніше, однак, якщо ви дійсно покладаєтесь на конкретну точність ваших плаваючих засобів, ви робите щось не так.
apokryfos

1
"Якщо хтось знає про вирішення цієї проблеми" - яка проблема? Я не бачу тут жодної проблеми. Якщо ви декодуєте JSON за допомогою PHP, ви повернете закодоване значення. І якщо ви декодуєте його іншою мовою, швидше за все, ви отримаєте те саме значення. У будь-якому випадку, якщо ви надрукуєте значення з 12 цифр, ви повернете початкове ("правильне") значення. Вам потрібна точність більше 12 десяткових цифр для поплавків, що використовуються вашим додатком?
axiac

12
@axiac 472.185! = 472.18500000000006. Існує чітка різниця до та після. Це частина запиту AJAX до браузера, і значення має залишатися у вихідному стані.
Gwi7d31

4
Я намагаюся уникати перетворення рядків, оскільки кінцевим продуктом є Highcharts, і він не приймає рядки. Я думаю, я вважав би це дуже неефективним і неакуратним, якщо взяти значення float, віддати його як рядок, відіслати, а потім попросити javascript інтерпретувати рядок назад у float за допомогою parseFloat (). Чи не так?
Gwi7d31

1
@axiac Я зауважую, що ти PHP json_decode () повертає початкове плаваюче значення. Однак, коли javascript повертає рядок JSON назад до об'єкта, він не перетворює значення назад на 472.185, як ви потенційно наводили ... звідси проблема. Я буду дотримуватися того, що маю намір.
Gwi7d31,

Відповіді:


97

Це трохи звело мене з глузду, поки я нарешті не знайшов цю помилку, яка вказує вам на цей RFC, який говорить

В даний час json_encode()використовується EG (точність), яка встановлена ​​на 14. Це означає, що для відображення (друку) номера використовується не більше 14 цифр. IEEE 754 double підтримує вищу точність та serialize()/ var_export()використовує PG (serialize_precision), який за замовчуванням встановлений на 17, щоб бути точнішим. Оскільки json_encode()використовує EG (точність), json_encode()видаляє нижчі цифри дробових частин і знищує початкове значення, навіть якщо плаваючий код PHP може містити більш точне значення плаваючого числа.

І (наголос на моєму)

Цей RFC пропонує ввести нову настройку EG (точність) = - 1 та PG (serialize_precision) = - 1, яка використовує режим zend_dtoa () 0, який використовує кращий алгоритм для округлення плаваючих чисел (-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 також може використовуватися в PHP 5.4
kittygirl

1
Будьте обережні з serialize_precision = -1. З -1 цей код echo json_encode([528.56 * 100]);друкується[52855.99999999999]
vl.lapikov

3
@ vl.lapikov Однак це звучить більше як загальна помилка з плаваючою комою . Ось демонстрація , де ви можете це зрозуміти - це не просто json_encodeпроблема
Machavity

39

Як розробник плагінів, я не маю загального доступу до налаштувань 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
Будьте обережні з цим, оскільки ваш плагін може змінити несподівані налаштування для решти програми розробника. Але, ІМО, я не впевнений, наскільки руйнівним може стати цей варіант ... ха-ха
igorsantos07

Майте на увазі, що значення точності зміни (другий приклад) може мати більший вплив на інші математичні операції, які у вас там є. php.net/manual/en/ini.core.php#ini.precision
Рікардо Мартінс

@RicardoMartins: Відповідно до документації, за замовчуванням точність становить 14. Наведене вище виправлення збільшує це значення до 17. Тож воно повинно бути ще точнішим. Ви згодні?
алев

@alev те, що я говорив, це те, що, змінивши лише serialize_precision, достатньо, і не компрометуйте інші поведінки PHP, які може зазнати ваша програма
Рікардо Мартінс,

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 (як це було встановлено в моєму файлі ini PHP7.0). Очевидно, зміна значення цього числа змінює значення обчисленого плаваючого числа.


3

Інші рішення для мене не працювали. Ось що я повинен був додати на початку мого виконання коду:

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

Хіба це в принципі не те саме, що відповідь Алін Поп?
igorsantos07

1

Що стосується мене, проблема полягала в тому, що JSON_NUMERIC_CHECK як другий аргумент json_encode () пройшов, який передавав усі типи чисел на 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, і він не працює (вихід все ще має занадто високу точність). Очевидно, прапор JSON_NUMERIC_CHECK не працює, оскільки PHP 7.1 - php.net/manual/de/json.constants.php#123167
Філіпп

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

Здається, проблема виникає, коли serializeі serialize_precisionвстановлюються різні значення. У моєму випадку 14 та 17 відповідно. Встановивши для них обох значення 14, вирішено проблему, як і встановлено serialize_precisionзначення -1.

Значення за замовчуванням serialize_precision було змінено на -1 станом на PHP 7.1.0, що означає "буде використовуватися вдосконалений алгоритм округлення таких чисел". Але якщо ви все ще стикаєтеся з цією проблемою, можливо, це пов’язано з тим, що у вас є конфігураційний файл PHP із попередньої версії. (Можливо, ви зберігали файл конфігурації під час оновлення?)

Інша річ, яку слід врахувати, - чи є сенс взагалі використовувати плаваючі значення у вашому випадку. Можливо, а може і не мати сенсу використовувати рядкові значення, що містять ваші числа, щоб забезпечити належну кількість десяткових знаків завжди зберігатись у вашому JSON.


-1

Ви можете змінити [max] => 472.185 із плаваючого на рядок ([max] => '472.185') перед json_encode (). Оскільки json у будь-якому випадку є рядком, перетворення значень float у рядки перед json_encode () збереже значення, яке ви бажаєте.


Технічно це певною мірою вірно, але дуже неефективно. Якщо Int / Float у рядку JSON не вказано, тоді Javascript може розглядати його як фактичний Int / Float. Виконання передачі змушує вас повернути кожне значення назад до Int / Float один раз на стороні браузера. Я часто мав справу з 10000+ значеннями, працюючи над цим проектом за запитом. Багато обробки здуття могло б закінчитися.
Gwi7d31,

Якщо ви використовуєте JSON для кудись надсилання даних, і очікується число, але ви надсилаєте рядок, це не гарантовано спрацює. У ситуаціях, коли розробник програми, що надсилає, не контролює програму, що отримує, це не рішення.
osullic
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.