PHP – Сериализовать плавающие точки

Я генерирую 10 случайных поплавков между 6 и 8 (все по уважительной причине) и записывая их в базу данных mysql в сериализованной форме. Но, по-видимому, при хранении появляется одна причуда:

Перед хранением я просто выводя те же данные, чтобы увидеть, как он выглядит, и это результат, который я получаю

a:10:{i:0;d:6.20000000000000017763568394002504646778106689453125;i:1;d:7.5999999999999996447286321199499070644378662109375;i:2;d:6.4000000000000003552713678800500929355621337890625;..} 

Как вы можете видеть, я получаю длинные цифры как 6.20000000000000017763568394002504646778106689453125 вместо того, что мне действительно понравится, всего 6.2. Это происходит только тогда, когда я сериализую данные, если я просто выдаю массив, я получаю поплавки до одного десятичного знака. Вот мой код:

 function random_float ($min,$max) { return ($min+lcg_value()*(abs($max-$min))); } $a1 = random_float(6, 8); $a1 = round($a1, 1); $a2 = random_float(6, 8); $a2 = round($a2, 1); $a3 = random_float(6, 8); $a3 = round($a3, 1); ... $array = array($a1, $a2, $a3, $a4, $a5, $a6, $a7, $a8, $a9, $a10); echo serialize($array); 

Число, подобное 6.2, невозможно точно представить с использованием математики с плавающей запятой в компьютерах, так как нет конечного представления базы 2. То, что вы видите, когда echo -число является чем-то, предназначенным для чтения человеком, и, таким образом, значение будет округлено до того, что поплавки могут обеспечить с точностью (около 6 знаков после запятой для 32-бит и 17 для 64-битных значений FP).

Однако при сериализации этих значений вам действительно нужно точное значение (т. Е. Все биты, которые там есть), а не только ближайшее «приятное» значение. Может быть более одного представления float / double, которое оценивается примерно до 6.2, и при сериализации вы, как правило, действительно хотите сохранить его точные значения до последнего бит, который у вас есть, чтобы правильно их восстановить. Вот почему вы получаете смешную «точность» в ценностях. Все просто сохранить точное представление бит о том, с чего вы начали.

Но почему именно вы хотите строго контролировать сериализованный вывод? Я имею в виду, он просто там, чтобы вы могли объединить свою структуру данных и прочитать ее позже. Вы, конечно же, не хотите использовать сериализованное представление где-нибудь в выпуске для людей или так. Поэтому, если речь идет о «приятных» значениях, вы не должны использовать сериализацию, которая имеет совершенно другую цель.

Сохраните их как строки после использования number_format :

 $number = number_format($float, 2); 

Храните их в виде целых чисел (сдвиньте первую десятичную точку перед точкой, умножив ее на 10) и преобразуйте обратно, если вам это нужно:

 function random_float($min,$max) { return ($min+lcg_value()*(abs($max-$min))); } $array = array(); for ($i=0; $i<10; $i++) { $array[] = (int) round(random_float(6, 8) * 10); } $serialized = serialize($array); var_dump($serialize); $array = unserialize($serialized); foreach ($array as $key => $val) { $array[$key] = $val / 10; } var_dump($array); 

Вот мой ответ на ответ Гумбо. Я помещаю IteratorAggregate туда, поэтому он будет доступен для foreach, но вы также можете добавить Countable и ArrayAccess.

 <?php class FloatStorage implements IteratorAggregate { protected $factor; protected $store = array(); public function __construct( array $data, $factor=10 ) { $this->factor = $factor; $this->store = $data; } public function __sleep() { array_walk( $this->store, array( $this, 'toSerialized' ) ); return array( 'factor', 'store' ); } public function __wakeup() { array_walk( $this->store, array( $this, 'fromSerialized' ) ); } protected function toSerialized( &$num ) { $num *= $this->factor; } protected function fromSerialized( &$num ) { $num /= $this->factor; } public function getIterator() { return new ArrayIterator( $this->store ); } } function random_float ($min,$max) { return ($min+lcg_value()*(abs($max-$min))); } $original = array(); for ( $i = 0; $i < 10; $i++ ) { $original[] = round( random_float( 6, 8 ), 1 ); } $stored = new FloatStorage( $original ); $serialized = serialize( $stored ); $unserialized = unserialize( $serialized ); echo '<pre>'; print_r( $original ); print_r( $serialized ); print_r( $unserialized ); echo '</pre>'; 

Для меня я нашел 3 способа:

  1. конвертировать float в целое число после того, как float var умножается на большое число (например, 1,000,000); это не очень удобный способ, так как вы не должны забывать делиться тем же 1,000,000, когда он используется
  2. для использования preg_replace('/d:([0-9]+(\.[0-9]+)?([Ee][+-]?[0-9]+)?);/e', "'d:'.((float)$1).';'", $value); где $ value – ваш float; найдено здесь
  3. также, чтобы округлить float round () и сохранить его в массиве как строку.

В любом случае я использую вариант №2

Просто снимите точность:

 ini_set('serialize_precision',2); 

Кастинг также работает , и он быстрее , Пример:

 $a = 0.631; $b = serialize($a); $c = serialize((string)$a); var_dump($b); 

строка (57) "d: 0,6310000000000000053290705182007513940334320068359375;"

 var_dump($c); 

string (12) "s: 5:" 0.631 ";"

 var_dump(unserialize($b)); 

Поплавок (0,631)

 var_dump(unserialize($c)); 

строка (5) "0,631"

Важно отбросить его на unserialize:

 var_dump((float)unserialize($c)); 

Поплавок (0,631)

Файл PHP.INI содержит директиву serialize_precision , которая позволяет вам контролировать, сколько значимых цифр будет сериализовано для вашего поплавка. В вашем случае сохранение всего одного десятичного числа чисел от 6 до 8 означает две значащие цифры.

Вы можете установить этот параметр в файле php.ini или непосредственно в вашем скрипте:

 ini_set('serialize_precision', 2); 

Если вас не интересует точное количество значимых цифр, но не заботясь о том, чтобы не иметь спагетти цифр, связанных с тем, как хранятся числа с плавающей точкой, вы также можете дать значение -1, которое вызывает «специальный алгоритм округления» ", это, скорее всего, сделает именно то, что требуется:

 ini_set('serialize_precision', -1); 

Вы можете даже вернуть его обратно к своему первоначальному значению после сериализации:

  $prec = ini_get('serialize_precision'); ini_set('serialize_precision', -1); ... // your serialization here ini_set('serialize_precision', $prec);