-1

I have very complicated calculation involving trigonometry that assign result to $x. When I print $x it will say it is float(-1):

var_dump($x); // this will print float(-1)

When I check if it is lower than -1, it will evaluate to true!

if ($x < -1) {
    echo $x.' is lower than -1';  // it will print "-1 is lower than -1"
}
if (floatval($x) < floatval(-1)) {
    echo floatval($x).' is lower than '.floatval(-1);  // it will print "-1 is lower than -1"
}
if (strval($x) === strval(-1)) {
    echo strval($x).' is equal to '.strval(-1); // it will print "-1 is equal to -1"
}

How is this possible? How can -1 be lower than -1? I'm using PHP 7.4.3 (built: Aug 13 2021 05:39:12) (NTS) from Ubuntu. But same thing happened in hosting.

This is offending function in it's entirety. It dies with saying "-1 is lower than -1":

function Qacos($aAngle) {
    if ($aAngle < -1) {
        die($aAngle.' is lower than -1');
    }
    return 180 * acos($aAngle) / M_PI;
}

function Qsin($aAngle) {
    return sin(M_PI * $aAngle / 180);
}
function Qcos($aAngle) {
    return cos(M_PI * $aAngle / 180);
}

$c = Qsin(7.5937478568555);
$d = Qsin(33.2207);
$e = Qsin(64.373047856856);
$f = Qcos(33.2207);
$g = Qcos(64.373047856856);
  
$x = ($c - $d * $e) / ($f * $g);
var_dump($x);
if ($x < -1) {
    die('x lower than -1');
}
asdjfiasd
  • 1,572
  • 15
  • 28
  • How you define `$x` value? I can't replicate the issue. The code is working as expected with php 7.4.16, 8.0.4. Probably the issue comes from getting `$x` value. – Adrian Kokot Oct 28 '21 at 07:07
  • It's 1700 lines of complicated math. I'll try to extract relevant portion. – asdjfiasd Oct 28 '21 at 07:08
  • Interesting thing about this is that PHP < 8 really does show -1 (instead of the whole(?) floating point value) when dumping the value of $x. As seen here - https://3v4l.org/JfaOW But PHP 8+ shows more decimal places, that would have made the problem more obvious. https://stackoverflow.com/questions/3148937/compare-floats-in-php – Hendrik Oct 28 '21 at 07:34
  • It looks like you are computing (sin(A)−sin(B)sin(C)) / (cos(B)cos(C)) for some angles A, B, and C. That expression is not always in the domain of arccosine ([−1, +1]). Why do you expect it to be in the domain? Is there some relationship between A, B and C that would constrain the expression to that interval? I ask, because if so, sometimes there are ways to arrange the floating-point computations so that results outside the interval are not produced. That might not be the case, but you should show the relationships. If they are suitable, you could post a new question about that. – Eric Postpischil Oct 28 '21 at 12:03
  • @deceze: This question is not a duplicate of [that](https://stackoverflow.com/questions/4915462/how-should-i-do-floating-point-comparison). OP’s issue is not that they want to do a floating-point comparison that tolerates rounding errors. They have two issues: (1) They were unable to observe the computed result was less than −1, due to insufficient precision in the output. (2) The computed result was outside the domain of `acos`. Using the method of “comparing with a tolerance” will not solve the latter problem and does not explain the former… – Eric Postpischil Oct 28 '21 at 12:07
  • … To avoid calling `acos` with a value outside its domain, the test `$aAngle < -1` must be used; merely testing for proximity to −1 is not correct. – Eric Postpischil Oct 28 '21 at 12:07
  • By the way, if computed exactly from the decimal values you show or those values converted to IEEE-754 double precision binary format, `x` would be about −1.00000000032781… So the fact that it is below −1 is not a result of floating-point rounding errors in the code you show, and it is good that `Qacos` reports it is outside the domain, because it is. – Eric Postpischil Oct 29 '21 at 00:28

1 Answers1

2

It may because your printed "-1" is indeed something like "-1.00000000000001" etc, and var_dump just does not show that precision. Indeed, it is usually not a good practice to compare equality for floating point numbers. If you want to check it is almost -1, then do something like $x > -1.001 && $x < -0.999 (code just for example).

ch271828n
  • 15,854
  • 5
  • 53
  • 88
  • 1
    Kludging the floating-point comparison is not the right answer here. First, if OP must use the value in `acos`, the issue is not whether it is “near” −1 but whether it is in the domain of the `acos` function. That domain is [-1, +1] exactly, and the test for whether it is outside the domain on the low side is `aAngle < -1`. If we expect computation of an angle may produce a result less than −1 due to rounding, an acceptable solution may be to test whether it is less than −1 and replace it by −1, not to test whether it is close to −1. – Eric Postpischil Oct 28 '21 at 11:53
  • 1
    Second, correct mathematical calculations of values that will be arguments of an real-number arccosine always produce numbers inside the domain, and a desirable solution could be to structure the floating-point computations so that the rounding errors never produce a result outside the domain. Whether this is feasible depends on circumstances and requires more information from the OP. (Here “correct mathematical calculations” includes not just computation but also the fact that the calculations are for a geometric model in which the calculated value is the cosine of a real angle.) – Eric Postpischil Oct 28 '21 at 11:55