diff --git a/core/includes/common.inc b/core/includes/common.inc index a4805c0..930f7b0 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2,6 +2,7 @@ use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Json; +use Drupal\Component\Utility\Number; use Drupal\Component\Utility\String; use Drupal\Component\Utility\Tags; use Drupal\Component\Utility\Url; @@ -628,47 +629,24 @@ function valid_url($url, $absolute = FALSE) { /** * Verifies that a number is a multiple of a given step. * - * The implementation assumes it is dealing with IEEE 754 double precision - * floating point numbers that are used by PHP on most systems. + * @see \Drupal\Component\Utility\Number::validStep() * - * This is based on the number/range verification methods of webkit. - * - * @param $value + * @param numeric $value * The value that needs to be checked. - * @param $step + * @param numeric $step * The step scale factor. Must be positive. - * @param $offset + * @param numeric $offset * (optional) An offset, to which the difference must be a multiple of the * given step. * * @return bool * TRUE if no step mismatch has occured, or FALSE otherwise. * - * @see http://opensource.apple.com/source/WebCore/WebCore-1298/html/NumberInputType.cpp + * @deprecated as of Drupal 8.0. Use + * \Drupal\Component\Utility\Number::validStep() directly instead */ function valid_number_step($value, $step, $offset = 0.0) { - $double_value = (double) abs($value - $offset); - - // 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 remainder. - // Since that remainder can't even be represented with a single precision - // float the following computation of the remainder makes no sense and we can - // safely ignore it instead. - if ($double_value / pow(2.0, 53) > $step) { - return TRUE; - } - - // Now compute that remainder of a division by $step. - $remainder = (double) abs($double_value - $step * round($double_value / $step)); - - // $remainder is a double precision floating point number. Remainders that - // can't be represented with single precision floats are acceptable. The - // fractional part of a float has 24 bits. That means remainders smaller than - // $step * 2^-24 are acceptable. - $computed_acceptable_error = (double)($step / pow(2.0, 24)); - - return $computed_acceptable_error >= $remainder || $remainder >= ($step - $computed_acceptable_error); + return Number::validStep($value, $step, $offset); } /** diff --git a/core/includes/form.inc b/core/includes/form.inc index 5abf669..c7aff5b 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -7,6 +7,7 @@ use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\NestedArray; +use Drupal\Component\Utility\Number; use Drupal\Core\Form\FormInterface; use Drupal\Core\Form\BaseFormIdInterface; use Drupal\Core\Database\Database; @@ -4364,7 +4365,7 @@ function form_validate_number(&$element, &$form_state) { // #min is set). $offset = isset($element['#min']) ? $element['#min'] : 0.0; - if (!valid_number_step($value, $element['#step'], $offset)) { + if (!Number::validStep($value, $element['#step'], $offset)) { form_error($element, t('%name is not a valid number.', array('%name' => $name))); } } diff --git a/core/lib/Drupal/Component/Utility/Number.php b/core/lib/Drupal/Component/Utility/Number.php new file mode 100644 index 0000000..5df6cf4 --- /dev/null +++ b/core/lib/Drupal/Component/Utility/Number.php @@ -0,0 +1,60 @@ + $step) { + return TRUE; + } + + // Now compute that remainder of a division by $step. + $remainder = (double) abs($double_value - $step * round($double_value / $step)); + + // $remainder is a double precision floating point number. Remainders that + // can't be represented with single precision floats are acceptable. The + // fractional part of a float has 24 bits. That means remainders smaller than + // $step * 2^-24 are acceptable. + $computed_acceptable_error = (double)($step / pow(2.0, 24)); + + return $computed_acceptable_error >= $remainder || $remainder >= ($step - $computed_acceptable_error); + } + +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/ValidNumberStepUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/ValidNumberStepUnitTest.php deleted file mode 100644 index 2d5d314..0000000 --- a/core/modules/system/lib/Drupal/system/Tests/Common/ValidNumberStepUnitTest.php +++ /dev/null @@ -1,77 +0,0 @@ - 'Number step validation', - 'description' => 'Tests number step validation by valid_number_step()', - 'group' => 'Common', - ); - } - - /** - * Tests valid_number_step() without offset. - */ - function testNumberStep() { - // Value and step equal. - $this->assertTrue(valid_number_step(10.3, 10.3)); - - // Valid integer steps. - $this->assertTrue(valid_number_step(42, 21)); - $this->assertTrue(valid_number_step(42, 3)); - - // Valid float steps. - $this->assertTrue(valid_number_step(42, 10.5)); - $this->assertTrue(valid_number_step(1, 1/3)); - $this->assertTrue(valid_number_step(-100, 100/7)); - $this->assertTrue(valid_number_step(1000, -10)); - - // Valid and very small float steps. - $this->assertTrue(valid_number_step(1000.12345, 1e-10)); - $this->assertTrue(valid_number_step(3.9999999999999, 1e-13)); - - // Invalid integer steps. - $this->assertFalse(valid_number_step(100, 30)); - $this->assertFalse(valid_number_step(-10, 4)); - - // Invalid float steps. - $this->assertFalse(valid_number_step(6, 5/7)); - $this->assertFalse(valid_number_step(10.3, 10.25)); - - // Step mismatches very close to beeing valid. - $this->assertFalse(valid_number_step(70 + 9e-7, 10 + 9e-7)); - $this->assertFalse(valid_number_step(1936.5, 3e-8)); - } - - /** - * Tests valid_number_step() with offset. - */ - function testNumberStepOffset() { - // Try obvious fits. - $this->assertTrue(valid_number_step(11.3, 10.3, 1)); - $this->assertTrue(valid_number_step(100, 10, 50)); - $this->assertTrue(valid_number_step(-100, 90/7, -10)); - $this->assertTrue(valid_number_step(2/7 + 5/9, 1/7, 5/9)); - - // Ensure a small offset is still invalid. - $this->assertFalse(valid_number_step(10.3, 10.3, 0.0001)); - $this->assertFalse(valid_number_step(1/5, 1/7, 1/11)); - - // Try negative values and offsets. - $this->assertFalse(valid_number_step(1000, 10, -5)); - $this->assertFalse(valid_number_step(-10, 4, 0)); - $this->assertFalse(valid_number_step(-10, 4, -4)); - } -} diff --git a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php new file mode 100644 index 0000000..5daace5 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php @@ -0,0 +1,126 @@ + 'Number step validation', + 'description' => 'Tests number step validation by valid_number_step()', + 'group' => 'Common', + ); + } + + /** + * Tests Number::validStep() without offset. + * + * @param numeric $value + * The value argument for Number::validStep(). + * @param numeric $step + * The step argument for Number::validStep(). + * @param boolean $expected + * Expected return value from Number::validStep(). + * + * @dataProvider providerTestValidStep + */ + public function testValidStep($value, $step, $expected) { + $return = Number::validStep($value, $step); + $this->assertEquals($return, $expected); + } + + /** + * Tests valid_number_step() with offset. + * + * @param numeric $value + * The value argument for Number::validStep(). + * @param numeric $step + * The step argument for Number::validStep(). + * @param numeric $offset + * The offset argument for Number::validStep(). + * @param boolean $expected + * Expected return value from Number::validStep(). + * + * @dataProvider providerTestValidStepOffset + */ + public function testValidStepOffset($value, $step, $offset, $expected) { + $return = Number::validStep($value, $step, $offset); + $this->assertEquals($return, $expected); + } + + /** + * Provides data for self::testNumberStep(). + * + * @see \Drupal\Tests\Component\Utility\Number::testValidStep + */ + public static function providerTestValidStep() { + return array( + // Value and step equal. + array(10.3, 10.3, TRUE), + + // Valid integer steps. + array(42, 21, TRUE), + array(42, 3, TRUE), + + // Valid float steps. + array(42, 10.5, TRUE), + array(1, 1/3, TRUE), + array(-100, 100/7, TRUE), + array(1000, -10, TRUE), + + // Valid and very small float steps. + array(1000.12345, 1e-10, TRUE), + array(3.9999999999999, 1e-13, TRUE), + + // Invalid integer steps. + array(100, 30, FALSE), + array(-10, 4, FALSE), + + // Invalid float steps. + array(6, 5/7, FALSE), + array(10.3, 10.25, FALSE), + + // Step mismatches very close to beeing valid. + array(70 + 9e-7, 10 + 9e-7, FALSE), + array(1936.5, 3e-8, FALSE), + ); + } + + /** + * Data provider for \Drupal\Test\Component\Utility\NumberTest::testValidStepOffset(). + * + * @see \Drupal\Test\Component\Utility\NumberTest::testValidStepOffset() + */ + public static function providerTestValidStepOffset() { + return array( + // Try obvious fits. + array(11.3, 10.3, 1, TRUE), + array(100, 10, 50, TRUE), + array(-100, 90/7, -10, TRUE), + array(2/7 + 5/9, 1/7, 5/9, TRUE), + + // Ensure a small offset is still invalid. + array(10.3, 10.3, 0.0001, FALSE), + array(1/5, 1/7, 1/11, FALSE), + + // Try negative values and offsets. + array(1000, 10, -5, FALSE), + array(-10, 4, 0, FALSE), + array(-10, 4, -4, FALSE), + ); + } + +}