2
\$\begingroup\$

Let's say we had a baseline value of 100 and I have a value of X.

If X is 5% or less higher/lower than 100 (95 to 105) then do something. If it's greater than 5% (Less than 95 or more than 105) do something else.

My initial thought was something like the below but it looks quite messy

$diff = 100*0.05;
$lower = 100-$diff;
$higher = 100+$diff;
if ($x >= $lower && $x <= $higher) {
     // Within tolerance
}
if ($X < $lower || $x > $higher) {
     // Outside tolerance
}
\$\endgroup\$
0

2 Answers 2

7
\$\begingroup\$

Apart from replacing the second if with an else, I would define this as a separate function:

function withinTolerance($value, $target, $tolerance) {
    return $value >= $target * (1.0 - $tolerance) && $value <= $target * (1.0 + $tolerance);
}

Then your call can just be:

if (withinTolerance($x, 100, 0.05)) {
    // Within tolerance
} else {
    // Outside tolerance
}

I made everything one line as it's a simple expression, which should clearly indicate what it does, but you can feel free to break $target * (1.0 - $tolerance) to $min, and $target * (1.0 + $tolerance) to $max if you like.

This allows you to change the target and tolerance easily, and also has the added bonus of being reusable, and testable. (How do you go about verifying that your current version works, for example?)

You can define some simple tests:

if (withinTolerance(99, 100, 0.05)) { echo "Pass"; } else { echo "Fail"; }
if (!withinTolerance(10, 100, 0.05)) { echo "Pass"; } else { echo "Fail"; }

And so on.

You could also use a trick with the absolute value (as mentioned in comments on the question), but that takes a lot of the meaning away. This version allows you to easily define upper and lower bounds:

function withinSingleTolerance($value, $target, $tolerance) {
    return withinTolerance($value, $target, $tolerance, $tolerance);
}
function withinTolerance($value, $target, $toleranceBelow, $toleranceAbove) {
    return $value >= $target * (1.0 - $toleranceBelow)
           && $value <= $target * (1.0 + $toleranceAbove);
}
\$\endgroup\$
1
\$\begingroup\$

It appears irrelevant to isolate whether the input number is above or below the tolerable range. By leveraging the abs() function, you can boil this task down to a single "less than or equal to" expression and avoid writing a mostly-duplicated second comparison expression.

For simplicity and re-usability, I recommend a user-defined function with default values for $baseline and $tolerance. Declaring default arguments in the function, will spare you having to repeat the 2nd & 3rd arguments in the majority of your use cases.

I have echo'ed the randomly generated input value and "absolute margin as percent" value to show how the resultant boolean value is generated.

Code: (Demo)

function isAcceptable($value,$baseline=100,$tolerance=.05){
    echo "$value (",abs($baseline-$value)/$baseline,") -> "; // margin value in parentheses
    return abs($baseline-$value)/$baseline<=$tolerance;
}

for($x=0; $x<5; ++$x){
    var_export(isAcceptable(mt_rand(90,110)));
    echo "\n";
}

echo "\n----\n";

for($x=0; $x<5; ++$x){
    var_export(isAcceptable(mt_rand(25,35),30,.07));
    echo "\n";
}

Possible Output:

98 (0.02) -> true
102 (0.02) -> true
94 (0.06) -> false
107 (0.07) -> false
92 (0.08) -> false

----
25 (0.16666666666667) -> false
34 (0.13333333333333) -> false
27 (0.1) -> false
32 (0.066666666666667) -> true
32 (0.066666666666667) -> true
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.