Проблема с умножением и оценкой числа с плавающей запятой - PullRequest
5 голосов
/ 06 февраля 2012

Эта проблема лучше всего выражается в коде:

$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) выводит: float(25000), а при приведении к int выводит: int(24999) - и, таким образом, заключается проблема.

Я действительно не знаю, что с этим делать. Эта проблема возникает при умножении на 100, и хотя есть несколько хитростей, которые можно сделать, чтобы обойти это (например, * 10 * 10), я хотел бы знать, есть ли «реальное» решение этой проблемы.

Спасибо :)

Ответы [ 5 ]

1 голос
/ 11 марта 2012

Всегда хорошая идея использовать ceil (или floor в зависимости от того, что вы хотите) при использовании числа с плавающей запятой как int В вашем случае попробуйте ceil ($ var4) перед сравнением!

1 голос
/ 06 февраля 2012

Это ужасное хакерское решение, и я немного ненавижу себя за него, но это дает ожидаемое поведение:

<?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!';
  }

Приведите их к строкам, и они преобразуются обратно в int / float в зависимости отсравнение.Мне это не нравится, но оно работает.

На самом деле вам нужна математика до н.э. для точной математики с плавающей точкой в ​​PHP.

0 голосов
/ 06 февраля 2012

Проблема в том, что числа с плавающей точкой просто не могут представлять некоторые числа. Поскольку в PHP нет типа «десятичный» (или другой тип с фиксированной запятой), вы в основном можете только решить эти проблемы.

Предполагая, что первое число в вашем примере $var1 = 286.46 обозначает какие-то деньги, вы можете просто конвертировать их в центы сразу после того, как пользователь ввел их (например, путем выделения точки и считывания ее как целого числа) и, таким образом, рассчитать все, используя целое число по математике.

Это не общее решение - и я сомневаюсь, что оно существует (если не использовать числа произвольной точности, которые предоставляют некоторые расширения PHP - но я, который пахнет для меня излишним).

0 голосов
/ 06 февраля 2012

Я думаю, вы могли бы использовать bccomp для сравнения значений с плавающей запятой, но я думаю, что эта функция отсутствует в ядре PHP.

В противном случае я нашел эту функцию здесь , но я не смог проверить ее, чтобы увидеть, работает ли она

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);
    }
  }
}
0 голосов
/ 06 февраля 2012

Это то, что иногда делают поплавки, все из-за того, что поплавки не могут время от времени точно представлять целые числа.

Вместо приведения его к целому числу вы можете округлить число до целочисленного значения и затем привести его к целому числу. (возможно, это не нужно, но PHP не совсем понимает, как такие вещи происходят внутри, и даже если вы знаете, как они происходят прямо сейчас, это может произойти не в будущем.

...