PHP - Сериализация плавающих точек - PullRequest
11 голосов
/ 10 июля 2009

Я генерирую 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);

Ответы [ 9 ]

16 голосов
/ 10 июля 2009

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

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

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

8 голосов
/ 10 июля 2009

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

$number = number_format($float, 2);
6 голосов
/ 13 сентября 2013

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

ini_set('serialize_precision',2);
3 голосов
/ 10 июля 2009

Сохраните их как целые числа (сдвиньте первую десятичную точку перед точкой, умножив ее на 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);
1 голос
/ 30 августа 2012

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

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

string (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"

Важно вернуть его на несериализацию:

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

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

1 голос
/ 09 октября 2010

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

  1. преобразовать число с плавающей точкой в ​​целое число после умножения числа с плавающей точкой на большое число (например, 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. также, чтобы округлить число с плавающей точкой с помощью round () и сохранить его в массиве в виде строки.

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

1 голос
/ 10 июля 2009

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

<code><?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 '
';
0 голосов
/ 13 декабря 2018

Установка значения serialize_precision в php.ini равным -1, решит проблему с плавающей запятой, или вы можете установить для нее значение, которое вы предпочитаете, в соответствии со спецификациями здесь: http://php.net/manual/en/ini.core.php#ini.serialize-precision

Версии PHP <= 5.3.5 поставляются со значением по умолчанию "100", в то время как в версии 7.0.33 по умолчанию используется значение "17", хотя пакет, поставляемый с вашим дистрибутивом, мог поставляться с "-1" </p>

Как указано в других ответах, вы можете переопределить этот параметр в самом приложении или даже в пользовательском php.ini, который указан в вашем контейнере VirtualHost или .htaccess.

Надеюсь, это поможет:)

0 голосов
/ 30 мая 2017

Файл 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);
...