PHP - Serialize floating points

前端 未结 9 1415
自闭症患者
自闭症患者 2021-01-02 02:19

I am generating 10 random floats between 6 and 8 (all for good reason), and writing them to a mysql database in a serialized form. But one quirk seems to emerge at the stora

相关标签:
9条回答
  • 2021-01-02 02:58

    Just reduce the precision:

    ini_set('serialize_precision',2);
    
    0 讨论(0)
  • 2021-01-02 02:58

    Store them as integers (shift the first decimal place in front of the point by multiplying it by 10) and convert them back if you need it:

    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 讨论(0)
  • 2021-01-02 02:59

    A number like 6.2 can't be represented exactly using floating-point math in computers as there is no finite base-2 representation of it. What you are seeing when echo-ing the number is something intended for human reading, and thus the value will be rounded to what floats can provide in accuracy (about 6 decimal places for 32-bit and 17 for 64-bit FP values).

    When serializing those values, however, you really want the exact value (i. e. all bits that are in there) and not just the nearest "nice" value. There could be more than one float/double representation which evaluates to approximately 6.2 and when serializing you usually really want to store he exact values to the last bit you are having in order to restore them correctly. That's why you're getting ridiculous "accuracy" in values there. It's all just to preserve the exact bit representation of what you started with.

    But why exactly do you want to control the serialized output that tightly? I mean, it's just there so you can round-trip your data structure and read it back in later. You certainly don't want to use that serialized representation somewhere in output for humans or so. So if it's just about "nice-looking" values, you shouldn't use serialize which has an entirely different purpose.

    0 讨论(0)
  • 2021-01-02 03:02

    Store them as strings after using number_format:

    $number = number_format($float, 2);
    
    0 讨论(0)
  • 2021-01-02 03:02

    Here's my take on Gumbo's answer. I put IteratorAggregate on there so it would be foreach-able, but you could add Countable and ArrayAccess also.

    <?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>';
    
    0 讨论(0)
  • 2021-01-02 03:03

    Casting also works, and it is faster, Example:

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

    float(0.631)

    var_dump(unserialize($c));
    

    string(5) "0.631"

    The important thing is to cast it back on unserialize:

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

    float(0.631)

    0 讨论(0)
提交回复
热议问题