user1192140
user1192140

Reputation: 51

Issue with float multiplication and evaluation

This problem is best expressed in code:

$var1 = 286.46; // user input data
$var2 = 3646; // user input data
$var3 = 25000; // minumum amount allowed
$var4 = ($var1 * 100) - $var2; // = 250000
if ($var4 < $var3) { // if 250000 < 250000
    print 'This returns!';
}

var_dump($var4) outputs: float(25000) and when cast to int, outputs: int(24999) - and thereby lies the problem.

I don't really know what to do about it though. The issue occurs upon multiplication by 100, and while there are little tricks I can do to get around that (such as *10*10) I'd like to know if there's a 'real' solution to this problem.

Thanks :)

Upvotes: 5

Views: 1682

Answers (5)

rru
rru

Reputation: 71

Its always a good idea to use ceil (or floor based on what you want) when using float number as int In your case try ceil($var4) before comparison!

Upvotes: 1

DaveRandom
DaveRandom

Reputation: 88647

This is a horrible hacky solution and I slightly hate myself for it, but this gives the expected behaviour:

<?php

  $var1 = 286.46; // user input data
  $var2 = 3646; // user input data
  $var3 = 25000; // minumum amount allowed
  $var4 = ($var1 * 100) - $var2; // = 250000
  if ((string) $var4 < (string) $var3) { // if 250000 < 250000
      print 'This returns!';
  }

Cast them to strings, and they get converted back to int/float as appropriate for the comparison. I don't like it but it does work.

Really you need BC Math for precise floating point mathematics in PHP.

Upvotes: 1

rwos
rwos

Reputation: 1841

The problem is that floats just cannot represent some numbers. Since PHP doesn't have a "decimal" (or other fixed-point) type, you can basically only hack your way around these problems.

Assuming the first number in your example $var1 = 286.46 denotes some kind of money, you could just convert that to cents directly after the user entered it (e.g. through stripping the point and reading it as an integer) and thus calculate everything using integer math.

That's not a general solution - and I doubt that one exists (short of using arbitrary precision numbers, which some PHP extensions provide - but I that smells like overkill to me).

Upvotes: 0

Nicola Peluchetti
Nicola Peluchetti

Reputation: 76880

I think you could use bccomp for comparing floating point values but i think it's a function that's not in the PHP Core.

Otherwise i found this function here but i couldn't test it to see if it works

function Comp($Num1,$Num2,$Scale=null) {
  // check if they're valid positive numbers, extract the whole numbers and decimals
  if(!preg_match("/^\+?(\d+)(\.\d+)?$/",$Num1,$Tmp1)||
     !preg_match("/^\+?(\d+)(\.\d+)?$/",$Num2,$Tmp2)) return('0');

  // remove leading zeroes from whole numbers
  $Num1=ltrim($Tmp1[1],'0');
  $Num2=ltrim($Tmp2[1],'0');

  // first, we can just check the lengths of the numbers, this can help save processing time
  // if $Num1 is longer than $Num2, return 1.. vice versa with the next step.
  if(strlen($Num1)>strlen($Num2)) return(1);
  else {
    if(strlen($Num1)<strlen($Num2)) return(-1);

    // if the two numbers are of equal length, we check digit-by-digit
    else {

      // remove ending zeroes from decimals and remove point
      $Dec1=isset($Tmp1[2])?rtrim(substr($Tmp1[2],1),'0'):'';
      $Dec2=isset($Tmp2[2])?rtrim(substr($Tmp2[2],1),'0'):'';

      // if the user defined $Scale, then make sure we use that only
      if($Scale!=null) {
        $Dec1=substr($Dec1,0,$Scale);
        $Dec2=substr($Dec2,0,$Scale);
      }

      // calculate the longest length of decimals
      $DLen=max(strlen($Dec1),strlen($Dec2));

      // append the padded decimals onto the end of the whole numbers
      $Num1.=str_pad($Dec1,$DLen,'0');
      $Num2.=str_pad($Dec2,$DLen,'0');

      // check digit-by-digit, if they have a difference, return 1 or -1 (greater/lower than)
      for($i=0;$i<strlen($Num1);$i++) {
        if((int)$Num1{$i}>(int)$Num2{$i}) return(1);
        else
          if((int)$Num1{$i}<(int)$Num2{$i}) return(-1);
      }

      // if the two numbers have no difference (they're the same).. return 0
      return(0);
    }
  }
}

Upvotes: 0

Jasper
Jasper

Reputation: 11908

That's what floats do sometimes, it is all due to how floats are unable to precisely represent integers from time to time.

Instead of casting it to an int, you can round the number to an integer value and then cast it to an int. (possibly that cast unnecessary, but PHP isn't to clear about how such things happen internally, and even if you know how they happen right now, they may not in the future.

Upvotes: 0

Related Questions