0

I am trying to create a script that cleans a string from anything that is not a number or operator and then performs the calculation.

It works fine if, for example, the sting is How much is 25 + 35 then * 8 and then / 2, but if the string is How much is 25.5 + 35.5 then * 8 and then / 2, the result is wrong, as it does not consider the float in the numbers

I have tried using is_float in the for loop but without success.

here is a demo http://sandbox.onlinephpfunctions.com/code/c06295bbb567b667cfd65ff0d736330fea0e774b

Any idea what can I do to make it calculate them right?

$result = "How much is 25.5 + 35.5";        
$allowed   = array('1','2','3','4','5','6','7', '8', '9','0','-','+','/','*','.');

$regex  = sprintf('/[^%s]/u', preg_quote(join($allowed), '/'));
$result = preg_replace($regex, '', $result);    
$str = preg_replace('/\s+/', '', $result); 

//Create calculation

$number = array();
$z = 0;
for ($i = 0; $i < iconv_strlen($str); $i++) {
    if (is_numeric($str[$i])) {
        $number[$z] .= $str[$i];
    } else {
        $z++;
        $number[$z] = $str[$i];
        $z++;
    }
};

for ($i = 0; $i < count($number); $i++) {
    $number[$i] = (int) $number[$i];
    $i++;
    $number[$i] = (string) $number[$i];
}

$res = $number[0];

for ($i = 0; $i < count($number); $i++) {
    if ($number[$i+1] === '+') {
        $res += $number[$i+2];
    }elseif($number[$i+1] === '-'){
        $res -= $number[$i+2];
    }elseif($number[$i+1] === '*'){
        $res *= $number[$i+2];
    }elseif($number[$i+1] === '/'){
        $res /= $number[$i+2];
    }
    $i++;
}

echo round($res,2);
lStoilov
  • 1,256
  • 3
  • 14
  • 30
  • 1
    You are casting to an int - (int) $number[$i]. floatval($number) would better suit this purpose – DonkeyKong Jan 24 '22 at 20:07
  • @DonkeyKong, using `floatval` gives the same result, if I use it like this `floatval($number[$i]);` – lStoilov Jan 24 '22 at 20:17
  • The `.` in `25.5` is being treated as an operator and not as part of a float number. You'll need to enhance the `is_numeric` check as it is applied to each character in the string. – Computable Jan 24 '22 at 21:34

2 Answers2

1

The issue is you are iterating over every character rather than keeping them together. You have:

Array
(
    [0] => 25
    [1] => .
    [2] => 5
    [3] => +
    [4] => 35
    [5] => .
    [6] => 5
    [7] => 
)

So:

