PHP floating point errors with basic Maths [duplicate]
Asked Answered
X

7

6

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.

Xyloid answered 6/7, 2012 at 16:57 Comment(3)
Maybe post what the "insane scientific answer" is ???Mouthpiece
possible duplicate of problem with floating values, Understanding floating point numbers in php, etc.pp.Damron
A great explanation of floating point issues: #1089518Vachill
R
5

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/>';
}
Recant answered 6/7, 2012 at 17:12 Comment(1)
I voted this as the correct answer as it, using my code, totally nails a quick and easy solution. I've voted up all the other answers because they deal with WHY. I will revisit this later but alas I don't have much time right now.Xyloid
W
5

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

Werewolf answered 6/7, 2012 at 17:3 Comment(0)
R
5

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/>';
}
Recant answered 6/7, 2012 at 17:12 Comment(1)
I voted this as the correct answer as it, using my code, totally nails a quick and easy solution. I've voted up all the other answers because they deal with WHY. I will revisit this later but alas I don't have much time right now.Xyloid
D
3

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;
  }
}
Detoxify answered 6/7, 2012 at 17:15 Comment(0)
P
2

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;
  }
}
Pernell answered 6/7, 2012 at 17:27 Comment(0)
W
1

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.

Whitnell answered 6/7, 2012 at 17:19 Comment(0)
M
0

The if statement may never execute simply due to floating point arithmetic in general - testing strict equality with floating point values always carries this risk. Why not change the conditional to check the variable lies within a small interval centered at 0.5 to ensure that you catch it???

Mouthpiece answered 6/7, 2012 at 17:5 Comment(0)
A
-1

Keep it simple, stupid and use integer arithmetic.

For example you start at 1.1

Why not:

for ( $i=1; $i<4; $i++ ) {
  for ( $j=0; $j<5; $j++ ) {
    $v = $i + ($j/10);
    # ...
  }
}
Aseptic answered 6/7, 2012 at 17:8 Comment(5)
-1 For "Oh, your car doesn't work? Well, use this motorcycle instead!"Damron
@x3ro, thanks for letting me know why -1 :-) However, the issue here is with decimal comparisons on binary floating point. One perfectly valid solution is avoid the problem altogether: use an algo form which doesn't do such comparisons. I would suggest the analogy: I can't get my car down that narrow lane -- you have a motorcycle; why not use it instead?.Aseptic
I will +1 because there is no reason to -1. There are multiple solutions to one problem. If one solution need a lot of workaround (in this case, inaccuracy of comparing two floats), why not use other solution which is more simple and could be faster.Detoxify
@Aseptic I agree with you that a valid "solution" to a problem might be avoiding it, but to reach the conclusion that you want to avoid the problem you are having, you must have understood it first. If you're answer would have been "You can't go down this lane because it's to narrow and your car is too wide, take this motorcycle instead!" I wouldn't have downvoted. However, your answer was more like "You were stupid for taking a car in the first place. Use motorcycles from now on!" without explaining the "Why?" and without explaining to the OP what he is doing wrong :)Damron
And yet, the accepted answer with multiple upvotes also does not explain why. Another answer with multiple upvotes suggests the same solution in a less elegant way.Moitoso

© 2022 - 2024 — McMap. All rights reserved.