2009-07-10 21 views
11

Sto generando 10 float casuali tra 6 e 8 (tutto per una buona ragione) e li scrivo in un database mysql in una forma serializzata. Ma un vezzo sembra emergere al tempo di conservazione:PHP - Serialize floating points

Prima di riporre sto solo emettere gli stessi dati per vedere come appare, e questo è il risultato che ottengo

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

Come si può vedere , Sto ricevendo numeri lunghi come 6.20000000000000017763568394002504646778106689453125 invece di quello che mi piacerebbe vedere, solo 6.2. Questo sta accadendo solo quando serializzo i dati, se ho appena emesso l'array, ottengo i float con un decimale. Ecco il mio codice:

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

Sembra giri eco sé galleggianti, ma questo è strano – usoban

risposta

15

Un numero come 6.2 non può essere rappresentato esattamente usando la matematica a virgola mobile nei computer in quanto non esiste una rappresentazione di base-2 finita. Quello che stai vedendo quando echo -ing il numero è qualcosa inteso per la lettura umana, e quindi il valore sarà arrotondato a quello che i float possono fornire in accuratezza (circa 6 posizioni decimali per 32-bit e 17 per valori FP a 64-bit).

Durante la serializzazione di tali valori, tuttavia, si desidera realmente il valore esatto (cioè tutti i bit presenti) e non solo il valore "più bello" più vicino. Potrebbero esserci più di una rappresentazione float/doppia che valuta circa 6.2 e quando si serializza di solito si desidera memorizzare i valori esatti sull'ultimo bit che si sta avendo per ripristinarli correttamente. Ecco perché stai ottenendo una "precisione" ridicola nei valori. È tutto solo per preservare l'esatta rappresentazione in bit di ciò che hai iniziato.

Ma perché si desidera controllare esattamente l'uscita serializzata? Voglio dire, è solo lì in modo da poter ruotare la tua struttura dati e leggerla di nuovo in seguito. Certamente non vuoi usare quella rappresentazione serializzata da qualche parte in uscita per gli umani o giù di lì. Quindi, se si tratta solo di valori "di bell'aspetto", non si dovrebbe usare la serializzazione che ha uno scopo completamente diverso.

+0

ah, grazie per la spiegazione, che probabilmente andrà un lungo cammino. Il motivo per cui vorrei mantenere il mio array serializzato semplice è perché temo che una stringa seriale più lunga aumenti in generale il carico sul server di query. Mi piacerebbe sapere che ho torto. grazie a tutti gli altri per le risposte tempestive ... ragazzi rock! Solo per mantenere le cose semplici (cioè senza funzioni extra), potrei limitarmi a questa soluzione, anche se Gumbo è molto elegante. –

+0

Bene, i database sono praticamente ottimizzati per il caso d'uso del recupero dei dati. Se stai leggendo 200 byte o 2000 non dovresti fare molta differenza se non fai qualcosa di davvero carico-intenso.Ma puoi ancora ottimizzare quando si rivela un collo di bottiglia. – Joey

+0

Ho riscontrato questo problema durante la memorizzazione di dati serializzati. Stavo archiviando un gran numero di documenti, quindi usando letteralmente 58 cifre per memorizzare quello che avrebbe dovuto essere 4 caratteri mi è sembrato inaccettabile. Ho usato la soluzione number_format fornita da 'shadowhand' qui sotto. –

3

memorizzarli come numeri interi (spostamento alla prima cifra decimale di fronte al punto di moltiplicandolo per 10) e li converte di nuovo in caso di necessità:

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

che è davvero una buona soluzione, ma penso di bastone con la serializzazione, per ora, per motivi ho scritto nel commento di cui sopra. grazie per il vostro aiuto anche se –

1

Ecco la mia risposta alla risposta di Gumbo. Ho messo IteratorAggregate lì, quindi sarebbe foreach-able, ma potresti aggiungere anche Countable e 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

memorizzarli come stringhe dopo l'utilizzo number_format:

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

Questo è quello che ho fatto per "risolvere" questa funzione. –

+0

numero_formato non è un modo sicuro per convertire i numeri in stringa. la locale en_US aggiungerà le virgole di raggruppamento. "1234.56" diventa "1.234.56". Aggiungi una stringa vuota al numero da convertire. . $ Numero = ''; – Schien

1

Per me ho trovato 3 modi:

  1. convertire galleggiante per intero dopo float var viene moltiplicato per un numero grande (per esempio 1.000.000); non è un modo molto conveniente in quanto non dovresti dimenticare di dividerne gli stessi 1.000.000 quando viene utilizzato
  2. per utilizzare preg_replace('/d:([0-9]+(\.[0-9]+)?([Ee][+-]?[0-9]+)?);/e', "'d:'.((float)$1).';'", $value); dove $ value è il tuo float; trovato here
  3. anche, per arrotondare il galleggiante di round() e memorizzarlo in serie come stringa.

In ogni caso io uso variante # 2

1

Casting anche funziona, ed è più veloce, Esempio:

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

string (57) "d: 0,6310000000000000053290705182007513940334320068359375;"

var_dump($c); 

stringa (12) "s: 5:" 0,631 ";"

var_dump(unserialize($b)); 

galleggiante (0,631)

var_dump(unserialize($c)); 

string (5) "0,631"

La cosa importante è quello di gettare di nuovo sul unserialize:

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

galleggiante (0,631)

1

Basta ridurre la precisione n:

ini_set('serialize_precision',2); 
0

di file php.ini contiene una direttiva serialize_precision, che permette di controllare il numero di cifre significative verrà serializzato per il galleggiante. Nel tuo caso, la memorizzazione di un solo decimale di numeri tra 6 e 8 indica due cifre significative.

È possibile impostare questa impostazione nel file php.ini o direttamente nello script:

ini_set('serialize_precision', 2); 

Se non vi interessa circa il numero esatto di cifre significative, ma la cura di non avere uno spaghetti di cifre risultanti dal modo in cui numeri float sono memorizzati, si può anche dare un andare per un valore di -1, che invoca "algoritmo di arrotondamento speciale", questo rischia di fare esattamente ciò che è necessario:

ini_set('serialize_precision', -1); 

è anche possibile ripristinare torna al suo valore originale dopo la serializzazione:

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

    ... // your serialization here 

    ini_set('serialize_precision', $prec);