PHP – serializar puntos flotantes

Estoy generando 10 flotantes aleatorios entre 6 y 8 (todo por una buena razón) y escribiéndolos en una base de datos mysql en forma serializada. Pero una peculiaridad parece surgir en el momento del almacenamiento:

Antes de almacenar, solo obtengo los mismos datos para ver cómo se ven, y este es el resultado que obtengo

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

Como puede ver, obtengo números largos como 6.20000000000000017763568394002504646778106689453125 en lugar de lo que realmente me gustaría ver, solo 6.2. Esto solo ocurre cuando serializo los datos, si acabo de sacar la matriz, obtengo los flotantes con un decimal. Aquí está mi código:

 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); 

Un número como 6.2 no se puede representar exactamente usando matemática de punto flotante en las computadoras ya que no hay una representación finita de la base 2. Lo que está viendo cuando el echo del número es algo destinado a la lectura humana, y por lo tanto el valor se redondeará a lo que los flotadores pueden proporcionar con precisión (aproximadamente 6 lugares decimales para 32 bits y 17 para valores FP de 64 bits).

Sin embargo, al serializar esos valores, realmente desea el valor exacto (es decir, todos los bits que están allí) y no solo el valor “agradable” más cercano. Puede haber más de una representación flotante / doble que evalúa aproximadamente 6.2 y, al serializar, generalmente desea almacenar los valores exactos en el último bit que tiene para restaurarlos correctamente. Es por eso que estás obteniendo una “precisión” ridícula en los valores allí. Todo es solo para preservar la representación exacta de los bits con los que comenzó.

Pero, ¿por qué exactamente quieres controlar la salida serializada con tanta fuerza? Quiero decir, está ahí para que puedas redirigir tu estructura de datos y leerla más tarde. Ciertamente, no desea utilizar esa representación serializada en algún lugar de salida para humanos más o menos. Entonces, si solo se trata de valores “agradables”, no debe usar serializar, que tiene un propósito completamente diferente.

Guárdelos como cadenas después de usar number_format :

 $number = number_format($float, 2); 

Guárdelos como números enteros (cambie el primer decimal al frente del punto multiplicándolo por 10) y conviértelos de nuevo si lo necesita:

 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); 

Aquí está mi opinión sobre la respuesta de Gumbo. Puse IteratorAggregate allí, por lo que sería foreach-able, pero también podría agregar Countable y ArrayAccess.

 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 '
'; print_r( $original ); print_r( $serialized ); print_r( $unserialized ); echo '

';

Para mí encontré 3 formas:

  1. convertir float en entero después de multiplicar flo por var a un número grande (por ejemplo, 1,000,000); no es una manera muy conveniente ya que no debes olvidar dividir por los mismos 1,000,000 cuando se usa
  2. para usar preg_replace('/d:([0-9]+(\.[0-9]+)?([Ee][+-]?[0-9]+)?);/e', "'d:'.((float)$1).';'", $value); donde $ valor es tu flotador; encontrado aquí
  3. también, para redondear el flotador por round () y almacenarlo en array como string.

En cualquier caso, uso la variante n. ° 2

Solo reduce la precisión:

 ini_set('serialize_precision',2); 

El lanzamiento también funciona , y es más rápido , Ejemplo:

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

cadena (57) “d: 0.6310000000000000053290705182007513940334320068359375;”

 var_dump($c); 

cadena (12) “s: 5:” 0.631 “;”

 var_dump(unserialize($b)); 

flotar (0.631)

 var_dump(unserialize($c)); 

cadena (5) “0.631”

Lo importante es volver a lanzarlo en unserialize:

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

flotar (0.631)

El archivo PHP.INI contiene una directiva serialize_precision , que le permite controlar cuántos dígitos significativos serán serializados para su flotador. En su caso, almacenar solo un decimal de números entre 6 y 8 significa dos dígitos significativos.

Puede establecer esta configuración en el archivo php.ini o directamente en su secuencia de comandos:

 ini_set('serialize_precision', 2); 

Si no le importa la cantidad exacta de dígitos significativos, pero le preocupa no tener un espagueti de dígitos como resultado de la forma en que se almacenan los números flotantes, también puede dar un valor de -1, que invoca el “algoritmo especial de redondeo”. “, es probable que haga exactamente lo que se requiere:

 ini_set('serialize_precision', -1); 

Incluso puede restablecerlo a su valor original después de su serialización:

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