2009-07-10 11 views
11

Je génère 10 flottants aléatoires entre 6 et 8 (tous pour une bonne raison), et les écrit dans une base de données mysql sous une forme sérialisée. Mais une bizarrerie semble émerger au moment de stockage:PHP - Sérialiser les points flottants

Avant de ranger Je suis juste pour sortir les mêmes données pour voir à quoi il ressemble, et c'est le résultat que je reçois

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

Comme vous pouvez le voir , Je reçois des nombres longs comme 6.20000000000000017763568394002504646778106689453125 au lieu de ce que je voudrais vraiment voir, juste 6.2. Cela se produit uniquement lorsque je sérialise les données, si je ne fais que sortir le tableau, j'obtiens les flottants à une décimale. Voici mon code:

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

Ressemble tours d'écho lui-même flotte, mais c'est étrange – usoban

Répondre

15

Un nombre comme 6.2 ne peut pas être représenté exactement en utilisant des mathématiques à virgule flottante dans les ordinateurs car il n'y a pas de représentation en base 2 finie. Ce que vous voyez lorsque echo -sur le numéro est quelque chose destiné à la lecture humaine, et donc la valeur sera arrondie à ce que les flotteurs peuvent fournir en précision (environ 6 décimales pour 32 bits et 17 pour FP valeurs 64 bits).

Lors de la sérialisation de ces valeurs, cependant, vous voulez vraiment la valeur exacte (c'est-à-dire tous les bits qui s'y trouvent) et pas seulement la valeur "agréable" la plus proche. Il peut y avoir plus d'une représentation float/double qui évalue à environ 6,2 et lors de la sérialisation voulez vraiment stocker les valeurs exactes au dernier bit que vous avez afin de les restaurer correctement. C'est pourquoi vous obtenez une "précision" ridicule là-bas. Tout est juste pour préserver la représentation exacte des bits de ce que vous avez commencé.

Mais pourquoi voulez-vous exactement contrôler la sortie sérialisée? Je veux dire, c'est juste là pour que vous puissiez faire le tour de votre structure de données et le relire plus tard. Vous ne voulez certainement pas utiliser cette représentation sérialisée quelque part dans la sortie pour les humains ou plus. Donc, s'il s'agit simplement de valeurs "jolies", vous ne devriez pas utiliser la sérialisation qui a un but entièrement différent.

+0

ah, merci pour l'explication, cela ira probablement un long chemin. La raison pour laquelle je voudrais garder mon tableau sérialisé simple est parce que je crains qu'une chaîne série plus longue augmentera globalement la charge sur le serveur de requête. J'aimerais apprendre que j'ai tort. merci à tout le monde pour les réponses rapides ... vous les gars rock! Juste pour garder les choses simples (c'est-à-dire sans fonctions supplémentaires), je pourrais juste rester avec cette solution, bien que Gumbo vôtre soit très élégant aussi bien. –

+0

Eh bien, les bases de données sont plutôt optimisées pour le cas d'utilisation de la récupération de données. Si vous lisez 200 octets ou 2000 ne devrait pas faire beaucoup de différence à moins que vous fassiez quelque chose de vraiment intense.Mais vous pouvez toujours optimiser quand il s'avère être un goulot d'étranglement. – Joey

+0

J'ai rencontré ce problème lors du stockage de données sérialisées. Je stockais un grand nombre d'enregistrements, donc utiliser littéralement 58 chiffres pour stocker ce qui aurait dû être 4 caractères semblait inacceptable. J'ai utilisé la solution number_format fournie par 'shadowhand' ci-dessous. –

3

magasin eux comme des entiers (décalage de la première décimale devant le point en multipliant par 10) et les convertir en arrière si vous en avez besoin:

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

qui est en fait une bonne solution, mais je pense que je vais rester avec la sérialisation pour l'instant, pour des raisons que j'ai écrit dans le commentaire ci-dessus. merci pour votre aide si –

1

Voici mon point de vue sur la réponse de Gumbo. J'ai mis IteratorAggregate là-bas donc ce serait foreach-able, mais vous pourriez également ajouter Countable et 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>'; 
8

les stocker sous forme de chaînes après avoir utilisé number_format:

$number = number_format($float, 2); 
+0

C'est ce que j'ai fait pour "résoudre" cette fonctionnalité. –

+0

nombre_format n'est pas un moyen sûr de convertir des nombres en chaîne. Les paramètres régionaux en_US ajouteront des virgules de regroupement. "1234.56" devient "1,234.56". Ajoutez une chaîne vide au nombre à convertir. $ number. = ''; – Schien

1

Pour moi, je trouve 3 façons:

  1. converti flotteur entier après flotteur var est multiplié à un grand nombre (pour exemple 1 000 000); ce n'est pas un moyen très pratique car vous ne devriez pas oublier de diviser par le même 1 000 000 quand il est utilisé
  2. pour utiliser preg_replace('/d:([0-9]+(\.[0-9]+)?([Ee][+-]?[0-9]+)?);/e', "'d:'.((float)$1).';'", $value); où $ value est votre float; trouvé here
  3. également, pour arrondir le flottant par round() et le stocker dans le tableau sous forme de chaîne.

En tout cas, j'utilise variante # 2

1

casting également fonctionne, et il est plus rapide, Exemple:

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

(57) « d: 0.6310000000000000053290705182007513940334320068359375; "

var_dump($c); 

chaîne (12) "s: 5:" 0,631 ";"

var_dump(unserialize($b)); 

flotteur (0,631)

var_dump(unserialize($c)); 
chaîne

(5) "0,631"

L'important est de le jeter en arrière sur unserialize:

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

flotteur (0,631

1

Réduisez simplement la précision n:

ini_set('serialize_precision',2); 
0

fichier php.ini contient une directive serialize_precision, qui vous permet de contrôler le nombre de chiffres significatifs seront sérialisés pour votre flotteur. Dans votre cas, mémoriser seulement une décimale de nombres entre 6 et 8 signifie deux chiffres significatifs.

Vous pouvez définir ce paramètre dans le fichier php.ini ou directement dans votre script:

ini_set('serialize_precision', 2); 

Si vous ne se soucient pas du nombre exact de chiffres significatifs, mais prendre soin de ne pas avoir un spaghetti de chiffres résultant du nombre de flotteur moyen sont stockés, vous pouvez également donner un coup à une valeur de -1, qui invoque « l'algorithme d'arrondi spécial », cela risque de faire exactement ce qui est nécessaire:

ini_set('serialize_precision', -1); 

vous pouvez même réinitialiser retour à sa valeur d'origine après votre sérialisation:

$prec = ini_get('serialize_precision'); 
    ini_set('serialize_precision', -1); 

    ... // your serialization here 

    ini_set('serialize_precision', $prec); 
Questions connexes