Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Liam Morland’s picture

Liam Morland’s picture

Status: Active » Needs review
FileSize
1.83 KB

This patch takes the lead from the SQL standard and returns NULL if the inputs are not numeric. Since NULL casts to FALSE, this will do what people expect in a Boolean context.

This patch needs to be applied after the patch in #2307631: *_not_* functions should be wrappers.

Liam Morland’s picture

Title: Fix string handling in numerica comparisons » Fix string handling in numerical comparisons
DanChadwick’s picture

You are flipping the result for not_equal empty. '' != '0' should return TRUE, as it does today.

I'm willing to take these changes to the comparison functions piecemeal, but I see a number of related things that I don't like. I want the result of the patches to be cohesive and consistent.

1) Redundant code (partly addressed in #2307631: *_not_* functions should be wrappers)
2) Inconsistent handling of empty for < and > with empty strings.
3) Inconsistent handling of "nearly equal" (withing epsilon of 0.000001) for > and <. BTW, really epsilon should be calculated by looking at the larger of the exponents of the operands and substracting 6 or whatever. If webform were used for scientific use, that would be relevant. Not sure it's worth the work.
4) And as you just pointed out, inconsistent treatment of invalidly formatted numbers, which also relates somewhat to #2290029: Thousands separator creates invalid value for HTML5 Number input.

The motivation for fixing 1) is gone, now that the select_or_other patch normalizes the input values before the comparison routines. But 2) thru 4) seem like related bugs to me.

DanChadwick’s picture

Status: Needs review » Needs work

I appreciate the work, Liam. If you disagree with my assessment, I'm willing to take some of this on. I feel like the car needed an oil change and I just found oil dripping out of the oil pan gasket.

Liam Morland’s picture

I feel a little like I've gone down the rabbit hole. It was when I was working on #2307619: Warning: strcasecmp() expects parameter 1 to be string, array given in webform_conditional_operator_string_equal() that I noticed that webform_conditional_operator_string_not_equal() could be a wrapper for webform_conditional_operator_string_equal(), since the code is the inverse. Now we're on to numeric comparison functions.

I don't have any strong feelings about how these functions should work. It seems to me that it shouldn't be so much special handling for empty string as special handling for any string. That is why I made them return NULL for non-numeric input.

I don't think I understand what you want the output to be in cases of string input. As the patch is, string input results in NULL output, much like SQL.

In the changes to webform_conditional_operator_numeric_not_equal() in the patch in #2 where not made, then that function would return TRUE for string input: The string would cause webform_conditional_operator_numeric_equal() to return NULL, which would become TRUE when inverted.

DanChadwick’s picture

I agree about the rabbit hole, but when you see a bug and are fixing things in the same area, it is never easier than to fix them too. :( Feel free to bail out if you want.

This is the part that I'm bitching about:

 function webform_conditional_operator_numeric_not_equal($input_values, $rule_value) {
+  if (!is_numeric($input_values[0])) {
+    return NULL;
+  }
   return !webform_conditional_operator_numeric_equal($input_values, $rule_value)
 }

This makes not_equal return a falsy value (NULL) when it should return TRUE for an empty operand.

I was thinking that we should make a helper like strcasecmp:
Returns < 0 if str1 is less than str2; > 0 if str1 is greater than str2, and 0 if they are equal, but also FALSE if the operand isn't numeric. It would respect epsilon.

So given a helper function h():

EQUAL :== h() === 0
NOT_EQUAL :== h() !== 0 (or just !EQUAL)
LT :== h() < 0
GT :== H() > 0

I think this covers all the empty/non_numeric cases, and epsilon, plus the trickiness in centralized in one function, which is especially helpful if we do a better job with calculating epsilon.

Liam Morland’s picture

Status: Needs work » Needs review
FileSize
2.64 KB

Still room for epsilon calculation improvements.

DanChadwick’s picture

Thanks for the excellent work. I already committed the parent issue, so I think there is a hunk here that needs rejecting. If you don't want to re-fetch and merge, let me know and I can take it from here.

I read up on epsilon (error) for floating point equality testing and what he have isn't great (as I knew) and what I found of stackexchange wasn't great either. Here's a good write-up. Much, much harder than it would seem to be:
http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

I think for relative error, 0.000001 (which is what we were using as absolute error) is about right.
I have yet to find how to determine the min absolute error in PHP. :(

Liam Morland’s picture

No worries, here is the re-roll.

Improving epsilon could be a separate issue.

The last submitted patch, 2: webform-numeric_comparisons-2311897-2.patch, failed testing.

The last submitted patch, 8: webform-numeric_comparisons-2311897-8.patch, failed testing.

DanChadwick’s picture

Status: Needs review » Fixed

Committed to 7.x-4.x and 8.x, with some additional block comment about epsilon.

After significant research, I determined that it would be quite difficult to do a better job. The current implementation does a good job with numbers on the order of magnitude of 1. Very small numbers (e.g. 1E-15) or very big numbers (e.g. 1E16) will not work properly. I believe these are outside the use case for webform conditionals.

I would have implemented something, but the constants that are used to determine the appropriate error thresholds are not available in PHP. While double precision IEEE floating point is standard, that is not to say that some servers might use single precision. In addition, in my quick testing, I found unexpected results.

It should be noted that floating point comparisons will always work if the numbers are imput in the same way. The issue comes when the numbers are calculated (using an add-on webform module of some sort). In this case, we are tying to ensure that 1.0 is equal to 0.7 + 0.3. So long as the difference between the two numbers is within 0.000001, webform will return the proper result.

Thank you Liam for your long suffering work on this issue.

Liam Morland’s picture

Status: Fixed » Needs review
FileSize
1.19 KB

Thanks very much. Here is one small improvement I was working on just as you were committing. Instead of casting the input numbers to float, this version uses "+ 0", which has the effect of casting to either int or float, as needed.

DanChadwick’s picture

Status: Needs review » Fixed

I'm not sure I agree that this is an improvement. Yes, in most cases it will save a tiny bit of execution time, but conditions are never a significant performance issue. There would be no functional difference, and I think the explicit cast is clearer and "less tricky", even with the comment. There will be a floating point comparison anyhow with $epsilon, so it's just a subtraction and absolute value, both of which will be fast with today's floating point hardware.

Feel free to re-open if you disagree.

Liam Morland’s picture

OK, I just thought if someone is sending in ints anyway that we might as well keep them that way. I wish PHP supported casting to "(number)".

If we did want to do it, it should be like this:

$number_1 += 0;
$number_2 += 0;

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.