if ($number[$i+1] === '+') {
   $res += $number[$i+2];

is only matched once at index 3 then index 4's value is taken (e.g. 35) for the addition to 25 (index 0). The decimal values are ignored entirely.

I would use an approach like this:

$result = "How much is 25.5 + 35.5";        
$allowed   = array('1','2','3','4','5','6','7', '8', '9','0','-','+','/','*','.');
$regex  = sprintf('/([%s]+)/u', preg_quote(join($allowed), '/'));
preg_match_all($regex, $result, $match);
switch($match[0][1]){
    case '-':
        echo $match[0][0] - $match[0][2];
    break;
    case '*':
        echo $match[0][0] * $match[0][2];
    break;
    case '+':
        echo $match[0][0] + $match[0][2];
    break;
    case '/':
        echo $match[0][0] / $match[0][2];
    break;
}

https://3v4l.org/Ug20p

user3783243
  • 5,368
  • 5
  • 22
  • 41
  • Your solution looks mu more elegant. What if you have more operations in a row... I updated the initial question example `How much is 25.5 + 35.5 then * 8 and then / 2` – lStoilov Jan 24 '22 at 20:47
  • Does a `then` imply parentheses and/or brackets? Getting a bit more advanced here, and might run into pemdas issues. – user3783243 Jan 24 '22 at 20:52
  • Nope. it is anyway stripped as it is not part of the $allowed array. Then is just what operation is following next. I use it In the example, to show that it shouldn't follow mathematically order as @Lenny4 pointed. So, it should read `How much is 25.5 + 35.5 * 8 / 2` – lStoilov Jan 24 '22 at 20:56
  • 1
    Something like https://3v4l.org/hQ8os can probably get your started – user3783243 Jan 25 '22 at 13:13
0

Maybe you should just use eval:

<?php
        //Enter your code here, enjoy!
$result = "How much is 25.5 + 35.5";        
$allowed   = array('1','2','3','4','5','6','7', '8', '9','0','-','+','/','*','.');

$regex  = sprintf('/[^%s]/u', preg_quote(join($allowed), '/'));
$result = preg_replace($regex, '', $result);

eval('$calculation = '.$result.';');
var_dump($calculation);

http://sandbox.onlinephpfunctions.com/code/ecbf77e8bed6c7d7bed93f97bde67ac48f46cff6

Edit

I just found this package: https://github.com/dbojdo/eval-math

Which solve the security problem for eval.

But your user will still have to write (25.5+35.5) * 8 / 2 if you want to perform the addition first.

Lenny4
  • 1,215
  • 1
  • 14
  • 33
  • I have tried using `eval`, but if you complicate the string, for example `How much is 25.5 + 35.5 *8 /2` it will calculate it wrongly as 167, while it should be 244 – lStoilov Jan 24 '22 at 20:13
  • `eval` should never be used. If you need `eval` you're doing something wrong – user3783243 Jan 24 '22 at 20:13
  • @user3783243 why this function has been created so ? – Lenny4 Jan 24 '22 at 20:16
  • @lStoilov 25.5 + 35.5 *8 /2 indeed equal 167.5 | 35.5*8 = 284 | 284 / 2 =142 |142 + 25.5 = 167.5 | this article will show you how to calculate: https://en.wikipedia.org/wiki/Order_of_operations – Lenny4 Jan 24 '22 at 20:19
  • @Lenny4 See https://stackoverflow.com/questions/951373/when-is-eval-evil-in-php – user3783243 Jan 24 '22 at 20:24
  • @user3783243 it's not written that you should never use `eval` it's written that you should be very careful when you use `eval`. Futhermore the accepted and most voted answer say: Sometimes eval() is the only/the right solution. – Lenny4 Jan 24 '22 at 20:26
  • @Lenny4 So this isn't achievable without `eval`? – user3783243 Jan 24 '22 at 20:27
  • @Lenny4, it is, if you consider it pure mathematical order. What if you have this string `How much is 25.5 + 35.5 then multiply by 8 and then divide by 2`. In this case, there is a specific order requested – lStoilov Jan 24 '22 at 20:29
  • 1
    Yes it can be, you just have to see if you want to take time to develop a "safer function" or use eval directly. It depends if the data in `eval` are secure. i don't have the answer to this quesiton, only @lStoilov has it. But my answer does the job and your sentence "eval should never be used. If you need eval you're doing something wrong" is not true – Lenny4 Jan 24 '22 at 20:30
  • @Lenny4 If PHP manual says `Its use thus is discouraged` to me that means `should never be used`. – user3783243 Jan 24 '22 at 20:32
  • @lStoilov you are right, eval will not work if you replace mathematical operator by string operator like "/" -> becoming "divide by". But in your `$allowed` var I didn't find "multiply by" and "divide by". However you can do that : `(25.5+35.5) * 8 / 2` – Lenny4 Jan 24 '22 at 20:34
  • well imagine that it is not divide but like this `How much is 25.5 + 35.5 then * 8 and then / 2` – lStoilov Jan 24 '22 at 20:37
  • 1
    it also say `If you have carefully verified that there is no other option than to use this construct` it's just a question of how much time you want to spend to recreate the eval function for this specific example. Now i won't continue to debate on if eval should never be used or not – Lenny4 Jan 24 '22 at 20:38
  • @Lenny4, I can do that `(25.5+35.5) * 8 / 2`, but the user that is typing it may not, and this is basically the scenario I am trying to catch – lStoilov Jan 24 '22 at 20:39
  • yes well in this scenario the result will be right. If you're user want to perform (25.5+35.5) * 8 / 2 then he must be aware of the order of operations. If it's not the case you can still manually add parenthesis before you pass the function to `eval` – Lenny4 Jan 24 '22 at 20:41