diff --git a/core/lib/Drupal/Component/Utility/Number.php b/core/lib/Drupal/Component/Utility/Number.php
index 39143d9..a48b784 100644
--- a/core/lib/Drupal/Component/Utility/Number.php
+++ b/core/lib/Drupal/Component/Utility/Number.php
@@ -17,9 +17,16 @@ class Number {
    *
    * This is based on the number/range verification methods of webkit.
    *
-   * @param float $value
+   * Besides integers and floating numbers, we also support decimal numbers
+   * which are not stored in IEEE 754 format. In somewhat higher precisions for
+   * these numbers, the $step value cannot accurately represent the desired
+   * precision, when it is passed as a float. Passing it as a string bypasses
+   * this loss of precision and enables a correct calculation of the step
+   * validity.
+   *
+   * @param float|string $value
    *   The value that needs to be checked.
-   * @param float $step
+   * @param float|string $step
    *   The step scale factor. Must be positive.
    * @param float $offset
    *   (optional) An offset, to which the difference must be a multiple of the
@@ -33,6 +40,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
@@ -43,8 +64,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/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php
index b4fac53..090ba1d 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php
@@ -73,6 +73,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       '#type' => 'number',
       '#default_value' => $value,
       '#placeholder' => $this->getSetting('placeholder'),
+      '#number_type' => $this->fieldDefinition->getType(),
     ];
 
     // Set the step for floating point and decimal numbers.
diff --git a/core/lib/Drupal/Core/Render/Element/Number.php b/core/lib/Drupal/Core/Render/Element/Number.php
index b7b2524..7733207 100644
--- a/core/lib/Drupal/Core/Render/Element/Number.php
+++ b/core/lib/Drupal/Core/Render/Element/Number.php
@@ -88,8 +88,14 @@ public static function validateNumber(&$element, FormStateInterface $form_state,
       // Check that the input is an allowed multiple of #step (offset by #min if
       // #min is set).
       $offset = isset($element['#min']) ? $element['#min'] : 0.0;
+      $step = $element['#step'];
+      if (isset($element['#number_type']) && ($element['#number_type'] == 'decimal')) {
+        // PHP mangles the precision of floating-point arguments, so convert
+        // the step to string for non-floating-point numbers.
+        $step = (string) $element['#step'];
+      }
 
-      if (!NumberUtility::validStep($value, $element['#step'], $offset)) {
+      if (!NumberUtility::validStep($value, $step, $offset)) {
         $form_state->setError($element, t('%name is not a valid number.', ['%name' => $name]));
       }
     }
diff --git a/core/modules/field/src/Tests/Number/NumberFieldTest.php b/core/modules/field/src/Tests/Number/NumberFieldTest.php
index c691410..0f548ad 100644
--- a/core/modules/field/src/Tests/Number/NumberFieldTest.php
+++ b/core/modules/field/src/Tests/Number/NumberFieldTest.php
@@ -41,13 +41,15 @@ protected function setUp() {
   public function testNumberDecimalField() {
     // Create a field with settings to validate.
     $field_name = Unicode::strtolower($this->randomMachineName());
+    // Having precision = 10 and scale = 4 means 6 numbers before the decimal
+    // point and 4 after.
     FieldStorageConfig::create([
       'field_name' => $field_name,
       'entity_type' => 'entity_test',
       'type' => 'decimal',
       'settings' => [
-        'precision' => 8, 'scale' => 4,
-      ]
+        'precision' => 10, 'scale' => 4,
+      ],
     ])->save();
     FieldConfig::create([
       'field_name' => $field_name,
@@ -74,16 +76,29 @@ public function testNumberDecimalField() {
     $this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
     $this->assertRaw('placeholder="0.00"');
 
-    // Submit a signed decimal value within the allowed precision and scale.
-    $value = '-1234.5678';
-    $edit = [
-      "{$field_name}[0][value]" => $value,
+    // Submit a few signed decimal value within the allowed precision and scale.
+    $valid_entries = [
+      '-1234.5678',
+      '19999.0000',
+      '99999.0000',
+      '909888.96',
+      '909888.99',
+      '988999.0096',
+      '988999.0099',
     ];
-    $this->drupalPostForm(NULL, $edit, t('Save'));
-    preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
-    $id = $match[1];
-    $this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
-    $this->assertRaw($value, 'Value is displayed.');
+
+    foreach ($valid_entries as $valid_entry) {
+      $this->drupalGet('entity_test/add');
+      $edit = [
+        "{$field_name}[0][value]" => $valid_entry,
+      ];
+      $this->drupalPostForm(NULL, $edit, t('Save'));
+      preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
+      $id = $match[1];
+      $this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
+      $this->assertRaw($valid_entry, t('Value @val is displayed.', ['@val' => $valid_entry]));
+      $this->assertNoRaw(t('%name is not a valid number.', ['%name' => $field_name]), 'Values are accepted');
+    }
 
     // Try to create entries with more than one decimal separator; assert fail.
     $wrong_entries = [
diff --git a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
index 1fc05d3..9e9f36d 100644
--- a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
@@ -75,6 +75,7 @@ public static function providerTestValidStep() {
       [1000, -10, TRUE],
 
       // Valid and very small float steps.
+      [1936.5, 3e-8, TRUE],
       [1000.12345, 1e-10, TRUE],
       [3.9999999999999, 1e-13, TRUE],
 
@@ -88,7 +89,28 @@ public static function providerTestValidStep() {
 
       // Step mismatches very close to being valid.
       [70 + 9e-7, 10 + 9e-7, FALSE],
-      [1936.5, 3e-8, FALSE],
+
+      // 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],
     ];
   }
 
