diff --git a/core/lib/Drupal/Component/Utility/Number.php b/core/lib/Drupal/Component/Utility/Number.php index 7038017..d45d6ba 100644 --- a/core/lib/Drupal/Component/Utility/Number.php +++ b/core/lib/Drupal/Component/Utility/Number.php @@ -42,6 +42,20 @@ class Number { public static function validStep($value, $step, $offset = 0.0) { $double_value = (double) abs($value - $offset); + // If step is in scientific notation, convert it to decimal. + $step_expanded = rtrim(number_format($step, 13, '.', ''), '0'); + // Desired precision of comparison is one order of magnitude greater than + // the precision of the step. + $desired_precision = strrpos($step_expanded, '.') !== FALSE ? strlen($step_expanded) - strrpos($step_expanded, '.') : 1; + + // If value is of higher precision than desired it isn't divisible by step. + $value_precision = strrpos($double_value, '.') !== FALSE ? strlen($double_value) - strrpos($double_value, '.') - 1 : 0; + if ($value_precision > $desired_precision) { + return FALSE; + } + + $double_value = (double) round($double_value, $desired_precision); + // The fractional part of a double has 53 bits. The greatest number that // could be represented with that is 2^53. If the given value is even bigger // than $step * 2^53, then dividing by $step will result in a very small @@ -52,8 +66,9 @@ public static function validStep($value, $step, $offset = 0.0) { return TRUE; } + $expected = (double) round($step * round($double_value / $step), $desired_precision); // Now compute that remainder of a division by $step. - $remainder = (double) abs($double_value - $step * round($double_value / $step)); + $remainder = (double) abs($double_value - $expected); // $remainder is a double precision floating point number. Remainders that // can't be represented with single precision floats are acceptable. The diff --git a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php index ffe0b9f..9e9f36d 100644 --- a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php @@ -92,10 +92,25 @@ public static function providerTestValidStep() { // These floats are valid, but might trigger FP math idiosyncrasies. // @see https://www.drupal.org/node/2230909 + [3333333, 0.01, TRUE], + [4444444, 0.01, TRUE], [9990009888.96, 0.01, TRUE], [9990009888.99, 0.01, TRUE], [990088999.0096, 0.0001, TRUE], [990088999.0099, 0.0001, TRUE], + [4031239412.52, 0.01, TRUE], + [90000000000.00, 0.01, TRUE], + [9999999999.99, 0.01, TRUE], + [-3.1933172, 0.0000001, TRUE], + [1109.87, 0.01, TRUE], + [70000000, 0.01, TRUE], + [70000000.00, 0.01, TRUE], + [13517282.20, 0.01, TRUE], + [13517282.21, 0.01, TRUE], + + // Precision of the value is higher than that of the step. + [990088999.0099, 0.001, FALSE], + [990088999.0099, 0.01, FALSE], ]; }