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);
Ответы
Ответ 1
Число, подобное 6.2, невозможно точно представить с использованием математики с плавающей запятой в компьютерах, так как нет конечного представления базы 2. То, что вы видите, когда echo
-исчисление числа - это что-то, предназначенное для чтения человеком, и, таким образом, значение будет округлено до того, что поплавки могут обеспечить с точностью (около 6 десятичных знаков для 32-битных и 17 для 64-битных значений FP).
Однако при сериализации этих значений вам действительно нужно точное значение (то есть все биты, которые там есть), а не только ближайшее "приятное" значение. Может быть более одного представления float/double, которое оценивается примерно до 6.2, и при сериализации вы, как правило, действительно хотите сохранить его точные значения до последнего бит, который у вас есть, чтобы правильно их восстановить. Вот почему вы получаете смешную "точность" в ценностях. Все это только для того, чтобы сохранить точное представление бит о том, с чего вы начали.
Но почему именно вы хотите строго контролировать сериализованный вывод? Я имею в виду, он просто там, чтобы вы могли объединить свою структуру данных и прочитать ее позже. Вы, конечно же, не хотите использовать сериализованное представление где-нибудь в выпуске для людей или так. Поэтому, если речь идет только о "симпатичных" значениях, вы не должны использовать сериализацию, которая имеет совершенно другую цель.
Ответ 2
Сохраните их как строки после использования number_format:
$number = number_format($float, 2);
Ответ 3
Просто снимите точность:
ini_set('serialize_precision',2);
Ответ 4
Храните их в виде целых чисел (сдвиньте первую десятичную точку перед точкой, умножив ее на 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);
Ответ 5
Вот мой ответ на вопрос Гумбо. Я помещаю 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>';
Ответ 6
Для меня я нашел 3 способа:
- конвертировать float в integer после того, как float var умножается на большое число (например, 1,000,000); это не очень удобный способ, так как вы не должны забывать делиться тем же 1,000,000, когда он использовал
- использовать
preg_replace('/d:([0-9]+(\.[0-9]+)?([Ee][+-]?[0-9]+)?);/e', "'d:'.((float)$1).';'", $value);
, где $value - ваш float; найдено здесь
- также, чтобы закруглить float round() и сохранить его в массиве как строку.
В любом случае я использую вариант # 2
Ответ 7
Кастинг также работает, а быстрее,
Пример:
$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)
Ответ 8
Файл PHP.INI содержит директиву serialize_precision, которая позволяет вам контролировать, сколько значащих цифр будет сериализован для вашего поплавка. В вашем случае сохранение всего одного десятичного числа чисел от 6 до 8 означает две значащие цифры.
Вы можете установить этот параметр в файле php.ini или непосредственно в script:
ini_set('serialize_precision', 2);
Если вы не заботитесь о точном количестве значимых цифр, но заботитесь о том, чтобы не иметь спагетти цифр, связанных с тем, как хранятся номера с плавающей запятой, вы также можете дать значение -1, которое вызывает "специальный алгоритм округления", это, скорее всего, сделает именно то, что требуется:
ini_set('serialize_precision', -1);
Вы можете даже вернуть reset к своему первоначальному значению после сериализации:
$prec = ini_get('serialize_precision');
ini_set('serialize_precision', -1);
... // your serialization here
ini_set('serialize_precision', $prec);
Ответ 9
Установка значения serialize_precision в php.ini на -1 решит проблему с плавающей запятой, или вы можете установить для нее значение, которое вы предпочитаете, в соответствии со спецификациями здесь: http://php.net/manual/en/ini.core.php # ini.serialize точности
Версии PHP <= 5.3.5 поставляются со значением по умолчанию "100", в то время как в версии 7.0.33 по умолчанию используется значение "17", хотя пакет, поставляемый с вашим дистрибутивом, мог поставляться с "-1"
Как указано в других ответах, вы можете переопределить этот параметр в самом приложении или даже в пользовательском php.ini, который указан в вашем контейнере VirtualHost или.htaccess.
Надеюсь, это поможет :)