Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP floating point errors with basic Maths [duplicate]

Tags:

php

math

Possible Duplicate:
Why can't decimal numbers be represented exactly in binary?
problem with floating values

$var1 = 1;

for ( $i=0; $i<30; $i++ ) {
  $var1 += 0.1;
  $var2 = floor($var1);
  $var3 = $var1-$var2;
  if ( $var3 == 0.5 ) {
    $var1 = $var2+1;
  }
}

The intention of this loop is to count 1.0, 1.1, 1.2, 1.3, 1.4, and then jump to 2.0, 2.1, 2.2 etc

The problem I'm getting is that the if statement is never true. Also every tenth calculation resolves to some insane scientific answer.

How do I fix this? please help!

Edit: I wrote the question in a bit of a frustrated rush and it was more than one, I see that now.

The first part of the question really was "how can I make this work by-passing this floating point querk" and "why is this querk even happening!"

Thank you for all the great replies and I'm voting the answer as correct that easily answered the core question of "how to make this work".

Using 0.49 instead of 0.5 and > instead of == does it. Crude and not the best code in the world but it does solve the original question. Thank you to everyone for the other responses that I am going to read and follow up on to improve my coding.

Once again, many thanks.

like image 271
Dorjan Avatar asked Jul 06 '12 16:07

Dorjan


5 Answers

This one gives desired results:

$var1 = 1;
for ( $i=0; $i<30; $i++ ) {
    $var1 += 0.1;
    $var2 = (int)($var1);
    $var3 = $var1-$var2;
    if ( $var3 >= 0.45 ) {
        $var1 = $var2+1;
    }
    echo $var1.' '.$var2.' '.$var3.'<br/>';
}
like image 60
Stano Avatar answered Nov 19 '22 16:11

Stano


Computers cannot accurately represent floats, as they're stored in binary (base 2). All floats will have an ever so slight bit of adjustment +/-.

See here: http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

like image 43
Josh Avatar answered Nov 19 '22 15:11

Josh


Binary float point will choose approximate value to represent base-10 float point which it cannot represents. So, it is not accurate to compare float to float (because you cannot determine its behavior).

You need to invent your own base-10 float point. It is not hard to invent if you know which precise you want. In your case, you will need only one-precise digit. Formula for our base-10 integer would be: value / 10 to power of our precise-digit which is 1, hence your formula is = value / 10.

Performing arithmetic operation is as easy as performing normal arithmetic. Just convert normal representation (refers to representation that computer use) to our invented representation. In your case, $var1 += 0.1 would turn to $var1 += 1.

Outputting would be as easy as converting your representation to normal representation. In your case, echo $var1 . '<br>'; would turn to echo $var1 / 10 . '<br>';

$var1 = 10;

for ( $i=0; $i<30; $i++ ) {
  echo $var1 / 10 . '<br>';
  $var1 += 1;
  if ($var1 % 10 == 5) {
    $var1 = $var1+5;
  }
}
like image 3
invisal Avatar answered Nov 19 '22 14:11

invisal


Lots of good information on why this happens and a number of solutions. One of the fundamental design goals of PHP is its loose-typed design and implicit casting. In that spirit I think one of the simplest solutions to this problem is to use string comparison. In your case it's a great solution:

<?php
$var1 = 1;

for ( $i=0; $i<30; $i++ ) {
  $var1 += 0.1;
  $var2 = floor($var1);
  $var3 = $var1 - $var2;
  echo "$var1 | $var2 | $var3 \n";
  if (number_format($var3, 1) == '0.5') {
    echo "It's true\n";
    $var1 = $var2 + 1;
  }
}
like image 2
gview Avatar answered Nov 19 '22 15:11

gview


A proper solution is to use the bcmath package:

$var1 = '1.0'; bcscale(1);

for ( $i = 0; $i < 30; $i++) {
    $var2 = $var1;
    for( $j = 0; $j < 5; $j++) {
        echo $var2 . "\n"; 
        $var2 = bcadd( $var2, '0.1');
    }
    $var1 = bcadd( $var1, '1');
}

This outputs the correct result.

like image 1
nickb Avatar answered Nov 19 '22 15:11

nickb