views:

371

answers:

5

Hi, 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 storage time:

Before storing I'm just outputting the same data to see what it looks like, and this is the result I get

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

As you can see, I'm getting long numbers like 6.20000000000000017763568394002504646778106689453125 instead of what I'd really to like see, just 6.2. This is happening only when I serialize the data, if I just output the array, I do get the floats to one decimal. Here is my 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);
+2  A: 

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);
Gumbo
that is indeed a good solution, but I think I'll stick with the serialization for now, for reasons I wrote in the above comment.thanks for your help though
+3  A: 

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.

Joey
ah, thanks for the explanation, that'll probably go a long way.The reason I would want to keep my serialized array straightforward is because of my fear that a longer serial string will overall increase load on the query server. I would love to learn that I am wrong.thanks to everyone else for the prompt responses...you guys rock! Just to keep things simple though (ie no extra functions), I just might stick with this solution, though Gumbo yours is very elegant as well.
Well, databases are pretty much optimized for the use case of retrieving data. Whether you are reading 200 bytes or 2000 shouldn't make much of a difference unless you do something really load-intense. But you can still optimize when it proves to be a bottleneck.
Joey
A: 

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>';
Peter Bailey
+1  A: 

Store them as strings after using number_format:

$number = number_format($float, 2);
shadowhand
A: 

For me I found 3 ways:

  1. convert float to integer after float var is multiplied to a big number (for example 1,000,000); it's not a very convenient way as you should not forget to divide by the same 1,000,000 it when it's used
  2. to use preg_replace('/d:([0-9]+(\.[0-9]+)?([Ee][+-]?[0-9]+)?);/e', "'d:'.((float)$1).';'", $value); where $value is your float; found here
  3. also, to round the float by round() and store it in array as string.

In any case I use variant #2

gray