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.

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