Problem/Motivation

- Create decimal field, set precision to 10 (minimum in the UI and scale to 4
- Saving new node with value 19999.0000 succeeds (precision is 5+4 = 9).
- Saving new node with value 99999.0000 fails (same precision as above).

or

- Create decimal field, set scale to anything over 6 (need 8 to store bitcoin values for example). I used 16 as precission.
- Saving new node with value 20.12345678 fails validation while 0.1234567 succeeds.

This is because Drupal\Component\Utility\Number::validStep() returns false. Detailed investigation on this function (which is only used once in Drupal), reveals that the first argument ($value) is received as a string, but $step and $offset are floats. PHP seems to slightly mangle the $step value on the first case above from 0.0001 to 0.00010000000000000001438. Passing the step parameter as a string in the case of decimal numbers maintains the correct precision, and allows a correct approximation calculation.

Proposed resolution

Bypass weak PHP floating-point handling by passing the step as a string.

Remaining tasks

Merge. Commit. Decimals FTW!

User interface changes

None.

API changes

None.

Data model changes

None.

Possible workaround if you need big decimals

Disable this validation by setting the render element #step to 'any'

i.e.

$element['#step'] = 'any';

In the case of fields, you can do

function MYMODULE_form_FORM_WITH_BIG_DECIMAL_FIELD_alter (array &$form, FormStateInterface $form_state) {
  $form['field_some']['widget'][0]['value']['#step']='any';
}

And if you want to target all decimal fields:

/**
 * Prevents validation of decimal numbers
 * @see https://www.drupal.org/node/2230909
 */
function MYMODULE_field_widget_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
  $field_definition = $context['items']->getFieldDefinition();
  if ($field_definition->getType() == 'decimal') {
    $element['value']['#step'] = 'any';
  }
}
CommentFileSizeAuthor
#430 2230909-simple-decimals-fail-validation-11.3.x-hacky-disable-validation-MR152288.patch1.03 KBanybody
#421 8282-10.6.x.patch13.74 KBberliner
#406 core-simple-decimals-fails-validation-2230909-406.patch17.08 KBmaxilein
#401 core-simple-decimals-fails-validation-2230909-400.patch14.41 KBmaxilein
#393 2230909-383-reroll-d11.3.patch16.01 KBnagy.balint
#390 2230909-390.patch17.41 KBspadxiii
#383 2230909-383.patch16.81 KBsomeshver
#381 interdiff_2230909_379_381.txt5.96 KBchewie
#381 2230909-381.patch16.94 KBchewie
#379 interdiff_309_379.txt1.07 KBkeszthelyi
#379 2230909-379.patch17.29 KBkeszthelyi
#378 core-simple-decimals-fails-validation-2230909-378.patch17.26 KBjoevagyok
#359 core-simple-decimals-fails-validation-2230909-359.patch20.03 KBhoporr
#341 core-simple-decimals-fails-validation-2230909-341.patch19.48 KBdrupalfan2
#325 2230909-325.patch19.95 KBgrevil
#323 2230909-nr-bot.txt89 bytesneeds-review-queue-bot
#321 2230909-321.patch20.03 KBgrevil
#309 reroll_diff_292_309.txt3.15 KBszeidler
#309 2230909-309.patch17.27 KBszeidler
#306 2023-12-16_16h05_09.png29.75 KBmaxilein
#306 2023-12-16_16h02_57.png22.42 KBmaxilein
#306 2023-12-16_16h02_10.png22.62 KBmaxilein
#306 2023-12-16_15h59_53.png33.24 KBmaxilein
#305 interdiff_292-305.txt16.43 KBasad_ahmed
#305 2230909-305.patch2.58 KBasad_ahmed
#292 interdiff_288_292.txt3.6 KBameymudras
#292 2230909-292.patch17.29 KBameymudras
#289 after_patch.jpg43.13 KBgaurav-mathur
#289 before_patch.jpg35.98 KBgaurav-mathur
#288 2230909-288-D10.patch17.36 KBfenstrat
#288 interdiff-2230909-286-288.txt804 bytesfenstrat
#286 2230909-285-D10.patch17.36 KBsmustgrave
#286 interdiff-269-285.txt0 bytessmustgrave
#283 2230909-283.patch17.28 KBpradhumanjain2311
#277 decimal-test-after-patch.png21.75 KBvinaymahale
#277 decimal-test-before-patch.png39.29 KBvinaymahale
#269 2230909-269.patch17.33 KBsmustgrave
#269 interdiff-251-269.txt4.24 KBsmustgrave
#268 interdiff_251-268.txt2.96 KBpooja saraah
#268 2230909-268.patch17.22 KBpooja saraah
#251 interdiff_2230909_245-251.txt1.55 KBandregp
#251 2230909-251.patch17.21 KBandregp
#251 2230909-251-tests-only.patch8.05 KBandregp
#246 interdiff_2230909_234-245.txt11.52 KBandregp
#246 2230909-245-tests-only.patch8.05 KBandregp
#246 2230909-245.patch17.38 KBandregp
#234 drupal-simple-decimal-validation-ver_9.2.7-2230909-234.patch15.85 KBbsztreha
#233 drupal-simple-decimal-validation-ver_9.2.7-2230909-233.patch15.93 KBmaxmendez
#232 drupal-simple-decimal-validation-ver_9.2.5-2230909-232.patch15.93 KBmaxmendez
#230 drupal-simple-decimal-validation-ver_9.2.5-2230909-230.patch23.32 KBbsztreha
#228 drupal-simple-decimal-validation-2230909-228.patch23.54 KBkevin.brocatus
#227 drupal-simple-decimal-validation-2230909-227.patch14.03 KBkevin.brocatus
#223 drupal-simple-decimal-validation-2230909-223.patch24.22 KBmaxmendez
#210 drupal-simple-decimal-validation-2230909-210.patch24.22 KBrafaelaleung
#209 interdiff_208-209.txt585 bytesrafaelaleung
#209 drupal-simple-decimal-validation-update-2230909-209.patch49.5 KBrafaelaleung
#208 drupal-simple-decimal-validation-2230909-208.patch24.47 KBrafaelaleung
#206 2230909-199-203 Combined.patch24.81 KBmaxilein
#203 2230909-199_NumberFieldTest_D8.9.8.patch8.9 KBmaxilein
#199 2230909-199.patch24.61 KBsokru
#199 interdiff-196-199.txt1.13 KBsokru
#196 interdiff-192-196.txt1.39 KBsokru
#196 2230909-196.patch24.54 KBsokru
#192 2230909-192.patch24.54 KBpaulocs
#192 interdiff-183-192.txt4.96 KBpaulocs
#189 2230909-189.patch19.09 KBayushmishra206
#189 interdiff_186-189.txt1.52 KBayushmishra206
#186 interdiff_173_186.txt584 bytesholist
#186 2230909-186-for-896.patch19.15 KBholist
#183 interdiff_181_183.txt6.95 KBanmolgoyal74
#183 2230909-183.patch25.41 KBanmolgoyal74
#181 interdiff_179_181.txt560 bytesholist
#181 2230909-181.patch19.14 KBholist
#179 interdiff-173-178.txt584 bytesholist
#179 2230909-179.patch19.13 KBholist
#173 interdiff_162_173.txt645 bytespavnish
#173 2230909-173.patch19.22 KBpavnish
#162 interdiff_151-162.txt645 bytespavnish
#162 2230909-162.patch19.22 KBpavnish
#158 interdiff_151-158.txt1.18 KBpavnish
#158 2230909-158.patch19.94 KBpavnish
#151 interdiff-147-151.txt843 byteshardik_patel_12
#151 drupal_2230909_151.patch18.8 KBhardik_patel_12
#147 interdiff.txt2.25 KBdrclaw
#147 drupal_2230909_147.patch19.12 KBdrclaw
#138 interdiff_138.txt1.38 KBbaluertl
#138 drupal_2230909_138.patch17.81 KBbaluertl
#131 interdiff.txt873 byteslapek
#131 drupal_2230909_131.patch17.5 KBlapek
#130 drupal_2230909_130.patch17.5 KBlapek
#129 drupal_2230909_129.patch17.61 KBlapek
#125 drupal_2230909_124.patch17.48 KBxano
#125 interdiff.txt2.16 KBxano
#120 2230909-119-interdiff.txt798 bytestacituseu
#120 2230909-120.patch17.93 KBtacituseu
#119 drupal_2230909_119.patch17.69 KBxano
#119 interdiff.txt3.41 KBxano
#118 drupal_2230909_118.patch16.92 KBxano
#118 interdiff.txt4.32 KBxano
#117 increment-2230909-117.txt1.67 KBpwolanin
#117 2230909-117.patch16.16 KBpwolanin
#113 drupal_2230909_113.patch15.3 KBxano
#113 interdiff.txt1.38 KBxano
#112 drupal_2230909_112.patch14.8 KBxano
#112 interdiff.txt4.94 KBxano
#111 drupal_2230909_111.patch14.31 KBxano
#111 interdiff.txt4.24 KBxano
#109 drupal_2230909_109.patch14.18 KBxano
#109 interdiff.txt6.01 KBxano
#102 drupal_2230909_102.patch11.75 KBxano
#102 interdiff.txt4.6 KBxano
#91 drupal_2230909_91.patch10.43 KBxano
#88 drupal_2230909_88.patch91.23 KBxano
#88 interdiff.txt1.33 KBxano
#85 interdiff.txt4.22 KBxano
#85 drupal_2230909_85.patch10.45 KBxano
#78 2230909-78-simple_decimals_fail_to.patch8.5 KBoperinko
#73 2230909-73-simple_decimals_fail_to-test-only.patch5.77 KBtacituseu
#72 2230909-72-simple_decimals_fail_to.patch8.49 KBtacituseu
#72 2230909-69-interdiff.txt1.42 KBtacituseu
#69 2230909-68-interdiff.txt3.03 KBtacituseu
#69 2230909-69-simple_decimals_fail_to.patch8.69 KBtacituseu
#68 2230909-27-interdiff.txt2.17 KBtacituseu
#68 2230909-68-simple_decimals_fail_to.patch6.53 KBtacituseu
#54 2230909-54.patch6.7 KBpwolanin
#37 2230909-validStep-fp-idiosyncracies-validStep-only.patch4.15 KBbc
#34 2230909-validStep-ZFstyle.patch6.8 KBbc
#27 simple_decimals_fail_to-2230909-27.patch7.08 KBjcnventura
#27 interdiff.txt1.85 KBjcnventura
#24 simple_decimals_fail_to-2230909-24.patch6.87 KBjcnventura
#24 interdiff.txt4.37 KBjcnventura
#22 validStep_fp_idiosyncracies-2230909-22-testonly.patch2.87 KBbc
#16 high_scale_decimals-2230909-16.patch4.19 KBjcnventura
#16 high_scale_decimals-2230909-16-testonly.patch1.94 KBjcnventura
#16 interdiff.txt3.2 KBjcnventura
#10 high_scale_decimals-2230909-10.patch4.05 KBjcnventura
#10 high_scale_decimals-2230909-10-testonly.patch2.47 KBjcnventura
#6 decimal-field-precission-validationError-2230909-6.patch725 bytesexlin

Issue fork drupal-2230909

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

djevans’s picture

I've added the following test data to NumberTest::providerTestValidStep() on my local machine:

array(20.12345678, 1e-8, TRUE),

and the test still passes. Similarly, executing

use Drupal\Component\Utility\Number;
dpm (Number::validStep(20.12345678, 1.0E-8, 0.0)? 'Yes' : 'No');

at /devel/php prints 'Yes'.

It looks like Number::validStep() is producing a non-zero remainder when it's called via form_validate_number() as opposed to being called directly, but I'm not sure why this is just yet.

Anonymous’s picture

I can confirm djevans conclusion that the problem lies in the calculation of the remainder.

$remainder = (double) abs($double_value - $step * round($double_value / $step));

For the unit test the remainder simply is 0, but when debugging the actual flow I get -1.4210854715202E-14.

The exact values I found (evaluated as watches) were:

$double_value = 20.12345678
$step * round($double_value / $step) = 20.12345678
$double_value - $step * round($double_value / $step) = -1.4210854715202E-14

As far as I can tell, this issue is caused by the floating point format that makes rounding errors. But I'm really no expert in this.

JensH’s picture

I also can confirm this issue. Having a decimal field with precision: 14 and scale: 2, entering the value "3333333" is vaild, while "4444444" will fail Number::validStep vaildation in form_validate_number() in form.inc.

exlin’s picture

Issue still seems to exist. What is your opinion, should this issues priority be boosted to major?

exlin’s picture

  public static function validStep($value, $step, $offset = 0.0) {
    dsm("value: ". $value);
    dsm("step". $step);
    dsm("offset: ". $offset);
    $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;
    }
    dsm("double-value: ". $double_value);
    dsm("abs: ". abs($double_value));
    // Now compute that remainder of a division by $step.
    $round = (double) $step * round($double_value / $step);
    dsm("round: ". $round);
    $remainder = (double) abs($double_value - $round);

    dsm("remainder: ". $remainder);
    // $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));
    dsm("acceptable-error: ". $computed_acceptable_error);

    return $computed_acceptable_error >= $remainder || $remainder >= ($step - $computed_acceptable_error);
  }

Returns:

value: -20.12345678
step1.0E-8
offset: 0
double-value: 20.12345678
abs: 20.12345678
round: 20.12345678
remainder: 1.0658141036402E-14
acceptable-error: 5.9604644775391E-16

And since remainder is about 9E-15 higher than calculated acceptable error form validation gives the error. Also as validateNumber() seems to get exactly right values the good question is at which point there becomes some "additional invisible decimals" to given value and why $round & $double_value seems to be the same.

exlin’s picture

Passing arguments as a string fixes issue for me. It's not particularly beautiful since we don't know exactly where things goes wrong but it's better than non-working decimal field.

exlin’s picture

Status: Active » Needs review

Updated status

Status: Needs review » Needs work

The last submitted patch, 6: decimal-field-precission-validationError-2230909-6.patch, failed testing.

dawehner’s picture

Issue tags: +Needs tests
jcnventura’s picture

Title: High scale decimals fails to pass validation » Simple decimals fail to pass validation
Priority: Normal » Major
Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs tests +SprintWeekend2016
StatusFileSize
new2.47 KB
new4.05 KB

Raising this to major, as decimals are fundamentally broken at this time. Even simple basic precision/scale values seem to fail for reasonable values.

No interdiff provided, as the patch in #6 was basically a terrible hack (as acknowledged by @exlin when he submitted it), and wasn't used in the fix.

The last submitted patch, 10: high_scale_decimals-2230909-10-testonly.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 10: high_scale_decimals-2230909-10.patch, failed testing.

The last submitted patch, 10: high_scale_decimals-2230909-10.patch, failed testing.

jcnventura’s picture

My bad.. The approach of not calling validStep for non-floats is wrong. Integers can have steps, so we should call validStep to verify them.

HOWEVER, it seems weird to use a function that is clearly aimed at checking floating-point step (and approximation errors), only for non-floating point numbers.. This because validStep is usually not called for float fields since these have step = any (integers have step=1, and decimals have step = 1^-scale).

So the newly added tests are correct, but I'm really having doubts on the usefulness of the current implementation of validStep. It should probably simply be checking if the value-min is a multiple of step.

jcnventura’s picture

Issue summary: View changes
jcnventura’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new3.2 KB
new1.94 KB
new4.19 KB

@exlin was right.. The only way to do this correctly is to cast step to a string.

Detailed investigation on this function (which is only used once in Drupal), reveals that the first argument ($value) is received as a string, but $step and $offset are floats. PHP seems to slightly mangle the $step value on the first case above from 0.0001 to 0.00010000000000000001438. Passing the step parameter as a string in the case of decimal numbers maintains the correct precision, and allows a correct approximation calculation.

The last submitted patch, 16: high_scale_decimals-2230909-16-testonly.patch, failed testing.

extexan’s picture

Status: Needs review » Reviewed & tested by the community

I had the issue described here...

https://www.drupal.org/node/2693467

...which was marked as a duplicate of this one. I applied patch 16 and it fixed my issue.

alexpott’s picture

  /**
   * 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.
   *
   * This is based on the number/range verification methods of webkit.
   *
   * @param float $value
   *   The value that needs to be checked.
   * @param float $step
   *   The step scale factor. Must be positive.
   * @param float $offset
   *   (optional) An offset, to which the difference must be a multiple of the
   *   given step.
   *
   * @return bool
   *   TRUE if no step mismatch has occurred, or FALSE otherwise.
   *
   * @see http://opensource.apple.com/source/WebCore/WebCore-1298/html/NumberInputType.cpp
   */
  public static function validStep($value, $step, $offset = 0.0) {

The code here seems to suggest this documentation is wrong.

I'd also expect some changes to Drupal\Tests\Component\Utility\NumberTest

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

bc’s picture

Here's a failing test for Number::validStep added into the testonly patch from above.

The fix supplied above doesn't help with validStep not working.

jcnventura’s picture

Status: Needs work » Needs review

Let's use #22 as the testonly patch.

jcnventura’s picture

StatusFileSize
new4.37 KB
new6.87 KB

This patch hopefully addresses the documentation request from @alexpott on #19.

It includes @bc's expanded test from #22, and some minor coding style changes.

The last submitted patch, 22: validStep_fp_idiosyncracies-2230909-22-testonly.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 24: simple_decimals_fail_to-2230909-24.patch, failed testing.

jcnventura’s picture

Status: Needs work » Needs review
StatusFileSize
new1.85 KB
new7.08 KB

Something deep is going on here, and we may have to redo this validStep function from scratch.. But for now, let me just save my current work. Some failing tests are commented out, and others are actually configured to report FALSE when they should be returning TRUE.

Status: Needs review » Needs work

The last submitted patch, 27: simple_decimals_fail_to-2230909-27.patch, failed testing.

jcnventura’s picture

Status: Needs work » Needs review

What are you doing testbot?

Status: Needs review » Needs work

The last submitted patch, 27: simple_decimals_fail_to-2230909-27.patch, failed testing.

jcnventura’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 27: simple_decimals_fail_to-2230909-27.patch, failed testing.

bc’s picture

jcventura, the tests that i added should be failing. Once the bug in Number::validStep is fixed, the tests will pass.

I think we should just parse the numbers as strings, rather than dealing with weird float issues. I can rewrite it if there are no objections.

bc’s picture

StatusFileSize
new6.8 KB

Here's a somewhat complete fix for validStep -- i've adapted code from zend framework's Zend\Validator\Step, but tweaked it to work with very small floats.

I removed a couple tests that don't pass -- if anyone knows how to make them pass, please modify :)

I'll work on it more tonight to try & get them to pass.

bc’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 34: 2230909-validStep-ZFstyle.patch, failed testing.

bc’s picture

Status: Needs work » Needs review
StatusFileSize
new4.15 KB

oops, the last patch i created was total trash; here is one that only has changes to validStep & the tests.

i added back the original (8.2.x) potentially useless tests that check for things like 100/7 -- will drupal ever be saving the floating point result of integer division into a field value?

Status: Needs review » Needs work

The last submitted patch, 37: 2230909-validStep-fp-idiosyncracies-validStep-only.patch, failed testing.

NetNerdy’s picture

tested patch #27 in drupal 8.1.8...fail

can't save/create a field decimal value like 9'999'999.99 or above.

is there any other solution?

i need values up to e.g. $ 999'999'999.99

thanks in advance

NetNerdy’s picture

Issue tags: -SprintWeekend2016 +Drupal 8, +number, +fields, +decimal, +decimal number database field

tested patch #27 in drupal 8.1.8...fail

can't save/create a field decimal value like 9'999'999.99 or above.

is there any other solution?

i need values up to e.g. $ 999'999'999.99

thanks in advance

bc’s picture

@NerdyCrowd have you tried my patch from #37? if it fails can you add the tests that should pass to core/tests/Drupal/Tests/Component/Utility/NumberTest.php?

NetNerdy’s picture

tested patch #27 on fresh drupal 8.1.8 localhost php7= pass

@bc...i will test your patch #37 within next days

inversed’s picture

Patch #27 solved a problem I was having on Drupal 8.1.8. I was getting an error with a valid number when submitting a decimal somewhere greater than 3,560,000.00. So this issue doesn't just effect small numbers.

Are there any know down sides to the 8.1.8 patch? One thing in particular I'm seeing is that validation no longer prevents entering a value larger than the DB permits. For example, @NerdyCrowd - I can submit a value as great as 999,999,999.99. I've got my field set to a precision of 12 and a scale of 2. However, I can't submit a value of 12,999,999,999.99 as that gives me an error like this:

Drupal\Core\Entity\EntityStorageException: SQLSTATE[22003]: Numeric value out of range: 1264 Out of range value for column

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

NetNerdy’s picture

@inversed
patch #27 tested again in drupal 8.1.10...
I've got my field set to a precision of 16 and a scale of 2...and the biggest value is 90.000.000.000,00. i need values max. 9.999.999.999,99...so this patch does it for me.

thlor’s picture

Patch #27 fails for me with the following setup:
- Decimal field, precision: 10, scale: 7
- Trying to save this number fails: -3.1933172

The same number saves successfully without the patch.

pianomansam’s picture

How is this coming along? I'm running 8.2.4 and tried #37 to fix entry of large numbers like 4,031,239,412.52 on a 20/2 field. While that worked, small numbers like 1109.87 became invalid. For the time being I'm running #16 as it appears to be working in both of these instances.

bc’s picture

@pianomansam when I add those numbers to `core/tests/Drupal/Tests/Component/Utility/NumberTest.php` they pass with my patch -- i wonder what is causing the validation failure...

array(4031239412.52, 0.01, TRUE),
array(1109.87, 0.01, TRUE),

also i've just noticed that for whatever reason, when i apply my patch in #37 to 8.2.x latest, these fractional test cases fail:

array(1, 1 / 3, TRUE),
array(-100, 100 / 7, TRUE),
array(2 / 7 + 5 / 9, 1 / 7, 5 / 9, TRUE),
bc’s picture

actually i think i know what is causing the validation failure -- the casting issue that jcventura's patch deals with.

maybe the best approach is to combine jcventura's patch in #16 and mine in #37.

jcnventura’s picture

@bc Yes, as long as you're passing the value as a float/double to validStep, you've already lost a lot of precision.. Just pass it as a string like I did in #27, and then you can try to optimize the validStep function.

I know it's awful to pass it as a string, but once you look at the values you get at the start of validStep if you don't do that, you'll also concede defeat and simply pass it a string.. Believe me, I tried hard not to have to do it.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

damondt’s picture

Quick work around in case people are stuck without a solution until this is resolved: https://gist.github.com/ummdorian/eaece93165df0bdd6546b07614e955e4

jwilson3’s picture

Patch in #16 worked for me with the following scenarios, that were failing without this patch:

- Decimal field, precision: 10, scale: 2
- Trying to save this number: 70000000
- Trying to save this number: 70000000.00

pwolanin’s picture

Version: 8.3.x-dev » 8.4.x-dev
Status: Needs work » Needs review
StatusFileSize
new6.7 KB

Here's a straight re-roll of #27 for 8.4.x

The test fail is reproducible but somewhat bogus since it's complaining about a class mentioned in comment.

Status: Needs review » Needs work

The last submitted patch, 54: 2230909-54.patch, failed testing.

perecedero’s picture

Quick work around, do not validate #step

<?php

  function MY_MODULE_form_alter (array &$form, FormStateInterface $form_state) {
    $form['field_your_field']['widget'][0]['value']['#step']='any';
  }

pwolanin’s picture

Not sure if this is a Chrome browser bug, but the with a high precision, using the up/down arrows in the field will sometimes give you a number in scientific notation like 3e-8.

This fails validation even with this patch:

This value is not valid.

The attribute in the HTML is:
step="1.0E-8"

full element:
<input data-drupal-selector="edit-field-chemical-amount-0-value" type="number" id="edit-field-chemical-amount-0-value" name="field_chemical_amount[0][value]" value="3e-8" step="1.0E-8" min="0" placeholder="" class="form-number error" aria-invalid="true" />

andrewhd’s picture

Issue tags: +Baltimore2017, +Triaged for D8 major current state

I've manually tested and confirmed the use cases in the issue summary and #3, #46, #47, #53 work using the patch in #54. The test fail due to a class mentioned in a comment still exists.

cilefen’s picture

I am updating credit for the triage work. Going forward, you should know that we like to see documented triage steps (even if brief). It is the only way to know if the triage has actually been completed. Here are some made-up examples of documented triage steps:

  • I tested the steps to reproduce and they did (or did not) work (so I am tagging it "Needs issue summary update").
  • I searched for duplicate issues but could not find any.
  • I checked the issue summary and it is accurate and up-to-date.
  • Etc...

Thank you!

nor4a’s picture

I'm using #54 patch. For some numbers it does work, but for some not.

13517282.20 - this fails
13517282.21 - this passes

tell me why? :)

The storage is using the default decimal field settings e.g.
precision: 10
scale: 2

hanoii’s picture

I second @Nor4a that those values fails and not as he said. Very odd. I tried looking but the math on validStep is a bit over my head, at least currently :).

hanoii’s picture

Issue summary: View changes

And also second the workaround on #56. I ended up doing the same

hanoii’s picture

Issue summary: View changes
hanoii’s picture

Issue summary: View changes

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

xjm’s picture

Adding a similar issue that @larowlan located.

larowlan’s picture

Issue tags: +Triaged core major

This was discussed on the major triage call by @cottser, @catch, @xjm, @effulgentsia, @cilifen and myself and agreed it was major on the following basis:

  • Interfere with normal site visitors' use of the site - validation errors for regular form submissions
  • Cause a significant admin- or developer-facing bug with no workaround.
tacituseu’s picture

Status: Needs work » Needs review
StatusFileSize
new6.53 KB
new2.17 KB

Re-rolling from #27 as #54 ate the $wrong_entries = [] part of the test, picked a comment from #54 and fix of misclassified test from #37.
This patch will fail.

tacituseu’s picture

@Nor4a:
To see why that is try setting ini_set('precision', 32);, then it turns out:
0.01 is actually 0.010000000000000000`208166817117217
13517282.20 is 13517282.199999999`254941940307617
13517282.21 is 13517282.210000000`894069671630859
after the 17th significant digit (marked with `) illusion falls apart (not enough bits in double).

The culprit is mentioned in #2, inside Number::validStep() there is:
$remainder = (double) abs($double_value - $step * round($double_value / $step));
This is the trouble maker, especially $step * round($double_value / $step)
because by dividing $double_value by $step you effectively increase its order by the precision of the $step so it becomes 1351728220,
which when multiplied by $step is enough orders of magnitude to bring the rounding errors into play,
and after subtraction it makes enough of a difference (0.00000000186264514923095703125) to go over $computed_acceptable_error (0.00000000059604644775390626240770918829542)

13517282.21 passes because appoximate 13517282.21 is more closely divided by approximate 0.01 than approximate 13517282.20 is.

The code all of this is based on moved to https://github.com/WebKit/webkit/blob/master/Source/WebCore/html/StepRange.cpp but hasn't improved much.

By not reaching into the abyss deeper than is necessary it should be possible to mitigate most of the problems, but you can only do so much with doubles, that's why Zend based fmod patch in #37 has its own problems. Added more test cases mentioned in the issue.

This patch should pass.

Edit: Also the comments on the original make me suspect the author might have been a bit confused about the difference between fixed-point and floating-point representations of the floats.

The last submitted patch, 68: 2230909-68-simple_decimals_fail_to.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 69: 2230909-69-simple_decimals_fail_to.patch, failed testing. View results

tacituseu’s picture

Status: Needs work » Needs review
StatusFileSize
new1.42 KB
new8.49 KB

Fixing comment, Drupal\Component should know nothing about Drupal\Core, even comments.

tacituseu’s picture

StatusFileSize
new5.77 KB

Test only, should fail.

Status: Needs review » Needs work

The last submitted patch, 73: 2230909-73-simple_decimals_fail_to-test-only.patch, failed testing. View results

tacituseu’s picture

Status: Needs work » Needs review

Note: Number::validStep() states:
The step scale factor. Must be positive.
but it is tested in NumberTest::providerTestValidStep() with
[1000, -10, TRUE],

bc’s picture

@tacituseu thank you for your effort in consolidating all of the previous discussion & coming up with a really great fix for this issue! even though the project i'd originally been working on this bug has come & gone, it still haunts me from time to time. in addition to being a comprehensive solution for the bug, you explained your thought process, and educated all of us. i really appreciate your work here.

operinko’s picture

I'm taking a look at this.

operinko’s picture

StatusFileSize
new8.5 KB

The patch from #72 does seem to fix the issue, but the patch was no longer applying cleanly to 8.5.x branch. Rerolled with fix for the non-applying chunk.

operinko’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Vienna2017

Since I only rerolled the patch to get it applying cleanly, manual testing showed it fixing the issues at hand and the automatic tests are passing:

RTBC'd

larowlan’s picture

Status: Reviewed & tested by the community » Needs review

Still needs review

borisson_’s picture

Status: Needs review » Needs work
Issue tags: -Drupal 8, -number, -fields, -decimal, -decimal number database field
  1. +++ b/core/modules/field/src/Tests/Number/NumberFieldTest.php
    @@ -72,16 +74,29 @@ public function testNumberDecimalField() {
    +    // Submit a few signed decimal value within the allowed precision and scale.
    +    $valid_entries = [
    ...
    +    foreach ($valid_entries as $valid_entry) {
    

    I wonder why we're not doing this with a dataprovider.

    --
    Oh, because of browser tests, okay - nevermind.

    I still think this looks weird but in the interest of testspeed I understand why this is happening.

  2. +++ b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
    @@ -88,7 +89,28 @@ public static function providerTestValidStep() {
    +      // These floats are valid, but might trigger FP math idiosyncrasies.
    

    Can we rewrite this comment? I understand this sentence up until "valid".

    How about something like this? Not sure if this is even correct.

    These floating numbers are valid, but since floating point numbers are not precise in computing, we are treating these as strings. This is especially so when using small steps.
    
    These testcases test that parsing them as a string resolves that, @see ...
    

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Pascal-’s picture

Status: Needs work » Needs review

Tested on Drupal 8.5.0 and works.

Pascal-’s picture

Status: Needs review » Needs work

Oops ... Didn't notice this was back to needs work, should probably leave it like that as explained in #81

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new10.45 KB
new4.22 KB

This updates the comment @borisson_ pointed out and *should* make it clearer.

Since the precision computation code is one of the most complex pieces, I split if off to its own method. This allows for more dedicated documentation and testing, as well as reusability of this generic code which is easy to get wrong.

borisson_’s picture

Status: Needs review » Needs work

This does make it a lot easier to read, good work @Xano! I really like the additional testcoverage.

I feel really bad for doing this but:

+++ b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
@@ -34,6 +34,52 @@ public function testValidStep($value, $step, $expected) {
+  public function testGetPrecision($expected, $number)
+  {

bracket position is incorrect.

tacituseu’s picture

Also comment from the part mentioned in #81.1

+    // Submit a few signed decimal value within the allowed precision and scale.
+    $valid_entries = [

they're no longer only signed.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new1.33 KB
new91.23 KB

Thanks for the reviews! :)

borisson_’s picture

Status: Needs review » Needs work

I'm pretty sure that patch contains too many things @Xano. It went from 10kb to 91kb. I guess you were also working on something else? The interdiff looks great though.

xano’s picture

Ah, meh :D I make interdiffs on the same branch, but patches between branches. I guess I diffed against the wrong branch. New patch coming soon!

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new10.43 KB

It helps if you rebase before generating diffs, Xano...

borisson_’s picture

Status: Needs review » Reviewed & tested by the community

All the nitpicks that were posted since #80 have been resolved, and this was RTBC before.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1.    * The implementation assumes it is dealing with IEEE 754 double precision
       * floating point numbers that are used by PHP on most systems.
       *
       * This is based on the number/range verification methods of webkit.
       *
       * 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.
    

    These docs contradict themselves. "The implementation assumes it is dealing with IEEE 754 double precision" and "which are not stored in IEEE 754 format".

  2. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,34 @@
    +    // Convert non-strings to strings, for consistent and lossless processing.
    +    if (is_float($number)) {
    +      $number = rtrim(number_format($number, 13, '.', ''), '0');
    +    }
    

    This needs to explain why 13 is chosen here.

  3. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -33,6 +68,19 @@ class Number {
    +    // If the value is of higher precision than desired it isn't divisible by
    +    // step.
    +    $value_precision = static::getPrecision((string) $double_value);
    

    This documentation is not easy to read. I suggest changing it to something like:
    The step is not valid if the value has a higher precision than the desired precision. This is because the value will not be divisible by the step.

  4. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -43,8 +91,9 @@ public static function validStep($value, $step, $offset = 0.0) {
    +    $expected = (double) round($step * round($double_value / $step), $desired_precision);
         // Now compute that remainder of a division by $step.
    

    What is $expected? Also the documentation below the line seems to belong above.

  5. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php
    @@ -73,6 +73,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
    +      '#number_type' => $this->fieldDefinition->getType(),
    
    +++ b/core/lib/Drupal/Core/Render/Element/Number.php
    @@ -88,8 +88,14 @@ public static function validateNumber(&$element, FormStateInterface $form_state,
    +      $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'];
    +      }
    

    As far as I can see this could is not tested.

  6. The amount of type juggling in \Drupal\Component\Utility\Number::validStep() concerns me. It also concerns me that inputs that look the same about from type give different answers. This seems a recipe for head scratching. For example:
          [13, '0.0000000000000'],
          [0, -0.0000000000000],
    

    Perhaps rather than trying to merge all the fix into \Drupal\Component\Utility\Number::validStep we could introduce a new method for decimals aka non IEEE 754 numbers.

xano’s picture

I brushed off my math (read: Wikipedia) skills and dug into this a little more.

This needs to explain why 13 is chosen here.

This looks intended to chop off any insignificant digits, but I don't know where 13 comes from. IEEE 754 double precision floats have 53-bit significands, leading to floor(53 * log(2)) = 15 guaranteed significant decimal digits (Kahan, 1997). I briefly tested this manually against a float with 25 decimals and after 15 decimals rounding errors started to show up after conversions. This leads me to believe 13 must instead be 15.

However, PHP does not guarantee it uses IEEE 754 for floats:

Although it depends on the system, PHP typically uses the IEEE 754 double precision format

(emphasis mine, source)

tacituseu’s picture

Re #93:
2. This is from #34 but is not part of Zend\Validator\Step, it seems to be a 'test passing constant' ;) as Drupal\Tests\Component\Utility\NumberTest::providerTestValidStep() has this:
[3.9999999999999, 1e-13, TRUE],
4. It is the closest evenly divisible by $step number, or a number it would expect to see if it was a valid step

xano’s picture

#34 seems to indicate the 13 comes from zendframework/zend-validator, but it's never been a part of \Zend\Validator\Step.

Also, what's preventing us from adding this Zend package as a dependency?

tacituseu’s picture

@Xano: it has its own/similar problems, so it won't pass.

alemadlei’s picture

I used the patch at #85.

However, saving data (latitude, longitude) is not working for me.

I created a content entity using Drupal Console, I modified and added the following.


  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {

    // ... All other fields here.
    // Latitude and Longitude fields.
    foreach (['latitude' => t('Latitude'), 'longitude' => t('Longitude')] as $field_name => $field_label) {

      $fields[$field_name] = BaseFieldDefinition::create('decimal')
        ->setLabel($field_label)
        ->setDescription(t("Clients @field_label.", ['@field_label' => $field_label]))
        ->setSettings([
          'precision' => 18,
          'scale' => 12,
        ])
        ->setDefaultValue(0.00)
        ->setDisplayOptions('view', [
          'label' => 'above',
          'type' => 'decimal',
          'weight' => -4,
        ])
        ->setDisplayOptions('form', [
          'type' => 'decimal',
          'weight' => -4,
        ])
        ->setDisplayConfigurable('form', TRUE)
        ->setDisplayConfigurable('view', TRUE)
        ->setRequired(TRUE);

    }
    
    return $fields;

  }

During data submission, the form structure I get is:

    [#required] => 1
    [#delta] => 0
    [#weight] => 0
    [#type] => number
    [#default_value] => 0.000000000000
    [#placeholder] => 
    [#number_type] => decimal
    [#step] => 1.0E-12
    [#input] => 1

So, when validStep is executed, the precision I always get is 6.

1.0E-12 represents the precision I want, 12, however


    $desired_precision = static::getPrecision($step) + 1;

Always returns 6, causing an error stating the the number is not valid.

Any ideas on how can I enforce the full representation to be used so that it properly calculates the precision?

alemadlei’s picture

So, I discovered that if I do this change manually during the buildForm() I can get it to work, even without the patch.

  public function buildForm(array $form, FormStateInterface $form_state) {

    // Other code here

    foreach (['latitude', 'longitude'] as $field) {
      $form[$field]['widget'][0]['value']['#step'] = "0.000000000001";
    }
    return $form;
  }
tacituseu’s picture

@alemadlei: 6 is because static::getPrecision() fails to do the expansion if the step is a string

var_dump(is_float('1.0E-12')); // => bool(false)
var_dump(is_float(1.0E-12)); // => bool(true)

and it ends up this way in the form because of:

+        // PHP mangles the precision of floating-point arguments, so convert
+        // the step to string for non-floating-point numbers.
+        $step = (string) $element['#step'];

and

var_dump((string)0.000000000001); // => string(7) "1.0E-12"

Also, 18 digits of precision is too much to ask of a double.

xano’s picture

1.0E-12 represents the precision I want, 12, however

That is becauseyour 1.0E-12 is actually a string, and not a numeric value, and currently the only string formats supported are integer and float values. I don't think adding support for all those cases is a good idea. Instead, this API should take a few simple, predictable input types, and user input parsing should be done elsewhere. However, this API cannot be cleaned up without breaking BC, so I suspect the core committers will see adding this support as the only realistic way forward, but it won't be particularly clean.

xano’s picture

StatusFileSize
new4.6 KB
new11.75 KB

This should clear up a few things :)

anpolimus’s picture

Status: Needs work » Needs review

Expecting the same issue at my project.
I have Decimal field with 12 digits and scale=2.
Patch at the #102 fixes this issue.
Thanks @Xano for this fix.

Code at the patch looks good for me.
Moving to Needs Review

runeasgar’s picture

  1. Testing undesirable behavior using both provided examples.
  2. Creating a decimal field on the article content type, with precision 10, scale 4.
  3. Trying to create a new article with 99999.0000 in that field: fail with message "test1 is not a valid number."
  4. Creating another decimal field on the article content type, with precision 16, scale 8.
  5. Trying to create a new article with 20.12345678 in that field: fail with message "test2 is not a valid number."
  6. Applying patch: $ curl -l https://www.drupal.org/files/issues/2018-04-18/drupal_2230909_102.patch | git apply -v
  7. Applied cleanly.
  8. drush cr
  9. Retesting step 3: success
  10. Retesting step 5: fail with message "test2 is not a valid number."

If the second use case in the description is no longer valid, then this is RTBC. Otherwise, it needs work. I'm not sure, so leaving it as "Needs review".

anpolimus’s picture

Status: Needs review » Needs work

I've found that existed code doesent check properly out of range numbers.
I have Decimal field with 12 digits and 2 for digits after dot.
I'm trying to enter 10 000 000 000 and getting mysql error: Numeric value out of range:
It means that high border validation doesent checked.

tacituseu’s picture

Re #101: The thing with 1.0E-12 is it looks like it is the patch itself that introduces the problem (see #100), and then tries to undo it with number_format(), maybe replacing the casting with number_format() would make it cleaner

Re #102:

-    $double_value = (double) abs($value - $offset);
+    $float_value = (float) abs($value - $offset);

why change in casting ? they might be somewhat synonymous in PHP but it still feels wrong.

Re #104: about the second case, it does feel wrong to fail like that, either:
1. there should be enforced upper limit on precision (15)
2. do as #93.6 proposed and use some arbitrary precision math library for that

xano’s picture

why change in casting ? they might be somewhat synonymous in PHP but it still feels wrong.

They are identical, but "float(ing point number)" is the official name, so the change was made for consistency's and clarity's sake.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new6.01 KB
new14.18 KB

This patch introduces a new method strictly for normalizing different numeric values into the simple string format we use in core. It's also a format used by BCMath, so I documented that as well. Furthermore, it addresses lack of test coverage reported in #93 by reverting the change, because the code normalizes all values under the hood now. Additionally, I fixed some documentation.

After discussion with @alexpott on IRC, we concluded that strings using scientific notation will not be supported. Scientific notation is for use in code only (after all, it is syntax sugar for ordinary floats), and string-based numeric values, such as from form input, will have to remain in an integer or plain float (digits with a decimal period) format.

tacituseu’s picture

Status: Needs review » Needs work

1.

       '#type' => 'number',
       '#title' => t('Scale', [], ['context' => 'decimal places']),
       '#min' => 0,
-      '#max' => 10,
+      '#max' => Number::IEEE_754_GUARANTEED_PRECISION,
       '#default_value' => $settings['scale'],
       '#description' => t('The number of digits to the right of the decimal.'),

If there's to be a limit it should be on $element['precision'] as floating point representation guarantees a specific number of significant decimal digits, and not amount of 'digits to the right of the decimal'.

2.

+  /**
+   * Gets a number's precision.
+   *
+   * @param int|float|string
+   *   The number as a numeric type or a decimal string.
+   *
+   * @return int
+   */
+  public static function getPrecision($number) {

This method name might be a bit confusing with FieldType defining 'precision' as:
'#description' => t('The total number of digits to store in the database, including those to the right of the decimal.'),

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new4.24 KB
new14.31 KB

Thanks! What about this?

xano’s picture

StatusFileSize
new4.94 KB
new14.8 KB

This fixes a typo introduced by the previous patch, and improves some additional variable names, as well as documentation.

xano’s picture

StatusFileSize
new1.38 KB
new15.3 KB

This patch adds test coverage to ensure the minimum number of significant digits is indeed enforced as an upper bound when counting a numbers significant decimals, as well as coverage to ensure this bound is not enforced for float-formatted strings, as well as documentation why.

tacituseu’s picture

1. Can't help much with naming, as a non-native speaker I've no idea what is the common way to refer to it, the least ambiguous to me seems to be 'decimal places'.
Looked around a bit and at least MySQL, SQL Server and BCMath seem to refer to it as scale though.

2,
+ public function testGetPrecision($expected, $number) {
+ public function provideGetPrecision() {
nitpick, but it still references old function name, also 'provider'.

Edit:
3.

+    $actual_significant_decimals = static::countSignificantDecimals((string) $float_value);

Didn't notice this earlier, but casting to string results in scientific notation in some cases (as shown in #100), which will make it not work as intended, will try to add a test case for that later.

pwolanin’s picture

I'm seeing a problem with the value populated into the HTML5 validation attribute which stops a form from submitting. This is using Drupal 8.5.2 and PHP 7.2.

The markup is:

<input data-drupal-selector="edit-amount-0-value" type="number" id="edit-amount-0-value" name="amount[0][value]" value="100.00000000" step="1.0000000000000005E-8" placeholder="" class="form-number">

Note the trailing 5 in the step attribute.

The base field definition is:


    $fields['amount'] = BaseFieldDefinition::create('decimal')
      ->setLabel(t('Threshold Amount'))
      ->setDescription('')
      ->setSetting('precision', '21')
      ->setSetting('scale', '8')
      ->setRequired(FALSE)
      ->addPropertyConstraints('value', ['Range' => ['min' => 0.0]])
      ->setDisplayOptions('view', [
        'label' => 'above',
        'weight' => 5,
      ])
      ->setDisplayOptions('form', [
        'type' => 'string_textfield',
        'weight' => 5,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);
pwolanin’s picture

A quick and dirty fix in \Drupal\Core\Field\Plugin\Field\FieldWidget\NumberWidget::formElement looks roughly like this where the 1st changed line is already part of this patch.

I'm really not sure where in the render flow it's getting mangled, but this is roughly the same solution (handle as string)

--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php
@@ -73,12 +73,13 @@ 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.
     switch ($this->fieldDefinition->getType()) {
       case 'decimal':
-        $element['#step'] = pow(0.1, $field_settings['scale']);
+        $element['#step'] = $field_settings['scale'] > 0 ? str_pad('0.', $field_settings['scale'] + 1, '0') . '1' : '1';
         break;
 
       case 'float':
pwolanin’s picture

StatusFileSize
new16.16 KB
new1.67 KB

Here's a patch with a fix included as above (but using number_format()) plus in \Drupal\Core\Field\Plugin\Field\FieldType\DecimalItem::fieldSettingsForm

Does there need to be added validation for base fields that the scale is not more than Number::IEEE_754_DOUBLE_GUARANTEED_SIGNIFICANT_DECIMALS ?

xano’s picture

StatusFileSize
new4.32 KB
new16.92 KB

@pwolanin Thanks!

Didn't notice this earlier, but casting to string results in scientific notation in some cases (as shown in #100), which will make it not work as intended, will try to add a test case for that later.

@tacituseu Good catch! The attached patch should take care of this. It also converts a few code comments from # to //, because I realized this is not Python... ;-)

xano’s picture

StatusFileSize
new3.41 KB
new17.69 KB

This adds some documentation, and adds the field-level validation @pwolanin brought up in #117.

tacituseu’s picture

StatusFileSize
new17.93 KB
new798 bytes

Added test case for #114.3 and also one for the other way it could go wrong.

Status: Needs review » Needs work

The last submitted patch, 120: 2230909-120.patch, failed testing. View results

tacituseu’s picture

Somehow for #119 only functional tests ran (results), so either I rolled the patch wrong or it introduced new failures, retesting.

xano’s picture

@tacituseu: #118 contains a fix (the preg_match() call) for the bug you tried to expose in #122. It basically checks if the string cast results in a number of decimals within the minimum guaranteed range of significant decimals (1-15). If not (because too much precision or scientific notation), it uses the existing number_format() solution). This handles PHP's unreliable float -> string cast, but also floats that are cast reliably, but have less than 15 significant decimals, and applying our number_format() approach to such numbers would result in the numbers being padded to the right until there are 15 decimal digits, which will automatically result in a false positive precision of 15 digits (due to the padding, even though the original float had perhaps only 3 significant decimal digits).

tacituseu’s picture

@Xano: looks like this part:

+    $constraints[] = $constraint_manager->create('Range', [
+      'min' => 0,
+      'max' => Number::IEEE_754_DOUBLE_GUARANTEED_SIGNIFICANT_DECIMALS,
+    ]);

is responsible for the failures.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new2.16 KB
new17.48 KB

This fixes the test failure by removing the field value constraint I added for unknown reasons (constraints cannot be applied to field settings), and it applies the jargon changes to the tests (thanks to @tacituseu for pointing that out).

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

lapek’s picture

Version: 8.7.x-dev » 8.6.x-dev

The patch in #125 (drupal_2230909_124.patch) fails to apply on Drupal 8.6.0

Status: Needs review » Needs work

The last submitted patch, 125: drupal_2230909_124.patch, failed testing. View results

lapek’s picture

StatusFileSize
new17.61 KB

Reroll for 8.6 and 8.7

lapek’s picture

Version: 8.6.x-dev » 8.7.x-dev
StatusFileSize
new17.5 KB

I jacked up my previous patch

lapek’s picture

StatusFileSize
new17.5 KB
new873 bytes

Attempting this again since this prevents us from editing nodes migrated from D7

lapek’s picture

Status: Needs work » Needs review
psf_’s picture

Status: Needs review » Reviewed & tested by the community

I applied this path and it's ok, for me. I review the test and I think that it's ok.

exlin’s picture

Great to hear that we are making progress, this has been originally reported 4,5 years ago so it has lived long ;)

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. This is looking really good. I read all the docs and they look good. I wondered about the language significant decimals and whether it should be significant decimal digits but I was wrong - significant decimals is used on the web so no change necessary for that.
  2. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,72 @@
    +   * @param $number int|float|string
    +   *   The value to normalize. If this is a string, it must be formatted as an
    +   *   integer or a float.
    

    I think it is important to mention that floats with a higher number of significant decimals than IEEE_754_DOUBLE_GUARANTEED_SIGNIFICANT_DECIMALS will lose the additional precision as it is not guaranteed by PHP.

  3. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,72 @@
    +   * @return string
    +   *   The normalized string containing integers with an optional decimal
    +   *   separator (.).
    

    This reads a bit odd. How about The normalised numeric string.? It can contain a minus sign as well for example.

  4. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,72 @@
    +  public static function normalize($number) {
    

    There is no explicit testing of this method. As we're making it public there should be.

  5. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,72 @@
    +  /**
    +   * Counts a number's significant decimals.
    +   *
    +   * @param int|float|string
    +   *   The number whose decimals to count. If this is a string, it must be
    +   *   formatted as an integer or a float.
    +   *
    +   * @return int
    +   */
    +  public static function countSignificantDecimals($number) {
    

    It feels important here to mention that floats are limited to the precision guaranteed by PHP - ie. 15. Hence you get things like

    Number::countSignificantDecimals(-0.000000009000000900009) // equals 15
    

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

apolitsin’s picture

 

baluertl’s picture

Status: Needs work » Needs review
StatusFileSize
new17.81 KB
new1.38 KB

Today tested, @lapek's patch from #131 applies cleanly on 8.8.x HEAD.

To address @alexpott's observations:

  1. Grepped in the patch file, "significant decimal digits": zero occurrences; "significant decimals": 10 occurrences.
  2. Suggestion appended with a slightly modified wording regarding Grammarly.
  3. The term "numeric" inserted into the sentence in question.
  4. Adding a unit test for this method is still an outstanding task.
  5. Suggestion appended with a slightly modified wording regarding Grammarly.
apolitsin’s picture

Will it ever be fixed?
This bug prevents us from every second project for the sale of real estate, when we try to save prices in rubles

baluertl’s picture

@APolitsin could you please provide a unit test to address this last remaining point from my previous list?

"4. Adding a unit test for this method is still an outstanding task."

AFAICT this seems the last remaining todo from @alexpott's comment #135. So if someone writes a unit test, then it can be reviewed and set to RTBC again.

Greenskin85’s picture

This isn't fixed in core while it exists for 5 years because of a missing test?

krzysztof domański’s picture

Issue tags: -Baltimore2017, -Vienna2017

1. I think we should add a separate issue to work on the method to count significant decimals. New method Number::countSignificantDecimals() does not affect the current HEAD so it can be reviewed and commited faster.

2. I created #3082919: Add to \Drupal\Component\Utility\Number a method to count significant decimals. It has a new test and changes according to #135 that needs review.

temkin’s picture

Just confirming that #138 fixes the issue for us.

krzysztof domański’s picture

Status: Needs review » Postponed

Postponed due to #142. This issue is not trivial and requires more attention during the review. IMO we should finish #3082919: Add to \Drupal\Component\Utility\Number a method to count significant decimals first.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

ivnish’s picture

Thank you! The patch save my time

drclaw’s picture

Status: Postponed » Needs review
StatusFileSize
new19.12 KB
new2.25 KB

Seems like this was close to being accepted. Not sure if splitting off Number countSignificantDecimals() is the best idea considering? Maybe it is, but in the case that it isn't, here is the patch from #138 with the Unit test from #3082919: Add to \Drupal\Component\Utility\Number a method to count significant decimals.

jungle’s picture

+++ b/core/lib/Drupal/Component/Utility/Number.php
@@ -9,6 +9,82 @@
+   * @param int|float|string

Missing parameter name

- @param int|float|string
+ @param $number int|float|string

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

thhafner’s picture

Status: Needs review » Needs work

#138 and #147 patches are failing to apply for 8.9.x and 9.1.x.

hardik_patel_12’s picture

StatusFileSize
new18.8 KB
new843 bytes

Re-roll for 9.1.x-dev.

hardik_patel_12’s picture

Status: Needs work » Needs review
thhafner’s picture

#151 works well for me on 8.8.x, 8.9.x, and 9.1.x

maxilein’s picture

#151 drupal_2230909_151.patch works well for me on 8.8.5 (php 7.4) where I applied the patch without problems and large numbers seem to work at first glance.

BUT:

I have a 20/2 decimal field - not a default 10/2:

When I enter 123456789123456789.12 it is saving without problems but I get: 123456789123460000.00

When I enter 12345678912346.01 I get the old error "is not a valid number."
Same for 1234567891234.01
When I enter 123456789123.01 it gets saved correctly. No error. And so do apparently do all shorter numbers.

So there is still some inconsistency with the patch here.

maxilein’s picture

Status: Needs review » Needs work
maxilein’s picture

Maybe there should be a warning in the interface that remarks the platform's max. of precision on the system (PHP_FLOAT_MAX).

It does not make sense to allow field settings for a larger number than technically possible by the underlying system.

pavnish’s picture

Assigned: Unassigned » pavnish
pavnish’s picture

Assigned: pavnish » Unassigned
Status: Needs work » Needs review
StatusFileSize
new19.94 KB
new1.18 KB

@maxilein can you please check this patch the #154 has been resolved
This is due to the presave in decimal DecimalItem.php and and numberFormat function in core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/DecimalFormatter.php

I don't know why we again change this value in numberFormat while we are saving the correct value in DB.

Status: Needs review » Needs work

The last submitted patch, 158: 2230909-158.patch, failed testing. View results

pavnish’s picture

Assigned: Unassigned » pavnish
pavnish’s picture

Assigned: pavnish » Unassigned

Working on #156
@maxilein this is due to the number_format function.
The bast solution is to add validation in field settings

pavnish’s picture

Status: Needs work » Needs review
StatusFileSize
new19.22 KB
new645 bytes

@maxilein I have changed the #max value 32 to 16 it's working fine.
The max value supported is 99999999999999.99 which i have checked .
It's meet the #156

maxilein’s picture

Hi,
thank you. For testing I created a new field "testdecimal". Selection of a maximum 16 precision upon creation is ok.
So I chose 16 with scale 2.

But when I enter your maximum:
99999999999999.99 ->1 error has been found: testdecimal is not a valid number.
9999999999999.99 ->1 error has been found: testdecimal is not a valid number.
999999999999.99 -> saves ok. (Thats 14 digits 16-2?)

The behaviour is consistent for the new and also already existing fields.
Maybe the max precision value is misleading? Does it include the scale?

pavnish’s picture

@maxilein yes it's including the scale

maxilein’s picture

Then it is wrong. It is missing 2 digits.
You can see that the working example above in #163 has only 14 digits (12+2), when it should have 16(14+2) like the number you tested.

pavnish’s picture

@maxilein it was working fine for me .but no problem I will test again and will attach a video.

pavnish’s picture

Assigned: Unassigned » pavnish
maxilein’s picture

Did your latest patch, patch your patch from #151?
I just used 8.8.6 and applied #162. Maybe thats what I do wrong?

pavnish’s picture

@maxilein i used drupal 9.1 and #162

maxilein’s picture

Ok. I have 8.8.6 on php 7.4 ubuntu 20.04

maxilein’s picture

So then #163 is still a problem. I can only save 14 digits (12+2) - when according to your test it should be 16 (14+2).

It applies to new and existing fields after applying #163

maxilein’s picture

Status: Needs review » Needs work
pavnish’s picture

Status: Needs work » Needs review
StatusFileSize
new19.22 KB
new645 bytes

Hi @maxilein yes you are correct .

This is working fine with 14 digits (12+2) so i have changed the patch accordingly .

maxilein’s picture

Looks good to me!

samiullah’s picture

While testing the new patch when i changed the scale of the decimal field, I got the following exception:

Attempt to update field dec failed: Exception thrown while performing a schema update. SQLSTATE[42000]: Syntax error or access violation: 1427 For float(M,D), double(M,D) or decimal(M,D), M must be >= D (column 'field_dec_value').: CREATE TABLE {node__field_dec} ( `bundle` VARCHAR(128) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT 'The field instance bundle to which this row belongs, used when deleting a field instance', `deleted` TINYINT NOT NULL DEFAULT 0 COMMENT 'A boolean indicating whether this data item has been deleted', `entity_id` INT unsigned NOT NULL COMMENT 'The entity id this data is attached to', `revision_id` INT unsigned NOT NULL COMMENT 'The entity revision id this data is attached to', `langcode` VARCHAR(32) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT 'The language code for this data item.', `delta` INT unsigned NOT NULL COMMENT 'The sequence number for this data item, used for multi-value fields', `field_dec_value` DECIMAL(14, 15) NOT NULL, PRIMARY KEY (`entity_id`, `deleted`, `delta`, `langcode`), INDEX `bundle` (`bundle`), INDEX `revision_id` (`revision_id`) ) ENGINE = InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT 'Data storage for node field field_dec.'; Array ( ) .

pavnish’s picture

Assigned: pavnish » Unassigned

@samiullah Thanks for the checking.
This is due to the max value of scale is 15 and according to the patch the max value of Precision is 14.
The value of scale must be under Precision value.

Thanks
Pavnish

maxilein’s picture

Works for me.

holist’s picture

Status: Needs review » Needs work

Looks to me like everything else has been addressed but the comment clarification in #135.2.

holist’s picture

Status: Needs work » Needs review
StatusFileSize
new19.13 KB
new584 bytes

I changed the comment as proposed in #135, I agree there is no need to further qualify the normalization but it rather can make the docs confusing. Also slight rerolling was needed.

Status: Needs review » Needs work

The last submitted patch, 179: 2230909-179.patch, failed testing. View results

holist’s picture

Status: Needs work » Needs review
StatusFileSize
new19.14 KB
new560 bytes

Added a code style fix, parameter documentation was wrong. I don't really understand what went wrong with the last test run though...

Status: Needs review » Needs work

The last submitted patch, 181: 2230909-181.patch, failed testing. View results

anmolgoyal74’s picture

Status: Needs work » Needs review
StatusFileSize
new25.41 KB
new6.95 KB

Fixed the deprecated assertions.

kris_nikolov’s picture

Status: Needs review » Reviewed & tested by the community

Tests pass and patch looks ok. RTBC!

maxilein’s picture

The patch #183 does not work with D 8.9.6

Failed for NumberFieldTest.php:

--- core/modules/field/tests/src/Functional/Number/NumberFieldTest.php
+++ core/modules/field/tests/src/Functional/Number/NumberFieldTest.php
@@ -76,18 +78,31 @@
     // Display creation form.
     $this->drupalGet('entity_test/add');
     $this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
-    $this->assertRaw('placeholder="0.00"');
+    $this->assertSession()->responseContains('placeholder="0.00"');
 
-    // Submit a signed decimal value within the allowed precision and scale.
-    $value = '-1234.5678';
-    $edit = [
-      "{$field_name}[0][value]" => $value,
+    // Submit decimal values 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->getUrl(), $match);
-    $id = $match[1];
-    $this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
-    $this->assertRaw($value);
+
+    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->getUrl(), $match);
+      $id = $match[1];
+      $this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
+      $this->assertSession()->responseContains($valid_entry);
+      $this->assertSession()->responseNotContains(t('%name is not a valid number.', ['%name' => $field_name]));
+    }
 
     // Try to create entries with more than one decimal separator; assert fail.
     $wrong_entries = [
@@ -104,7 +119,7 @@
         "{$field_name}[0][value]" => $wrong_entry,
       ];
       $this->drupalPostForm(NULL, $edit, t('Save'));
-      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
+      $this->assertSession()->responseContains(t('%name must be a number.', ['%name' => $field_name]));
     }
 
     // Try to create entries with minus sign not in the first position.
@@ -122,7 +137,7 @@
         "{$field_name}[0][value]" => $wrong_entry,
       ];
       $this->drupalPostForm(NULL, $edit, t('Save'));
-      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
+      $this->assertSession()->responseContains(t('%name must be a number.', ['%name' => $field_name]));
     }
   }
 
@@ -191,7 +206,7 @@
     // Display creation form.
     $this->drupalGet('entity_test/add');
     $this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
-    $this->assertRaw('placeholder="4"');
+    $this->assertSession()->responseContains('placeholder="4"');
 
     // Submit a valid integer
     $value = rand($minimum, $maximum);
@@ -209,7 +224,7 @@
       "{$field_name}[0][value]" => $minimum - 1,
     ];
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertRaw(t('%name must be higher than or equal to %minimum.', ['%name' => $field_name, '%minimum' => $minimum]));
+    $this->assertSession()->responseContains(t('%name must be higher than or equal to %minimum.', ['%name' => $field_name, '%minimum' => $minimum]));
 
     // Try to set a decimal value
     $this->drupalGet('entity_test/add');
@@ -217,7 +232,7 @@
       "{$field_name}[0][value]" => 1.5,
     ];
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertRaw(t('%name is not a valid number.', ['%name' => $field_name]));
+    $this->assertSession()->responseContains(t('%name is not a valid number.', ['%name' => $field_name]));
 
     // Try to set a value above the maximum value
     $this->drupalGet('entity_test/add');
@@ -225,7 +240,7 @@
       "{$field_name}[0][value]" => $maximum + 1,
     ];
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertRaw(t('%name must be lower than or equal to %maximum.', ['%name' => $field_name, '%maximum' => $maximum]));
+    $this->assertSession()->responseContains(t('%name must be lower than or equal to %maximum.', ['%name' => $field_name, '%maximum' => $maximum]));
 
     // Try to set a wrong integer value.
     $this->drupalGet('entity_test/add');
@@ -233,7 +248,7 @@
       "{$field_name}[0][value]" => '20-40',
     ];
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
+    $this->assertSession()->responseContains(t('%name must be a number.', ['%name' => $field_name]));
 
     // Test with valid entries.
     $valid_entries = [
@@ -251,7 +266,7 @@
       preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
       $id = $match[1];
       $this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
-      $this->assertRaw($valid_entry);
+      $this->assertSession()->responseContains($valid_entry);
       $this->assertNoFieldByXpath('//div[@content="' . $valid_entry . '"]', NULL, 'The "content" attribute is not present since the Prefix is not being displayed');
     }
 
@@ -331,7 +346,7 @@
     // Ensure that the 'number_decimal' formatter displays the number with the
     // expected rounding.
     $this->drupalGet('entity_test/' . $id);
-    $this->assertRaw(round($value, 2));
+    $this->assertSession()->responseContains(round($value, 2));
 
     // Try to create entries with more than one decimal separator; assert fail.
     $wrong_entries = [
@@ -348,7 +363,7 @@
         "{$field_name}[0][value]" => $wrong_entry,
       ];
       $this->drupalPostForm(NULL, $edit, t('Save'));
-      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
+      $this->assertSession()->responseContains(t('%name must be a number.', ['%name' => $field_name]));
     }
 
     // Try to create entries with minus sign not in the first position.
@@ -366,7 +381,7 @@
         "{$field_name}[0][value]" => $wrong_entry,
       ];
       $this->drupalPostForm(NULL, $edit, t('Save'));
-      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
+      $this->assertSession()->responseContains(t('%name must be a number.', ['%name' => $field_name]));
     }
   }
 
@@ -432,9 +447,9 @@
     ];
     $this->drupalPostForm($field_configuration_url, $edit, t('Save settings'));
     // Check if an error message is shown.
-    $this->assertNoRaw(t('%name is not a valid number.', ['%name' => t('Minimum')]));
+    $this->assertSession()->responseNotContains(t('%name is not a valid number.', ['%name' => t('Minimum')]));
     // Check if a success message is shown.
-    $this->assertRaw(t('Saved %label configuration.', ['%label' => $field->getLabel()]));
+    $this->assertSession()->responseContains(t('Saved %label configuration.', ['%label' => $field->getLabel()]));
     // Check if the minimum value was actually set.
     $this->drupalGet($field_configuration_url);
     $this->assertFieldById('edit-settings-min', $minimum_value, 'Minimal ' . gettype($minimum_value) . '  value was set on a ' . $field->getType() . ' field.');

holist’s picture

StatusFileSize
new19.15 KB
new584 bytes

Here's #173 with just the comment change from #179 without rerolling needed for 9.1.x, should apply to 8.9.6, @maxilein.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,81 @@
    +  /**
    +   * The minimum guaranteed significant number of floating point decimals.
    +   *
    +   * PHP's floating point implementation follows IEEE 754 doubles, which have
    +   * 53-bit significands. For a significand with N bits, floor((N-1) * log10(2))
    +   * gives the minimum number of significant decimals (Kahan, 1997, retrieved
    +   * from https://people.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF).
    +   * For IEEE 754 doubles (PHP floats), this is floor((53-1) * log10(2)) = 15.
    +   */
    +  const IEEE_754_DOUBLE_GUARANTEED_SIGNIFICANT_DECIMALS = 15;
    

    Are we sure about 15? For the PHP documentation we have

    The size of a float is platform-dependent, although a maximum of approximately 1.8e308 with a precision of roughly 14 decimal digits is a common value (the 64 bit IEEE format).

    - see https://www.php.net/manual/en/language.types.float.php

  2. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,81 @@
    +      // guarantee, convert it to a stirng directly.
    

    spelling - string.

  3. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,81 @@
    +    elseif (is_int($number)) {
    +      return (string) $number;
    +    }
    +    return $number;
    

    This could be simplified to return (string) $number; we can lose the elseif and the is_int() because the cast is going to check the type anyway - less logic and doing less.

  4. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -30,8 +114,28 @@ class Number {
    -  public static function validStep($value, $step, $offset = 0.0) {
    -    $double_value = (double) abs($value - $offset);
    +  public static function validStep($value, $step, $offset = NULL) {
    

    Why the default value change here? The code comments later:

        // Convert the value to a float so we can evaluate the precision later.
        // Because subtracting the offset may change the value's precision, we only
        // do so if it was set explicitly (is not null).
    

    BUT If I change this back to default to 0.0 and run \Drupal\Tests\Component\Utility\NumberTest it passes so either we're missing test coverage or this change is unnecessary.

  5. +++ b/core/lib/Drupal/Core/Render/Element/Number.php
    @@ -87,7 +87,6 @@ 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;
    

    We're setting $offset to 0.0 here anyway so I'm not sure I understand why the default value change is important.

ayushmishra206’s picture

Assigned: Unassigned » ayushmishra206
ayushmishra206’s picture

Assigned: ayushmishra206 » Unassigned
Status: Needs work » Needs review
StatusFileSize
new1.52 KB
new19.09 KB

Made changes #187.1 #187.2 #187.3 in this patch. Please review.

ayushmishra206’s picture

Status: Needs review » Needs work

Back to needs work for remaining changes.

alexpott’s picture

+++ b/core/lib/Drupal/Component/Utility/Number.php
@@ -9,6 +9,81 @@
+  /**
+   * The minimum guaranteed significant number of floating point decimals.
+   *
+   * PHP's floating point implementation follows IEEE 754 doubles, which have
+   * 53-bit significands. For a significand with N bits, floor((N-1) * log10(2))
+   * gives the minimum number of significant decimals (Kahan, 1997, retrieved
+   * from https://people.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF).
+   * For IEEE 754 doubles (PHP floats), this is floor((53-1) * log10(2)) = 15.
+   */
+  const IEEE_754_DOUBLE_GUARANTEED_SIGNIFICANT_DECIMALS = 15;

for what it is worth I don't think this number should be higher than the PHP variable precision - see http://php.net/precision - the default value is 14 but there's a question about why we are defining a constant here at all. And also what happens when a user set's the PHP variable to something else.

paulocs’s picture

Status: Needs work » Needs review
StatusFileSize
new4.96 KB
new24.54 KB

Hello,
I created a new patch for the 9.1.x-dev branch.
I did the changes suggested on comment 187.
I also removed the constant variable as @alexpott pointed on comment #191.

Cheers, Paulo.

holist’s picture

Status: Needs review » Needs work

Thanks @paulocs for the update. I however think what @alexpott meant was that we should not hardcode a value for precision as it can be different from configuration to configuration. So not defining a constant but still using the same value is not really the solution we should be looking at here. Instead we could just get the precision value from the PHP runtime configuration with ini_get() so whatever is set up in the environment will be used.

krug’s picture

Drupal Version: 8.9.5
nginx/1.14.2
PHP Version: 7.3
Database Version: 5.5.5-10.3.23-MariaDB

Filed Number (decimal)
Precision: 10
Scale: 2

For a value above 3999999,99 "field is not a valid number".
Тemporarily solved based on #56

In the admin theme for editing and creating content, in the part after

function ADMIN_THEME_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
...
ADD
$form['field_your_field']['widget'][0]['value']['#step'] = 'any';

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

sokru’s picture

Status: Needs work » Needs review
StatusFileSize
new24.54 KB
new1.39 KB

This is reroll from #192, with issue pointed out on #191 and #193.

holist’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @sokru for the update! Tests pass, I'd say this is (again) at a point where all raised concerns have been addressed, so RTBCing.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php
    @@ -68,7 +69,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
    -      '#max' => 32,
    +      '#max' => 14,
    

    Should this be converted to ini_get('precision')? Also do we need to ensure it's bigger than #min?

  2. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php
    @@ -78,7 +79,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
    -      '#max' => 10,
    +      '#max' => Number::IEEE_754_DOUBLE_GUARANTEED_SIGNIFICANT_DECIMALS,
    

    This constant does not exist.

sokru’s picture

Status: Needs work » Needs review
StatusFileSize
new1.13 KB
new24.61 KB

Addressing the issues mentioned on #198.

alexpott’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php
    @@ -68,7 +69,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
           '#min' => 10,
    -      '#max' => 32,
    +      '#max' => ini_get('precision') < $element['precision']['#min'] ? $element['precision']['#min'] : ini_get('precision'),
    

    I'm not sure this work - https://3v4l.org/AJSO8. I think we have to handle this after creating the array.

    The issue this highlights is that we need an update path :( - some site's will have fields configured with impossible precisions. We need to work out how to update that field config. Even more tricky is what if the updates impact the data?!?!?

  2. +++ b/core/lib/Drupal/Core/Render/Element/Number.php
    @@ -87,7 +87,6 @@ public static function validateNumber(&$element, FormStateInterface $form_state,
           $offset = isset($element['#min']) ? $element['#min'] : 0.0;
    -
           if (!NumberUtility::validStep($value, $element['#step'], $offset)) {
    
    +++ b/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php
    @@ -76,18 +78,30 @@ public function testNumberDecimalField() {
    -    $this->assertRaw('placeholder="0.00"');
    +    $this->assertSession()->responseContains('placeholder="0.00"');
    
    @@ -104,7 +118,7 @@ public function testNumberDecimalField() {
    -      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
    +      $this->assertSession()->responseContains(t('%name must be a number.', ['%name' => $field_name]));
    
    @@ -122,7 +136,7 @@ public function testNumberDecimalField() {
    -      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
    +      $this->assertSession()->responseContains(t('%name must be a number.', ['%name' => $field_name]));
    
    @@ -191,7 +205,7 @@ public function testNumberIntegerField() {
    -    $this->assertRaw('placeholder="4"');
    +    $this->assertSession()->responseContains('placeholder="4"');
    
    @@ -209,7 +223,7 @@ public function testNumberIntegerField() {
    -    $this->assertRaw(t('%name must be higher than or equal to %minimum.', ['%name' => $field_name, '%minimum' => $minimum]));
    +    $this->assertSession()->responseContains(t('%name must be higher than or equal to %minimum.', ['%name' => $field_name, '%minimum' => $minimum]));
    
    @@ -217,7 +231,7 @@ public function testNumberIntegerField() {
    -    $this->assertRaw(t('%name is not a valid number.', ['%name' => $field_name]));
    +    $this->assertSession()->responseContains(t('%name is not a valid number.', ['%name' => $field_name]));
    
    @@ -225,7 +239,7 @@ public function testNumberIntegerField() {
    -    $this->assertRaw(t('%name must be lower than or equal to %maximum.', ['%name' => $field_name, '%maximum' => $maximum]));
    +    $this->assertSession()->responseContains(t('%name must be lower than or equal to %maximum.', ['%name' => $field_name, '%maximum' => $maximum]));
    
    @@ -233,7 +247,7 @@ public function testNumberIntegerField() {
    -    $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
    +    $this->assertSession()->responseContains(t('%name must be a number.', ['%name' => $field_name]));
    
    @@ -251,7 +265,7 @@ public function testNumberIntegerField() {
    -      $this->assertRaw($valid_entry);
    +      $this->assertSession()->responseContains($valid_entry);
    
    @@ -316,7 +330,7 @@ public function testNumberFloatField() {
    -    $this->assertRaw('placeholder="0.00"');
    +    $this->assertSession()->responseContains('placeholder="0.00"');
    
    @@ -331,7 +345,7 @@ public function testNumberFloatField() {
    -    $this->assertRaw(round($value, 2));
    +    $this->assertSession()->responseContains(round($value, 2));
    
    @@ -348,7 +362,7 @@ public function testNumberFloatField() {
    -      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
    +      $this->assertSession()->responseContains(t('%name must be a number.', ['%name' => $field_name]));
    
    @@ -366,7 +380,7 @@ public function testNumberFloatField() {
    -      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
    +      $this->assertSession()->responseContains(t('%name must be a number.', ['%name' => $field_name]));
    
    @@ -432,9 +446,9 @@ public function assertSetMinimumValue($field, $minimum_value) {
    -    $this->assertNoRaw(t('%name is not a valid number.', ['%name' => t('Minimum')]));
    +    $this->assertSession()->responseNotContains(t('%name is not a valid number.', ['%name' => t('Minimum')]));
    ...
    -    $this->assertRaw(t('Saved %label configuration.', ['%label' => $field->getLabel()]));
    +    $this->assertSession()->responseContains(t('Saved %label configuration.', ['%label' => $field->getLabel()]));
    

    All this changes are out-of-scope. There are other issues focussing on replace assertRaw().

  3. +++ b/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php
    @@ -76,18 +78,30 @@ public function testNumberDecimalField() {
    +      $this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
    

    The message 'Entity was created' is not necessary - as we have to change this line it is in scope. Also this should use $this->assertSession()->pageTextContains() and should not use t(). It could assert on the text "entity_test $id has been created."

  4. +++ b/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php
    @@ -76,18 +78,30 @@ public function testNumberDecimalField() {
    +      $this->assertSession()->responseContains($valid_entry);
    

    pageTextContains() - I don't know why this has to be in the raw html.

  5. +++ b/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php
    @@ -76,18 +78,30 @@ public function testNumberDecimalField() {
    +      $this->assertSession()->responseNotContains(t('%name is not a valid number.', ['%name' => $field_name]));
    

    This is not necessary. If the entity didn't validate we would not have the created message.

alexpott’s picture

Also #200.1 implies that we don't have actual test coverage of this form! If I try out #199 locally I get PHP warnings.

andypost’s picture

+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php
@@ -69,7 +69,7 @@
+      '#max' => ini_get('precision') < $element['precision']['#min'] ? $element['precision']['#min'] : ini_get('precision'),

@@ -79,7 +79,7 @@
+      '#max' => ini_get('precision'),

please replace 3 function calls with local variable

maxilein’s picture

StatusFileSize
new8.9 KB

#199 addition to the patch for the NumberFieldTest.php which did not patch on D8.9.8

laravz’s picture

Hi,

We have applied patch #186 (the last working applicable patch for D8) to our working project and noticed that the site gets a fatal error when using a very large number.

decimal number: 9831510254545454000,00
field settings: decimals 3, precision 10
Drupal\Core\Entity\EntityStorageException: SQLSTATE[22003]: Numeric value out of range: 1264 Out of range value for column

We have seen the same error with previous patches. Is this something that should be fixed here?

maxilein’s picture

Using the patch #199 with #203 (which is of no importance I think) on D.8.9.8: Creating a new decimal field give Error on step 2 "field settings":

    Notice: Undefined index: precision in Drupal\Core\Field\Plugin\Field\FieldType\DecimalItem->storageSettingsForm() (line 72 of core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php).

    Drupal\Core\Field\Plugin\Field\FieldType\DecimalItem->storageSettingsForm(Array, Object, ) (Line: 92)
    Drupal\field_ui\Form\FieldStorageConfigEditForm->form(Array, Object) (Line: 149)
    Drupal\Core\Entity\EntityForm->buildForm(Array, Object) (Line: 56)
    Drupal\field_ui\Form\FieldStorageConfigEditForm->buildForm(Array, Object, 'node.fue_projekt_kosten.field_kostenschaetzung')
    call_user_func_array(Array, Array) (Line: 532)
    Drupal\Core\Form\FormBuilder->retrieveForm('field_storage_config_edit_form', Object) (Line: 278)
    Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 91)
    Drupal\Core\Controller\FormController->getContentResult(Object, Object) (Line: 39)
    Drupal\layout_builder\Controller\LayoutBuilderHtmlEntityFormController->getContentResult(Object, Object)
    call_user_func_array(Array, Array) (Line: 123)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 573)
    Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 151)
    Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 68)
    Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
    Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
    Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
    Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
    Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
    Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
    Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
    Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 708)
    Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

    Notice: Trying to access array offset on value of type null in Drupal\Core\Field\Plugin\Field\FieldType\DecimalItem->storageSettingsForm() (line 72 of core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php).

    Drupal\Core\Field\Plugin\Field\FieldType\DecimalItem->storageSettingsForm(Array, Object, ) (Line: 92)
    Drupal\field_ui\Form\FieldStorageConfigEditForm->form(Array, Object) (Line: 149)
    Drupal\Core\Entity\EntityForm->buildForm(Array, Object) (Line: 56)
    Drupal\field_ui\Form\FieldStorageConfigEditForm->buildForm(Array, Object, 'node.fue_projekt_kosten.field_kostenschaetzung')
    call_user_func_array(Array, Array) (Line: 532)
    Drupal\Core\Form\FormBuilder->retrieveForm('field_storage_config_edit_form', Object) (Line: 278)
    Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 91)
    Drupal\Core\Controller\FormController->getContentResult(Object, Object) (Line: 39)
    Drupal\layout_builder\Controller\LayoutBuilderHtmlEntityFormController->getContentResult(Object, Object)
    call_user_func_array(Array, Array) (Line: 123)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 573)
    Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 151)
    Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 68)
    Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
    Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
    Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
    Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
    Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
    Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
    Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
    Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 708)
    Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

The field does get created and seems to work though.

maxilein’s picture

StatusFileSize
new24.81 KB

Here is the combination of #199 and #203 in one file.
Patches on 8.9.11

o&#039;briat’s picture

Here's something strange, maybe it's only related to my dev env, but when testing this validation from within xdebug or drush php /ev, it return TRUE :

drush ev "var_dump(\Drupal\Component\Utility\Number::validStep("600000000000.00", 0.01, 0.0))" 
bool(true)

My field is 20 + 2
php 7.4.12 (docker4drupal stack)

rafaelaleung’s picture

Rerolling patch from #206 for 9.2.x. Have not created interdiff since I was not able to apply the original patch on 9.2.x.

rafaelaleung’s picture

Have removed the unused use statement error from patch #208 in this patch.
Still on 9.2.x.

rafaelaleung’s picture

StatusFileSize
new24.22 KB

Rerolling patch for 9.2.x - fixing typo

maxilein’s picture

Will this EVER reach production?!

wombatbuddy’s picture

In addition to the #56 workaround, or if you are using the "number" form element and want to keep the "step" attribute, that you can do it like this:

form['my_number'] = [
  '#type' => 'number',
  '#step' => 'any',
  '#attributes' => [
    'step' => '0.01',
  ],
];

Spokje made their first commit to this issue’s fork.

spokje’s picture

Used drupal-simple-decimal-validation-2230909-210.patch as base for the newly created MR.

maxilein’s picture

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

maxilein’s picture

Worked fine for me on 9.1.7 and 9.1.8

maxmendez’s picture

Thanks for your work, the patch stop working on 9.1.10

maxilein’s picture

@MaxMendez: can you be more specific. I just applied drupal-simple-decimal-validation-2230909-210 without problems to 9.1.10.

maxilein’s picture

This error occurs on a content type adding decimal field or looking at the field's configuration:

Notice: Undefined index: precision in Drupal\Core\Field\Plugin\Field\FieldType\DecimalItem->storageSettingsForm() (line 71 of core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php).

Drupal\Core\Field\Plugin\Field\FieldType\DecimalItem->storageSettingsForm(Array, Object, 1) (Line: 92)
Drupal\field_ui\Form\FieldStorageConfigEditForm->form(Array, Object) (Line: 106)
Drupal\Core\Entity\EntityForm->buildForm(Array, Object) (Line: 56)
Drupal\field_ui\Form\FieldStorageConfigEditForm->buildForm(Array, Object, 'node.klient.field_bmgl_lt_antrag')
call_user_func_array(Array, Array) (Line: 532)
Drupal\Core\Form\FormBuilder->retrieveForm('field_storage_config_edit_form', Object) (Line: 278)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 73)
Drupal\Core\Controller\FormController->getContentResult(Object, Object) (Line: 39)
Drupal\layout_builder\Controller\LayoutBuilderHtmlEntityFormController->getContentResult(Object, Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 573)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 158)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 80)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 706)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
dpw’s picture

Hello, here's the output when I try to apply the patch from #210 to 9.1.10. Needless to say, I have a valid decimal number that receives a "not a valid number." error message.

$ patch -p1 --dry-run < ../patches/drupal-simple-decimal-validation-2230909-210.patch 
checking file core/lib/Drupal/Component/Utility/Number.php
checking file core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php
checking file core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php
checking file core/lib/Drupal/Core/Render/Element/Number.php
checking file core/modules/field/tests/src/Functional/Number/NumberFieldTest.php
Hunk #2 FAILED at 78.
Hunk #10 FAILED at 253.
Hunk #15 succeeded at 439 with fuzz 2 (offset 1 line).
2 out of 15 hunks FAILED
checking file core/tests/Drupal/Tests/Component/Utility/NumberTest.php

edit: choosing to convert the fields to integers.

maxmendez’s picture

StatusFileSize
new24.22 KB

Thanks @dpw for the output.

I've worked to make the patch #210 works on Drupal 9.2.1, the main problem was on NumberFieldTest.php.

I've fixed the problem on NumberFieldTest.php and the notice reported on #221.

Be free to test and improved if it is necessary.

kkasson’s picture

We've been using the patch for a while and it has helped but I just found another case that's still failing.

I have a decimal field with with Precision 20 and Scale 5. Validation fails for numbers above 2914543744, e.g. 2914543800 will fail but 2914542000 is okay. I'm not completely sure but I think it has to do with the floating point the conversion of $step, i.e. PHP converts step from 0.0001 to 0.00001000000000000000081803053914031309545862313826

It starts working again at 68719476737, that one is valid.

maxilein’s picture

This seems to be a never ending story. We should ask if this is solvable with current means at all.

May I ask why there is no library in the core/vendor doing all the decimal handling?
Or a specialized extension:

https://php-decimal.io/#introduction

Read more about it here: https://medium.com/@rtheunissen/accurate-numbers-in-php-b6954f6cd577

Could this be a more reliable approach?

I don’t want to include an extension as a dependency.

This has so far been the most common response to recommending the decimal extension as a solution. While I understand and respect the caution towards non-default extensions, it is becoming easier and easier to manage these as dependencies in the same way you would any other dependency: Composer, paired with container-based environments.

Require “php-decimal/php-decimal” as a dependency, which indirectly adds a dependency on the extension and provides stubs for auto-completion.

Composer does not install extensions, so this requires a few additional steps.

andypost’s picture

There was support for bcmath and gmp in early core but afaik only drupal commerce require bcmath
Moreover PECL decimal is hard to require on shared hostings

kevin.brocatus’s picture

#223 no longer applied to Drupal 9.2.x.

kevin.brocatus’s picture

Fixed some issues with #227.

maxilein’s picture

#227 Seems to work on 9.2.4. Thank you!

bsztreha’s picture

I have to apply this patch against 9.2.5, so I created a modified version from #228 to make it apply.

maxmendez’s picture

Thanks @bsztreha, I've applied the patch #230 without problems.

maxmendez’s picture

Updated patch to work on 9.2.7.

maxmendez’s picture

StatusFileSize
new15.93 KB

Updated correct patch

bsztreha’s picture

Thanks @MaxMendez for updated, but seems you left /web in patch file and I couldn't apply the patch, uploaded the version without extra dir

maxilein’s picture

Is there a reason why you left out so much functionality from 228?

Diff between 228 and 234:

--- X:/DEV/www/drupal-simple-decimal-validation-2230909-228.patch	Mon Oct 11 15:06:13 2021
+++ X:/DEV/www/drupal-simple-decimal-validation-ver_9.2.7-2230909-234.patch	Mon Oct 11 15:06:20 2021
@@ -2 +2 @@ diff --git a/core/lib/Drupal/Component/Utility/Num
-index 838c33c454..963427418c 100644
+index 838c33c45..963427418 100644
@@ -8 +8 @@ diff --git a/core/lib/Drupal/Component/Utility/Num
- 
+
@@ -125 +125 @@ diff --git a/core/lib/Drupal/Component/Utility/Num
- 
+
@@ -136 +136 @@ diff --git a/core/lib/Drupal/Component/Utility/Num
- 
+
@@ -141 +141 @@ diff --git a/core/lib/Drupal/Component/Utility/Num
- 
+
@@ -148 +148 @@ diff --git a/core/lib/Drupal/Component/Utility/Num
- 
+
@@ -152 +152 @@ diff --git a/core/lib/Drupal/Core/Field/Plugin/Fie
-index 379ff74db0..8c42f2b1fa 100644
+index 379ff74db..8c42f2b1f 100644
@@ -176 +176 @@ diff --git a/core/lib/Drupal/Core/Field/Plugin/Fie
- 
+
@@ -183 +183 @@ diff --git a/core/lib/Drupal/Core/Field/Plugin/Fie
- 
+
@@ -187 +187 @@ diff --git a/core/lib/Drupal/Core/Field/Plugin/Fie
-index b4fac537b4..fe028fd3f8 100644
+index b4fac537b..fe028fd3f 100644
@@ -196 +196 @@ diff --git a/core/lib/Drupal/Core/Field/Plugin/Fie
- 
+
@@ -203 +203 @@ diff --git a/core/lib/Drupal/Core/Field/Plugin/Fie
- 
+
@@ -206 +206 @@ diff --git a/core/modules/field/tests/src/Function
-index 667a587a9d..66f118e567 100644
+index 595aba8e2..01a1ec067 100644
@@ -224,3 +224 @@ diff --git a/core/modules/field/tests/src/Function
-@@ -76,18 +78,31 @@ public function testNumberDecimalField() {
-     // Display creation form.
-     $this->drupalGet('entity_test/add');
+@@ -78,16 +80,29 @@ public function testNumberDecimalField() {
@@ -228,3 +226,2 @@ diff --git a/core/modules/field/tests/src/Function
--    $this->assertRaw('placeholder="0.00"');
-+    $this->assertSession()->responseContains('placeholder="0.00"');
- 
+     $this->assertSession()->responseContains('placeholder="0.00"');
+
@@ -249 +246 @@ diff --git a/core/modules/field/tests/src/Function
--    $this->assertRaw($value);
+-    $this->assertSession()->responseContains($value);
@@ -263 +260 @@ diff --git a/core/modules/field/tests/src/Function
- 
+
@@ -270 +267 @@ diff --git a/core/modules/field/tests/src/Function
--      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
+-      $this->assertSession()->pageTextContains("{$field_name} must be a number.");
@@ -273 +270 @@ diff --git a/core/modules/field/tests/src/Function
- 
+
@@ -279 +276 @@ diff --git a/core/modules/field/tests/src/Function
--      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
+-      $this->assertSession()->pageTextContains("{$field_name} must be a number.");
@@ -283,10 +280 @@ diff --git a/core/modules/field/tests/src/Function
- 
-@@ -191,7 +206,7 @@ public function testNumberIntegerField() {
-     // Display creation form.
-     $this->drupalGet('entity_test/add');
-     $this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
--    $this->assertRaw('placeholder="4"');
-+    $this->assertSession()->responseContains('placeholder="4"');
- 
-     // Submit a valid integer
-     $value = rand($minimum, $maximum);
+
@@ -297 +285 @@ diff --git a/core/modules/field/tests/src/Function
--    $this->assertRaw(t('%name must be higher than or equal to %minimum.', ['%name' => $field_name, '%minimum' => $minimum]));
+-    $this->assertSession()->pageTextContains("{$field_name} must be higher than or equal to {$minimum}.");
@@ -299 +287 @@ diff --git a/core/modules/field/tests/src/Function
- 
+
@@ -306 +294 @@ diff --git a/core/modules/field/tests/src/Function
--    $this->assertRaw(t('%name is not a valid number.', ['%name' => $field_name]));
+-    $this->assertSession()->pageTextContains("{$field_name} is not a valid number.");
@@ -308 +296 @@ diff --git a/core/modules/field/tests/src/Function
- 
+
@@ -315 +303 @@ diff --git a/core/modules/field/tests/src/Function
--    $this->assertRaw(t('%name must be lower than or equal to %maximum.', ['%name' => $field_name, '%maximum' => $maximum]));
+-    $this->assertSession()->pageTextContains("{$field_name} must be lower than or equal to {$maximum}.");
@@ -317 +305 @@ diff --git a/core/modules/field/tests/src/Function
- 
+
@@ -324 +312 @@ diff --git a/core/modules/field/tests/src/Function
--    $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
+-    $this->assertSession()->pageTextContains("{$field_name} must be a number.");
@@ -326 +314 @@ diff --git a/core/modules/field/tests/src/Function
- 
+
@@ -329,27 +316,0 @@ diff --git a/core/modules/field/tests/src/Function
-@@ -251,7 +266,7 @@ public function testNumberIntegerField() {
-       preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
-       $id = $match[1];
-       $this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
--      $this->assertRaw($valid_entry);
-+      $this->assertSession()->responseContains($valid_entry);
-       // Verify that the "content" attribute is not present since the Prefix is
-       // not being displayed.
-       $this->assertSession()->elementNotExists('xpath', '//div[@content="' . $valid_entry . '"]');
-@@ -320,7 +335,7 @@ public function testNumberFloatField() {
-     // Display creation form.
-     $this->drupalGet('entity_test/add');
-     $this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
--    $this->assertRaw('placeholder="0.00"');
-+    $this->assertSession()->responseContains('placeholder="0.00"');
- 
-     // Submit a signed decimal value within the allowed precision and scale.
-     $value = '-1234.5678';
-@@ -335,7 +350,7 @@ public function testNumberFloatField() {
-     // Ensure that the 'number_decimal' formatter displays the number with the
-     // expected rounding.
-     $this->drupalGet('entity_test/' . $id);
--    $this->assertRaw(round($value, 2));
-+    $this->assertSession()->responseContains(round($value, 2));
- 
-     // Try to create entries with more than one decimal separator; assert fail.
-     $wrong_entries = [
@@ -360 +321 @@ diff --git a/core/modules/field/tests/src/Function
--      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
+-      $this->assertSession()->pageTextContains("{$field_name} must be a number.");
@@ -363 +324 @@ diff --git a/core/modules/field/tests/src/Function
- 
+
@@ -369 +330 @@ diff --git a/core/modules/field/tests/src/Function
--      $this->assertRaw(t('%name must be a number.', ['%name' => $field_name]));
+-      $this->assertSession()->pageTextContains("{$field_name} must be a number.");
@@ -373,4 +334,2 @@ diff --git a/core/modules/field/tests/src/Function
- 
-@@ -437,9 +452,9 @@ public function assertSetMinimumValue($field, $minimum_value) {
-     $this->drupalGet($field_configuration_url);
-     $this->submitForm($edit, 'Save settings');
+
+@@ -439,7 +454,7 @@ public function assertSetMinimumValue($field, $minimum_value) {
@@ -378,2 +337 @@ diff --git a/core/modules/field/tests/src/Function
--    $this->assertNoRaw(t('%name is not a valid number.', ['%name' => t('Minimum')]));
-+    $this->assertSession()->responseNotContains(t('%name is not a valid number.', ['%name' => t('Minimum')]));
+     $this->assertSession()->pageTextNotContains("Minimum is not a valid number.");
@@ -381 +339 @@ diff --git a/core/modules/field/tests/src/Function
--    $this->assertRaw(t('Saved %label configuration.', ['%label' => $field->getLabel()]));
+-    $this->assertSession()->pageTextContains("Saved {$field->getLabel()} configuration.");
@@ -386,177 +343,0 @@ diff --git a/core/modules/field/tests/src/Function
-diff --git a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
-index 0c5a5fa507..ec85c4d19d 100644
---- a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
-+++ b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
-@@ -22,9 +22,9 @@ class NumberTest extends TestCase {
-    * @dataProvider providerTestValidStep
-    * @covers ::validStep
-    *
--   * @param numeric $value
-+   * @param int|float|string $value
-    *   The value argument for Number::validStep().
--   * @param numeric $step
-+   * @param int|float|string $step
-    *   The step argument for Number::validStep().
-    * @param bool $expected
-    *   Expected return value from Number::validStep().
-@@ -34,17 +34,104 @@ public function testValidStep($value, $step, $expected) {
-     $this->assertEquals($expected, $return);
-   }
- 
-+  /**
-+   * @covers \Drupal\Component\Utility\Number::normalize
-+   *
-+   * @param int|float|string $number
-+   *   The number to test the count on.
-+   * @param int $expected
-+   *   The expected number of significant decimals.
-+   *
-+   * @dataProvider providerNormalize
-+   */
-+  public function testNormalize($number, $expected) {
-+    $this->assertEquals($expected, Number::normalize($number));
-+  }
-+
-+  /**
-+   * Provides data to self::testNormalize().
-+   */
-+  public function providerNormalize() {
-+    return [
-+      ['', ''],
-+      [0, 0],
-+      [123456, 123456],
-+      [-123456, -123456],
-+      [0.0, 0.0],
-+      [0.12300000000000000001, 0.123],
-+      [1 / 3, 0.33333333333333],
-+      [10.00000000000000000999, 10],
-+      [1234.1234567800000000000000001, 1234.12345678],
-+      ['1234.1234567800000000000000001', '1234.1234567800000000000000001'],
-+    ];
-+  }
-+
-+  /**
-+   * @covers ::countSignificantDecimals
-+   *
-+   * @dataProvider provideCountSignificantDecimals
-+   *
-+   * @param int $expected
-+   *   The expected number of significant decimals.
-+   * @param int|float|string $number
-+   *   The number to test the count on.
-+   */
-+  public function testCountSignificantDecimals($expected, $number) {
-+    $this->assertEquals($expected, Number::countSignificantDecimals($number));
-+  }
-+
-+  /**
-+   * Provides data to self::testCountSignificantDecimals().
-+   */
-+  public function provideCountSignificantDecimals() {
-+    return [
-+      [0, 0],
-+      [0, '0'],
-+      [0, 9],
-+      [0, '9'],
-+      [0, -9],
-+      [0, '-9'],
-+      [0, 999999999],
-+      [0, '999999999'],
-+      [0, -999999999],
-+      [0, '-999999999'],
-+      [0, 0.0],
-+      [1, '0.0'],
-+      [0, -0.0],
-+      [1, '-0.0'],
-+      // The maximum supported number of significant float decimals is 14.
-+      [0, 0.0000000000000],
-+      [0, -0.0000000000000],
-+      [9, -0.0000000090000],
-+      [9, -0.0000000090000],
-+      [9, -0.00000000900000],
-+      [9, -0.000000009000000000],
-+      [14, -0.00000000900009],
-+      [14, -0.0000000090000099],
-+      [14, -0.0000000090000090009],
-+      // Numeric strings do not suffer from the system-specific limitations to
-+      // float precision, so they can contain many more significant decimals.
-+      // This is especially useful when working with solutions such as BCMath
-+      // (https://secure.php.net/manual/en/book.bc.php)
-+      [14, '0.00000000000000'],
-+      [14, '-0.00000000000000'],
-+      [14, '-0.00000000900000'],
-+      [16, '-0.0000000090000000'],
-+      [20, '-0.00000000900000000000'],
-+    ];
-+  }
-+
-   /**
-    * Tests Number::validStep() with offset.
-    *
-    * @dataProvider providerTestValidStepOffset
-    * @covers ::validStep
-    *
--   * @param numeric $value
-+   * @param int|float|string $value
-    *   The value argument for Number::validStep().
--   * @param numeric $step
-+   * @param int|float|string $step
-    *   The step argument for Number::validStep().
--   * @param numeric $offset
-+   * @param float $offset
-    *   The offset argument for Number::validStep().
-    * @param bool $expected
-    *   Expected return value from Number::validStep().
-@@ -72,23 +159,50 @@ public static function providerTestValidStep() {
-       [42, 10.5, TRUE],
-       [1, 1 / 3, TRUE],
-       [-100, 100 / 7, TRUE],
--      [1000, -10, TRUE],
- 
-       // Valid and very small float steps.
-+      [1936.5, 3e-8, TRUE],
-       [1000.12345, 1e-10, TRUE],
-       [3.9999999999999, 1e-13, TRUE],
- 
-       // Invalid integer steps.
-       [100, 30, FALSE],
-       [-10, 4, FALSE],
-+      [1000, -10, FALSE],
- 
-       // Invalid float steps.
-       [6, 5 / 7, FALSE],
-       [10.3, 10.25, FALSE],
-+      [1000, -10.0, FALSE],
- 
-       // 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 fail due to precision (rounding)
-+      // errors that are inherent to floating point numbers.
-+      [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],
-+      +
-+      // Ensure floats with more significant decimals that the guaranteed
-+      // minimum are normalized.
-+      [0.0000000000000900009, 0.0000000000000100001, TRUE],
-     ];
-   }
- 

maxilein’s picture

#234 applied fine with 9.2.8. Thanks

maxilein’s picture

#234 applied fine with 9.2.9. Thanks

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

maxilein’s picture

#234 applied fine with 9.3.0. Thanks

When will we finally set this into production?!

jbowm2’s picture

#234 applied clean and worked for me! Thanks

rgristroph’s picture

I applied #234 and it fixed the problem for me ! Thanks.

maxilein’s picture

#234 applied fine with 9.3.6.
When will this finally get into core?!

andregp’s picture

Status: Needs work » Reviewed & tested by the community

Patch #234 Applied cleanly on 9.4.x and solved the issue (I was able to save decimal numbers that were returning errors before the patch (Following IS steps to reproduce)).

Setting to RTBC as per #236, #237, #239, #240, #241 and #242, who also tested the patch.

jwilson3’s picture

Status: Reviewed & tested by the community » Needs work

Afaict, there is a valid question on #235 that should be addressed:

Is there a reason why you left out so much functionality from 228?

At least some justification about why the removals are Okay.

All the additions to core/tests/Drupal/Tests/Component/Utility/NumberTest.php should at least be added back to the patch to confirm that we're passing the expanded test cases...

diff --git a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
index 0c5a5fa507..ec85c4d19d 100644
--- a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
@@ -22,9 +22,9 @@ class NumberTest extends TestCase {
    * @dataProvider providerTestValidStep
    * @covers ::validStep
    *
-   * @param numeric $value
+   * @param int|float|string $value
    *   The value argument for Number::validStep().
-   * @param numeric $step
+   * @param int|float|string $step
    *   The step argument for Number::validStep().
    * @param bool $expected
    *   Expected return value from Number::validStep().
@@ -34,17 +34,104 @@ public function testValidStep($value, $step, $expected) {
     $this->assertEquals($expected, $return);
   }
 
+  /**
+   * @covers \Drupal\Component\Utility\Number::normalize
+   *
+   * @param int|float|string $number
+   *   The number to test the count on.
+   * @param int $expected
+   *   The expected number of significant decimals.
+   *
+   * @dataProvider providerNormalize
+   */
+  public function testNormalize($number, $expected) {
+    $this->assertEquals($expected, Number::normalize($number));
+  }
+
+  /**
+   * Provides data to self::testNormalize().
+   */
+  public function providerNormalize() {
+    return [
+      ['', ''],
+      [0, 0],
+      [123456, 123456],
+      [-123456, -123456],
+      [0.0, 0.0],
+      [0.12300000000000000001, 0.123],
+      [1 / 3, 0.33333333333333],
+      [10.00000000000000000999, 10],
+      [1234.1234567800000000000000001, 1234.12345678],
+      ['1234.1234567800000000000000001', '1234.1234567800000000000000001'],
+    ];
+  }
+
+  /**
+   * @covers ::countSignificantDecimals
+   *
+   * @dataProvider provideCountSignificantDecimals
+   *
+   * @param int $expected
+   *   The expected number of significant decimals.
+   * @param int|float|string $number
+   *   The number to test the count on.
+   */
+  public function testCountSignificantDecimals($expected, $number) {
+    $this->assertEquals($expected, Number::countSignificantDecimals($number));
+  }
+
+  /**
+   * Provides data to self::testCountSignificantDecimals().
+   */
+  public function provideCountSignificantDecimals() {
+    return [
+      [0, 0],
+      [0, '0'],
+      [0, 9],
+      [0, '9'],
+      [0, -9],
+      [0, '-9'],
+      [0, 999999999],
+      [0, '999999999'],
+      [0, -999999999],
+      [0, '-999999999'],
+      [0, 0.0],
+      [1, '0.0'],
+      [0, -0.0],
+      [1, '-0.0'],
+      // The maximum supported number of significant float decimals is 14.
+      [0, 0.0000000000000],
+      [0, -0.0000000000000],
+      [9, -0.0000000090000],
+      [9, -0.0000000090000],
+      [9, -0.00000000900000],
+      [9, -0.000000009000000000],
+      [14, -0.00000000900009],
+      [14, -0.0000000090000099],
+      [14, -0.0000000090000090009],
+      // Numeric strings do not suffer from the system-specific limitations to
+      // float precision, so they can contain many more significant decimals.
+      // This is especially useful when working with solutions such as BCMath
+      // (https://secure.php.net/manual/en/book.bc.php)
+      [14, '0.00000000000000'],
+      [14, '-0.00000000000000'],
+      [14, '-0.00000000900000'],
+      [16, '-0.0000000090000000'],
+      [20, '-0.00000000900000000000'],
+    ];
+  }
+
   /**
    * Tests Number::validStep() with offset.
    *
    * @dataProvider providerTestValidStepOffset
    * @covers ::validStep
    *
-   * @param numeric $value
+   * @param int|float|string $value
    *   The value argument for Number::validStep().
-   * @param numeric $step
+   * @param int|float|string $step
    *   The step argument for Number::validStep().
-   * @param numeric $offset
+   * @param float $offset
    *   The offset argument for Number::validStep().
    * @param bool $expected
    *   Expected return value from Number::validStep().
@@ -72,23 +159,50 @@ public static function providerTestValidStep() {
       [42, 10.5, TRUE],
       [1, 1 / 3, TRUE],
       [-100, 100 / 7, TRUE],
-      [1000, -10, TRUE],
 
       // Valid and very small float steps.
+      [1936.5, 3e-8, TRUE],
       [1000.12345, 1e-10, TRUE],
       [3.9999999999999, 1e-13, TRUE],
 
       // Invalid integer steps.
       [100, 30, FALSE],
       [-10, 4, FALSE],
+      [1000, -10, FALSE],
 
       // Invalid float steps.
       [6, 5 / 7, FALSE],
       [10.3, 10.25, FALSE],
+      [1000, -10.0, FALSE],
 
       // 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 fail due to precision (rounding)
+      // errors that are inherent to floating point numbers.
+      [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],
+      +
+      // Ensure floats with more significant decimals that the guaranteed
+      // minimum are normalized.
+      [0.0000000000000900009, 0.0000000000000100001, TRUE],
     ];
   }
andregp’s picture

Assigned: Unassigned » andregp

@jwilson3 You're right,
I couldn't find a reason for this code being removed. I tried adding it to the code and it even corrected the 2 failed tests on patch #234 (My bad for changing it to RTBC) =/
I'm working on this now.

andregp’s picture

Assigned: andregp » Unassigned
Status: Needs work » Needs review
StatusFileSize
new17.38 KB
new8.05 KB
new11.52 KB

Bellow is the new patch with the expanded tests added back.

I also fixed some small coding standard errors in the patch code, to ensure no CS errors would be introduced by it.

I also removed some changes introduced by #183 because:
1 - They are outside this issue's scope so shouldn't have been added here in first place.
2 - These deprecated lines were already fixed in between core commits (as you can see between the patches #228 and #230).

I'm also adding a tests-only patch (which is expected to fail).

Status: Needs review » Needs work

The last submitted patch, 246: 2230909-245-tests-only.patch, failed testing. View results

andregp’s picture

Status: Needs work » Needs review

tests only patch failed as expected :)

samaphp’s picture

Status: Needs review » Reviewed & tested by the community

Great effort. Thank you everyone. I can confirm the patch in comment #246 is working well.
https://www.drupal.org/files/issues/2022-03-10/2230909-245.patch

The numbers that was failing before is working fine. I was trying to save (201500000.00) and now i can save it with no issues after applying the patch. I checked the DB and the node view page all working fine.
Actually it is great to compare the float number instead of the exponential number that was failing.

TESTED on Drupal 9.3.7

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs issue summary update
  1. This looks really close. Nice. There's a couple of nits due to typehinting that's now possible that was not when this issue started. And I think we might need to do a little bit of issue management / summary updating / change record writing.
  2. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,67 @@
    +  public static function normalize($number) {
    

    Return type hint of : string

  3. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,67 @@
    +  public static function countSignificantDecimals($number) {
    

    Return type hint of : int

  4. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php
    @@ -73,12 +73,13 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
    +      '#number_type' => $this->fieldDefinition->getType(),
    

    Is this necessary - I'm not sure it is. It appears unused. Yet we're listing it as an API change. I think we need to fix up the issue summary to reflect the current direction. If the issue summary is correct then we'll need a change record. We might need a change record anyway.

andregp’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs issue summary update
StatusFileSize
new8.05 KB
new17.21 KB
new1.55 KB

Removed #number_type mention on the IS.
Changed patch as per #250.

The last submitted patch, 251: 2230909-251-tests-only.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 251: 2230909-251.patch, failed testing. View results

andregp’s picture

Status: Needs work » Needs review

The test fail seems unrelated, requeueing tests.

Status: Needs review » Needs work

The last submitted patch, 251: 2230909-251.patch, failed testing. View results

andregp’s picture

Status: Needs work » Needs review

Okay, I still suspect the test fail is unrelated (see bellow), but it's odd that the same supposedly random error happened twice:

1) Drupal\Tests\aggregator\Functional\migrate_drupal_ui\d7\MultilingualReviewPageTest::testMigrateUpgradeReviewPage
Behat\Mink\Exception\ElementNotFoundException: Element matching xpath "//td[contains(@class, 'checked') and text() = 'Color']" not found.

Requeueing one more time (I hope it's the last).

Status: Needs review » Needs work

The last submitted patch, 251: 2230909-251.patch, failed testing. View results

andregp’s picture

Okay, it seems the error is indeed related to the patch (three times the same error!), but this is weird.
I don't see a connection between the changes on patch #251 (see interdiff_2230909_245-251.txt) and the error returned by the bot (see #256).

maxilein’s picture

andregp’s picture

Status: Needs work » Needs review

HA! The fail is unrelated!
I just got another of this exact same fail on a previously passing patch from another issue (see here https://www.drupal.org/pift-ci-job/2364559), the HEAD seems to be broken (see here https://www.drupal.org/pift-ci-job/2364550).
Sending back to needs review. This time I'll wait for the HEAD to pass to then retest.

maxilein’s picture

Applied fine to D 9.3.12

maxilein’s picture

Applied fine to D 9.3.13

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

xjm’s picture

Issue tags: -Triaged for D8 major current state
maxilein’s picture

In the meantime: https://www.drupal.org/files/issues/2022-03-10/2230909-245.patch applied fine on D 9.4 and D9.4.1

darvanen’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Bug Smash Initiative

Applying the patch in #251 fixed this issue for me.

I have also:

  • Rerun the test on 9.4.x which passed
  • Scanned the patch and could not find anything I would feedback on
  • Confirmed feedback in #250 was addressed.

I reckon this is RTBC.

Somewhat related: #2965627: Bad step value with decimals listed this issue as a possible duplicate but I could not reproduce that issue and have closed it as such.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,67 @@
    +   *   will lose the additional precision as PHP does not guarantee.
    

    PHP does not guarantee .... what?

    Don't leave me hanging!

    jokes aside, this sentence seems like its missing a resolution, what does PHP not guarantee?

  2. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,67 @@
    +      // guarantee, discard the not guaranteed ones.
    

    suggest 'discard the not guaranteed ones' => 'discard those that are not guaranteed'

  3. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,67 @@
    +   *   The number whose decimals needed to be to count. If this is a string, it
    

    count => counted

  4. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,67 @@
    +   *   must be an integer or a float formatted. Floats are limited to the
    

    'or a float formatted' => 'or formatted as a float'

  5. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,67 @@
    +   *   the system-specific limitations to float precision, so they can contain
    +   *   many more significant decimals.
    

    I don't think the word 'many' adds anything here

  6. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,67 @@
    +    $number = static::normalize($number);
    

    should we be doing something like an is_numeric check here?

    I think this function would return 3 for "Yep.yep"

    Would we return 0? or perhaps throw an invalid argument exception?

  7. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,67 @@
    +    if (strrpos($number, '.') === FALSE) {
    

    we can use str_contains here now, we have a polyfill for lower PHP versions

  8. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -31,7 +101,27 @@ class Number {
    +    // If the actual value has more significant decimals than expected, it has a
    +    // higher precision than desired it isn't divisible by the step.
    

    is this missing 'if' before 'it'?

  9. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php
    @@ -68,7 +68,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
    +      '#max' => ini_get('precision') < 10 ? 10 : ini_get('precision'),
    

    If precision is less than 10, should we allow 10?

  10. +++ b/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php
    @@ -79,16 +81,29 @@ public function testNumberDecimalField() {
    +    foreach ($valid_entries as $valid_entry) {
    +      $this->drupalGet('entity_test/add');
    +      $edit = [
    +        "{$field_name}[0][value]" => $valid_entry,
    +      ];
    +      $this->submitForm($edit, 'Save');
    +      preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
    +      $id = $match[1];
    +      $this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
    +      $this->assertSession()->responseContains($valid_entry);
    +      $this->assertSession()->responseNotContains("{$field_name} is not a valid number.");
    

    I was going to suggest that we should be able to test this with a kernel test by creating an entity and calling ->validate on it, but it looks like this is only used in form validation. The constraint added to DecimalItem does not validate precision and scale, so that means API clients don't get the same validation as form submissions, which is a bug. Can someone search to see if we have an existing issue for that, and if not create a follow-up? We shouldn't be validating this in a form validation callback.

  11. +++ b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
    @@ -34,17 +34,104 @@ public function testValidStep($value, $step, $expected) {
    +      [20, '-0.00000000900000000000'],
    

    Yep, we need some non numeric values here to test they return 0, or even better, throw an exception

pooja saraah’s picture

StatusFileSize
new17.22 KB
new2.96 KB

Attached patch against Drupal 9.5.x
Addressed the point 1,2,3,4,5,7,8 on #267
the Point 6,9,10,11 needs to be worked

smustgrave’s picture

Status: Needs work » Needs review
StatusFileSize
new4.24 KB
new17.33 KB

Addressed issues 1-9 + 11 from #267 only one I need a little guidance on is 267.10 not sure what to best search for to find that issue.

darvanen’s picture

Opened #3300348: Decimal precision and scale are not validated when submitted via API as a follow up to cover 267.10, no need to postpone either issue in my opinion.

jaime@gingerrobot.com’s picture

Thankyou Smustgrave #269 works for me for saving integers that have no min value on the fields on Chart Module. Also able to save the field length on a new text field on a node that was default 255 then wouldn't save.

smustgrave’s picture

If it solves your issue can you move to RTBC and let’s see if we can get it committed!

jaime@gingerrobot.com’s picture

Status: Needs review » Reviewed & tested by the community
maxilein’s picture

Patch works fine for me.

tgoeg’s picture

#269 applies to 9.3.21 cleanly as well and works as expected here.

Please merge this, this is a major bug that's been open for 8 years now, had several rounds of reviews and tests and the solution in #269 is definitely better than the current state in core (if not the final fix).
Improvements for corner cases can be added later anyway.

Thanks for all your efforts!

maxilein’s picture

#269 applies to 9.4.5 on php 8.1 as well.

vinaymahale’s picture

StatusFileSize
new39.29 KB
new21.75 KB

Patch #269 works for me on D9.5.x-dev.

Before applying patch:

SS before patch

After applying patch:
SS after patch

Looks Good to merge!

quietone’s picture

Status: Reviewed & tested by the community » Needs review

Thanks everyone for working on this. This looks quite close but I noticed something that still needs to be done.

There were changes requested to the patch in #267. There have been two patches since then, to address those issues. However, there has not been a review of those changes. We should have confirmation that all those changes were made and they are correct. Setting back to NR for a code review of the patch.

xjkwak’s picture

Patch #269 works for me too on Drupal 9.4.6, however, it crashed when I added more digits than the set for the field. I fix it by adding a custom mix and max for the field:

function my_module_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
  $field_definition = $context['items']->getFieldDefinition();
  if ($field_definition->getType() == 'decimal') {
    $element['value']['#attributes'] = [
      'min' => 0,
      'max' => 99999999,
    ];
  }
}

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

LionKingMC’s picture

Hello everyone,

Thanks for trying to solve this important issue.

For those looking for a solution away from having to use patches, I found a work-around by using a module called https://www.drupal.org/project/fraction. It solves the issue by storing the numbers as fractions in the background with the option of working in decimals. It works for me building a website needing numbers having 8 numbers to the right of a decimal.

nod_’s picture

Status: Needs review » Needs work

Patch doesn't apply to 10.1.x

pradhumanjain2311’s picture

Status: Needs work » Needs review
StatusFileSize
new17.28 KB

Fix Patch #269 for 10.1.x.
Please review.

smustgrave’s picture

Status: Needs review » Needs work

Thank you for the patch but please upload an interdiff so we know what's changed.

pradhumanjain2311’s picture

@smustgrave
Actually the patch was unable to apply on 10.1.x that's why i changed the files manually.

smustgrave’s picture

Status: Needs work » Needs review
StatusFileSize
new0 bytes
new17.36 KB

You should still be able to supply an interdiff.

Rerolled #269 for D10.1

Putting in NR if someone can confirm the issues in #267 have been addressed per #278 request.

smustgrave’s picture

Apologize @pradhumanjainOSL believe you are correct.

fenstrat’s picture

StatusFileSize
new804 bytes
new17.36 KB

I've reviewed the changes requested in #267 as implemented in #269 and they all make sense to me.

The only thing #269 left outstanding was about the validation via the API, but that now has a follow up: #3300348: Decimal precision and scale are not validated when submitted via API.

When reviewing I found a minor 80 char line wrap issue, attached fixes that.

I hit this when storing map lat/lng values with precision 10 and scale 8. The changes here allow field values to pass validation.

gaurav-mathur’s picture

StatusFileSize
new35.98 KB
new43.13 KB

Applied patch #288 successfully on drupal version 10.1.x,refer to the screenshots.
Thank you

fenstrat’s picture

Status: Needs review » Reviewed & tested by the community

Marking as RTBC, which I should have done in #288.

quietone’s picture

Status: Reviewed & tested by the community » Needs work

I have not reviewed the code, just the comments.

The comments refer to the default precision for PHP and also an example precision, 'for example, 15'. I am not convinced that that is needed in the comments. I think they can be moved to being mention in an example.

The string 'the precision guaranteed by PHP' could be improved. Since the precision can be set in the ini file we should say it is a configuration value. Something like 'Floats are limited to the precision set in your PHP configuration'.

  1. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,68 @@
    +   * BCMath (https://secure.php.net/manual/en/book.bc.php).
    

    This should be an @see in the correct place in the doc block.
    See https://www.drupal.org/docs/develop/standards/php/api-documentation-and-...

  2. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,68 @@
    +   *   The value to normalize. If this is a string, it must be formatted as an
    +   *   integer or a float.
    

    s/If this/If it/
    The last sentence is needs work to make it easier to read. I am just too tired right now to make a suggestion.

  3. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,68 @@
    +    // Convert non-strings to strings, for consistent and lossless processing.
    

    Not sure this is needed. It is repeating the information in the summary line of the doc block.

  4. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,68 @@
    +   *   Number of significant decimal digits. Floats are limited to the precision
    

    s/Number/The number/

  5. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,68 @@
    +   *   Number::countSignificantDecimals(100.12345678901234567890) returns 11 but
    

    These are example and should be in a clear example sections and using @code and @endcode

  6. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -9,6 +9,68 @@
    +    // If no decimal separator is encountered, the step is an integer and the
    

    What does 'step' mean here?

  7. +++ b/core/lib/Drupal/Component/Utility/Number.php
    @@ -17,11 +79,20 @@ class Number {
    +   *   The step scale factor. Must be positive. If this is a string, it must be
    

    It can be <= 0, it is just the the method returns FALSE in that case.

ameymudras’s picture

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Seems points in #291 have been addressed.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php
@@ -69,7 +69,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
-      '#max' => 32,
+      '#max' => ini_get('precision') <= 10 ? 10 : ini_get('precision'),

@@ -79,7 +79,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
-      '#max' => 10,
+      '#max' => ini_get('precision'),

I think we need a update function here for existing fields as we're changing the maximum so current configuration can be invalid. Not sure what we should be doing with existing decimal fields...

kiwimind’s picture

Having come across this issue on 9.5.8, applying the patch from https://www.drupal.org/project/drupal/issues/2230909#comment-14626332 has solved this issue.

Prior to the patch we were unable to set a default value of a trillion (1 with 12 zeroes) on a 15,2 decimal field. After the patch it has saved as expected.

andypost’s picture

+++ b/core/lib/Drupal/Component/Utility/Number.php
@@ -9,6 +9,71 @@
+      // If the float has less significant decimals than the number we can
+      // guarantee, convert it to a string directly.
+      if (preg_match(sprintf('/^\d+\.\d{1,%d}$/', ini_get('precision')), (string) $number)) {
+        return (string) $number;
+      }
+      // For floats with more significant decimals than the number we can
+      // guarantee, discard those that are not guaranteed.
+      return rtrim(number_format($number, ini_get('precision'), '.', ''), '0');

+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php
@@ -69,7 +69,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
       '#title' => $this->t('Precision'),
       '#min' => 10,
-      '#max' => 32,
+      '#max' => ini_get('precision') <= 10 ? 10 : ini_get('precision'),

@@ -79,7 +79,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
       '#title' => $this->t('Scale', [], ['context' => 'decimal places']),
       '#min' => 0,
-      '#max' => 10,
+      '#max' => ini_get('precision'),

+++ b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php
@@ -34,17 +34,107 @@ public function testValidStep($value, $step, $expected) {
+   * @covers \Drupal\Component\Utility\Number::normalize
...
+  public function testNormalize($number, $expected) {
+    $this->assertEquals($expected, Number::normalize($number));
...
+   * Provides data to self::testNormalize().

Checking requirements should go to report/status instead of runtime

If you need properly configured server no reason to scatter this checks all over codebase

Also a test should use permutations for precision configured in field and via ini, I mean missing case when ini_get returns wrong data or it <10

andypost’s picture

Also this code should be overridable from contrib to allow override math via PHP bc, gmp, decial extensions

pwolanin’s picture

@anypost - this important patch has been waiting for 7+ years to get committed. we should not expand the scope. If you want to e.g. be able to use BC math, please open a follow-up issue.

samaphp’s picture

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

vinaymahale’s picture

Status: Needs work » Needs review

Any more work need to be done here?

smustgrave’s picture

Status: Needs review » Needs work

#294

and #296 or follow up least opened.

kopeboy’s picture

I'm not an expert developer and couldn't read all the comments and patches, but I hope this can help.

I was appalled to see I couldn't save web3 wallet balances to my Drupal site (there a lot of decimals places) even if I could easily define my "balance" field (in my custom entity's BaseFieldDefinition) with precision = 65 and scale = 30 (which correctly creates a column in the database with Type: DECIMAL, Lenght: 65,30)...
These values will come from APIs, but while testing with a manual form input (or even by setting the value programmatically though an ECA plugin) a value of 0.123456789012345678901234567890 would be magically converted to 0.123456789012350000000000000000 in the UI AND database (without validation warnings).

So I checked core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php myself and I found that I could fix the problem just by commenting lines 122 to 127:

  /**
   * {@inheritdoc}
   */
  public function preSave() {
    $this->value = round($this->value, $this->getSetting('scale'));
  }

Note that scale should be already defined by this (right?):

  public static function baseFieldDefinitions(EntityTypeInterface $entity_type): array {

    $fields = parent::baseFieldDefinitions($entity_type);

    $fields['balance'] = BaseFieldDefinition::create('decimal')
      ->setSettings(array(
        'type' => 'numeric',
        'unsigned' => TRUE,
        'precision' => 65,
        'scale' => 30,
        'not null' => FALSE,
        'description' => t('the decimal integer amount'),
      ))
      ->setLabel(t('Balance'))
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

So why do we want to round at all when we are already forcing the precision and scale in the field (storage) definition?
From https://www.php.net/manual/en/function.round I immediately read

round — Rounds a float

,
but this is a decimal!!

Then shouldn't that be just an optional Manage Display's setting for decimal fields?
Also, why isn't the round function called for float number fields (FloatItems.php file in the same folder) instead?

tgoeg’s picture

Also not a dev, but I think I can at least clear up things a little.

First, this is not a decimal (as strange as it may sound). Your number is a float (mathematically) and SQL stores it as an exact data type DECIMAL with a given precision (which in fact is a float). There is an explicit FLOAT data type in SQL, but that is not guaranteed to be exact.

The basic underlying problem is that computers "think" binary and not decimally. Representing an arbitrary number with lots of places after the decimal point will inherently produce errors (depending on which number it is, as some ("machine numbers") map cleanly to binary base and some do not. That's why people here see this only with select numbers). See https://en.wikipedia.org/wiki/Round-off_error for details.

The internal representation of a number 0.123456 might be 0.123456789 because of rounding errors. That's why rounding to the exact given scale does make sense. It's no actual rounding of the given number in the human sense. It just makes sure the number is exactly the one entered and has exactly that many decimal places as configured.

Does the patch in #2230909-292: Simple decimals fail to pass validation not fix it for you? That would be an important information.

asad_ahmed’s picture

StatusFileSize
new2.58 KB
new16.43 KB

I have made the changes as per comment #294. And write a update function in Field.install file but i am not sure where to place it, currently i have placed Field.install here core/lib/Drupal/Core/Field/Field.install. Please review it and give feedback if any. Thanks

maxilein’s picture

StatusFileSize
new33.24 KB
new22.62 KB
new22.42 KB
new29.75 KB

patch #305 applies to D10.2 but it does not work.

I tested a default decimal field (10,2) and a large decimal field (14,2)

Problem 1: Both act like a standard field with 10 digits.
Problem 2: the decimal separator is counted as one digit!
Problem 3: There used to be larger numbers possible than 14.

Please see attached screenshots.

maxilein’s picture

I just realized that #305 is just an addition.

#292 does not apply to 10.2

szeidler’s picture

StatusFileSize
new17.27 KB
new3.15 KB

Rerolling #292 for Drupal 10.2

maxilein’s picture

#309 helps to use large numbers. Thank you very much!!!! We can upgrade to 10.2!

If we get out of bounds there is just a nasty crash error. Not a warning in the gui.

1,123,123,123.00 - just ok.
123,123,123,123 -> crash


The website encountered an unexpected error. Try again later.

Drupal\Core\Entity\EntityStorageException: SQLSTATE[22003]: Numeric value out of range: 1264 Out of range value for column 'field_kostenschaetzung_value' at row 1: INSERT INTO "node__field_kostenschaetzung" ("entity_id", "revision_id", "bundle", "delta", "langcode", "field_kostenschaetzung_value") VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5); Array ( [:db_insert_placeholder_0] => 6479 [:db_insert_placeholder_1] => 19995 [:db_insert_placeholder_2] => fue_projekt_kosten [:db_insert_placeholder_3] => 0 [:db_insert_placeholder_4] => en [:db_insert_placeholder_5] => 123123123123 ) in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 817 of core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).

Drupal\Core\Database\StatementWrapperIterator->execute() (Line: 44)
Drupal\mysql\Driver\Database\mysql\Insert->execute() (Line: 1400)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->saveToDedicatedTables() (Line: 971)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->doSaveFieldItems() (Line: 718)
Drupal\Core\Entity\ContentEntityStorageBase->doSave() (Line: 486)
Drupal\Core\Entity\EntityStorageBase->save() (Line: 806)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (Line: 352)
Drupal\Core\Entity\EntityBase->save() (Line: 270)
Drupal\node\NodeForm->save()
call_user_func_array() (Line: 129)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers() (Line: 67)
Drupal\Core\Form\FormSubmitter->doSubmitForm() (Line: 597)
Drupal\Core\Form\FormBuilder->processForm() (Line: 325)
Drupal\Core\Form\FormBuilder->buildForm() (Line: 73)
Drupal\Core\Controller\FormController->getContentResult() (Line: 39)
Drupal\layout_builder\Controller\LayoutBuilderHtmlEntityFormController->getContentResult()
call_user_func_array() (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 627)
Drupal\Core\Render\Renderer->executeInRenderContext() (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext() (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 181)
Symfony\Component\HttpKernel\HttpKernel->handleRaw() (Line: 76)
Symfony\Component\HttpKernel\HttpKernel->handle() (Line: 58)
Drupal\Core\StackMiddleware\Session->handle() (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle() (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass() (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle() (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle() (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle() (Line: 36)
Drupal\Core\StackMiddleware\AjaxPageState->handle() (Line: 51)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle() (Line: 704)
Drupal\Core\DrupalKernel->handle() (Line: 19)

weseze’s picture

Same experience as maxilein.

The patch fixes our problem for storing correct numbers.
But if we try and enter bigger numbers we get a nasty WSOD and some SQL out-of-bound errors. Not a warning in the interface.

keszthelyi’s picture

@maxilein, @weseze There is another issue and patch for the exception thrown when the entered value is too big: https://www.drupal.org/project/drupal/issues/1003692

apolitsin’s picture

💥 Friends!
🎉🎉🎉🎉 10 years to this wonderful issue! 🎉🎉🎉🎉
🔥 Created 2 Apr 2014 at 00:19 MSK

Congratulations to everyone on this important event, especially parent #1 @exlin

☀️ Good day, everyone.

mstrelan’s picture

I guess that's supposed to be sarcasm. Seems that we need to take 309 and add the test from 305 and review against that. A merge request would probably be helpful. The hook_requirements in 296 hasn't been addressed either I don't think.

monaw’s picture

i am seeing the same problem in D10.2.5...is there a solution/fix yet?

anybody’s picture

I can sadly also confirm this core bug which basically makes using decimal fields with a default value impossible.

I'll ask our team if we can invest some time in the plan written in #314! :)

Grevil made their first commit to this issue’s fork.

grevil’s picture

https://git.drupalcode.org/project/drupal/-/merge_requests/579 still has their target on 9.2.x and can be removed safely.

New MR targeting 11.x can be found here: https://git.drupalcode.org/project/drupal/-/merge_requests/8282.

grevil’s picture

[...] and add the test from 305 and review against that

I think you meant the update hook provided in #305? Unfortunately, the update hook from #305 is pretty much a template implementation and won't work in its current state. I'll give it a shot.

The hook_requirements in 296 hasn't been addressed either I don't think

I'd agree with @pwolanin in #298 on this topic:

@anypost - this important patch has been waiting for 7+ years to get committed. we should not expand the scope [...]

Any further work on this should be done in a follow-up issue.

grevil’s picture

StatusFileSize
new20.03 KB

Looks like everything works as expected! Let's wait for the tests to succeed.

Here is a static patch of the current MR for the time being (which also applies to 10.2.x, 10.3.x and 10.4.x).

grevil’s picture

Status: Needs work » Needs review

Tests are all green now! Ready to get reviewed. 👍

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new89 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

anybody’s picture

Nice work! Just gave it a try and at least I can now save the numeric fields I wasn't able to safe before! :) YAY

Sadly the update hook still needs some work:
The SQL storage cannot change the schema for an existing field (field_example in commerce_product entity) with data.

grevil’s picture

Status: Needs work » Needs review
Issue tags: +no-needs-review-bot
StatusFileSize
new19.95 KB

Alright, the field storage should be updatable now, through the in 10.3.x newly introduced "updateFieldStorageDefinition()" method!(https://www.drupal.org/node/3397892)

Please review (no idea why the review bot set this to needs work, it definitely applies on 11.x).

Here is the new static patch (Update hook won't work for Drupal < 10.3.x).

smustgrave’s picture

Probably because there is a 9.2 MR and a mix of patches when probably should just be a single MR and no patches

maxilein’s picture

Thanks for all the sudden speed!!

I'd love to help by testing it.

Why are the automated tests only limited choices ( e.g. 10.1.x) ?
Is this already testable?

grevil’s picture

@maxilein yes, this is testable if you are using 10.3.x! Just apply https://www.drupal.org/files/issues/2024-06-04/2230909-325.patch through composer! :)

@smustgrave probably... I don't have the permission to close https://git.drupalcode.org/project/drupal/-/merge_requests/579, unfortunately. Anybody with proper permissions, feel free to close it in favor of https://git.drupalcode.org/project/drupal/-/merge_requests/8282.

maxilein’s picture

Sorry. I am still on 10.2
I don't have 10.3 yet

phthlaap’s picture

Status: Needs review » Needs work

The HOOK_field_widget_form_alter has been deprecated, I think the issue summary needs update.

function hook_field_widget_single_element_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
  $field_definition = $context['items']->getFieldDefinition();
  if ($field_definition->getType() == 'decimal') {
    $element['value']['#step'] = 'any';
  }
}

https://www.drupal.org/node/3180429

jcnventura’s picture

Status: Needs work » Needs review

No need to set this to "Needs work" if the workarounds in the issue summary are starting to get outdated. The workarounds are not really part of the issue, and are only there because of our failure to fix this in the last 10 years.

smustgrave’s picture

Cleaning patches for clarity

May need a rebase as the test-only feature is failing to run.

Left some small comments.

smustgrave’s picture

Status: Needs review » Needs work

Appears to have consistent Unit test failures.

But test-only feature is now running and showing coverage!

alfthecat’s picture

Just pitching in with my report: after patching Drupal 10.3 I have no more issues with large numbers with large decimal scales. Thanks everyone!

maxilein’s picture

MR !8282 applies to D10.3.1.
But the new max value seems a bit low.

I have a decimal field with precison 20 and the update_hook fails on it.
Is there a way to force the update?

drush updb
 -------- ----------- --------------- ----------------------------------------
  Module   Update ID   Type            Description
 -------- ----------- --------------- ----------------------------------------
  field    10001       hook_update_n   10001 - Update existing DecimalItem
                                       fields to match the new maximum value.
 -------- ----------- --------------- ----------------------------------------


 Do you wish to run the specified pending updates? (yes/no) [yes]:
 > y

>  [notice] Update started: field_update_10001
>  [error]  The SQL storage cannot change the schema for an existing field (field_with_prec_20 in node entity) with data.
>  [error]  Update failed: field_update_10001
 [error] Update aborted by: field_update_10001
 [error] Finished performing updates.

I also tried to use this module: https://www.drupal.org/project/precision_modifier
But it is not working (I opened an issue https://www.drupal.org/project/precision_modifier/issues/3460201 )

Is there a way to force this update?

maxilein’s picture

I am confused about the max size of the decimal numbers.
Why do we limit fields to 14?

Here https://www.drupal.org/docs/7/api/schema-api/data-types/decimal-numeric it says:

MySQL mappings in the schema.inc file:

'numeric:normal' => 'DECIMAL',

We use DECIMAL data type to store exact numeric values, where we do not want any rounding off, but exact and accurate values. A Decimal type can store a Maximum of 65 Digits, with 30 digits after decimal point.

If Maximum Digits (or Precision) is not defined, It defaults to 10 and if Scale is not defined, it defaults to 0.

All basic calculations (+, -, *, /) with DECIMAL columns are done with a precision of 65 digits.

MySQL also says it uses 65 digits.
https://dev.mysql.com/doc/refman/8.4/en/precision-math-decimal-character...

anybody’s picture

@maxilein Re: #336: Have you read all the comments above? That would be important.
Especially see #200!

My impression is, that this change is made to align the database schema max with the max php precision value?

Floats are complex, so this should definitely please be checked by someone who digged into this in the recent past. E.g. see https://stackoverflow.com/questions/14587290/can-i-rely-on-php-php-ini-p...

maxilein’s picture

Thank you Anybody. I really missed the setting in the php.ini

My question was: if the database gives us a specified DECIMAL column with a precision of 65 digits - why would we limit ourselves to 14?!
That does not make any sense. Even if it is the default precision limit of php.

Maybe we should think of an informative check on the Status page - just to let anybody more easily understand the situation:

php DECIMAL precision: 14 
This value can be increased in php.ini: precision = 14. 
The max float precision of your system is: 1.7976931348623E+308

echo "The max float precision of you system is: " . PHP_FLOAT_MAX;

maxilein’s picture

Testing the php.ini precision value with larger precision.

I have a decimal field with a precision of 20 scale 2.
(It dates back to when there was no limit by this patch). It has worked ever since.
And this is what confused me in the first place. I have a field with precision 20 while the php limit is 14.
But the field works within the limits 14 (even though has larger precision)

1) When I use the 20/2 field with a default php precision of 14:

I can enter numbers within the limits of 14 and I can enter something after the decimal point whith other digits than zero. Everything is working fine.

2) When I increase to a default php precision of 24/2 (without changing the field):

I can add save large numbers according to the 20/2 settings of the field: 123.123.123.123.123.123,00

BUT there is a bug: I can not enter other digits than zero behind the decimal point on ANY decimal field:
eg. 123.123.123.123.123.123,01 -> is not a valid number.
Even on other fields with default precision of 14 and when the numbers are really small (e.g. 890.34)

maxilein’s picture

Another observation when precision on the site is set to 24 (instead of the default 14). It probably has nothing to do with decimal fields, but maybe someone of you can think of other potentially unsafe situations.

False Status Message: Incompatible with PHP 8.3.6: https://www.drupal.org/project/diff/issues/3462791

the comparison from 8.3 in the info.yml file of the module is compared to a float and fails.
I have not yet get a feedback of the module developers. So I can't say how they do that comparison.

drupalfan2’s picture

Attached is the same patch as in #325 but with unix line endings.
Needed to be able to use the patch with an older version of the unix patch command line tool.

grevil’s picture

@phthlaap what is up with MR 579? Seems quite broken to me?

EDIT: Ah I see, it's on the same branch but against 9.2.x, that won't work, and we already have an existing one, which can be easily backported to 10.x.

@maxilein I think we should create a follow-up issue concerning the higher precision issues. Most people will stay at the default PHP precision level.

anybody’s picture

Re #3

@maxilein I think we should create a follow-up issue concerning the higher precision issues. Most people will stay at the default PHP precision level.

That's possible if we can be sure that the fixes from this issue are non-destructive. This should not break anything. Otherwise it has to be solved here.

grevil’s picture

Changes by @phthlaap actually introduced new phpunit test failures. I reverted them again.

grevil’s picture

Status: Needs work » Needs review

@smustgrave the unit test failures were introduced by unrelated changes done to the issue branch. The only failures left now are functional javascript failures. And I am fairly certain these are unrelated:

Drupal\Tests\ckeditor5\FunctionalJavascript\CKEditor5Test::testAttributeEncoding
WebDriver\Exception\UnknownError: Unable to execute request for an existing
session: java.util.concurrent.TimeoutException
Build info: version: '4.23.0', revision: '77010cd'
System info: os.name: 'Linux', os.arch: 'amd64', os.version:
'5.4.266-178.365.amzn2.x86_64', java.version: '17.0.11'
Driver info: driver.version: unknown

At least, I see no point on how this is caused by the changes done here.

grevil’s picture

Ha! I knew the test failures had nothing to do with this patch. Just some 11.x shenanigans going on. Rebasing fixed the tests.

Please review!

anybody’s picture

RTBC for MR !8282 once again, thank you @Grevil for bringing this right on track again!

@phthlaap please stop these destructive changes.

maxilein’s picture

I think we must make sure that the following

bug: I can not enter other digits than zero behind the decimal point on ANY decimal field:
eg. 123.123.123.123.123.123,01 -> is not a valid number.
Even on other fields with default precision of 14 and when the numbers are really small (e.g. 890.34)

is not caused by this patch.
After all it validates the numbers and I think it is very probable that the error is caused by some routine of this patch.
And if not: it still seems like a matter of validation to me.

Can someone point us to the code where this is done?

It is very easy to reproduce this bug:
Set precision to something like 24. Restart Webserver. You don't need to change or create a field. All decimal fields are affected.
Just try to add a digit (on an existing decimal field) behind the comma separator other than zero.

grevil’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

@maxilein sorry, it sounded like this problem was only reproducible with manually adjusting the precision in the php.ini. But you are correct, this is reproducible with the default precision value of "14":

Steps to reproduce

  • Leave the default php.ini precision value at "14"
  • Create a decimal field on an entity (e.g. node: article)
  • Set the field precision to "14"

Input / Output outcome

123123123123,01 is valid, and the entity can be saved without any problem.

123123123123123,01 is not valid and saving the entity will result in an error message saying:

{FieldName} is not a valid number.

123123123123123123,01 is not valid and will result in an EntityStorageException:

Drupal\Core\Entity\EntityStorageException: SQLSTATE[22003]: Numeric value out of range: 1264 Out of range value for column 'field_dezimal_value' at row 1: INSERT INTO "node__field_dezimal" ("entity_id", "revision_id", "bundle", "delta", "langcode", "field_dezimal_value") VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5); Array ( [:db_insert_placeholder_0] => 2 [:db_insert_placeholder_1] => 35 [:db_insert_placeholder_2] => article [:db_insert_placeholder_3] => 0 [:db_insert_placeholder_4] => de [:db_insert_placeholder_5] => 1.2312312312312E+17 ) in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 817 of core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).
Drupal\Core\Database\StatementWrapperIterator->execute() (Line: 44)
Drupal\mysql\Driver\Database\mysql\Insert->execute() (Line: 1400)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->saveToDedicatedTables() (Line: 971)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->doSaveFieldItems() (Line: 718)
Drupal\Core\Entity\ContentEntityStorageBase->doSave() (Line: 486)
Drupal\Core\Entity\EntityStorageBase->save() (Line: 806)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (Line: 354)
Drupal\Core\Entity\EntityBase->save() (Line: 277)
Drupal\node\NodeForm->save()
[...]

Back to needs work again. We also need new tests reproducing the beforementioned issue.

maxilein’s picture

@grevil. Yours is just an ugly unhandled but expected error. You tried to save a value larger than precision 14.

My bug is different: it is only about the 2 digits behind the comma (,00).

If default precision 14 is set you can save digits other than zero behind the comma.
If precision is larger (e.g. 24) you CANNOT save digits other than ,00 behind the comma. That is the bug.

I suppose there is some routine in this patch evaluating precision wrong therefore dropping the evaluation of digits other than ,00.

grevil’s picture

@maxilein no. The precision value is

The number of significant digits displayed in floating point numbers.

(https://www.php.net/manual/en/ini.core.php#ini.precision)

All the numbers I used for testing use a precision of 2.

And as I said in #342, we should create a follow-up issue for any problems with the decimal field when using a higher precision than "14", since in 99% of all cases you don't need a higher precision value.

maxilein’s picture

@grevil: you are mixing precision and scale.

The bug is messing up the scale validation.

And that must be connected to this patch. So it cannot be too difficult to look at it now.
I cannot find the code for it. That is why I asked for help to point to that.

anybody’s picture

This is the largest rabbit hole I've seen for a long time! ;)

There's another issue: If you have an existing decimal field with for example a precision of 20 you can't save changes to the field anymore, as there's a form validation error:

Precision must be smaller or equal 14

which is totally correct, but you're just locked in!

Trying to change the precision in the field.storage...yml as a hacky workaround is refused by the system by

The SQL storage cannot change the schema for an existing field (field_height in taxonomy_term entity) with data

which is also totally fine and correct, but makes the lock-in even harder.

Any ideas?

Maybe we can at least find a way to first fix the user-facing issues and afterwards do further clean-up?
This is a really problematic thing for Drupal as a framework for "simple" (haha) numeric fields! and we already have 10Y and 352 comments. Users being new to Drupal and running into this will be quite confused and unsettled...

Update: Setting
php_value precision 20 for php.ini was the only workaround that made it possible to save the field for me.

maxilein’s picture

@anybody it makes sense to only be able to update fields within the bounds of the set precision of your site.

In the past - before this patch - it was possible to create larger (>14 precision) fields. That is probably how you ended up with them. That is how I ended up with them.
I would the say the behavior is as intended and makes sense.

If you want to change a precision for an existing 20 field. Set php.ini to 20. Then use this module to rescale your precision. Then afterwards adjust your php.ini back to the intended size.

Also if your field is larger than default 14 or whatever you have set in your php.ini - you can save values to this (lager than the value in php.ini) field without problem as long as the value you are saving does not exceed the php.ini default precision. E.g. you can save a value with precision 14 into a field which has precision 20. Just the values that are transported to the field will never exceed the php.ini value.

hoporr’s picture

Patch #325 and #341 fail on an upgrade from
core 10.1.3
to
core 10.3.10

with the following message:
Do you wish to run the specified pending updates? (yes/no) [yes]:
> yes

> [notice] Update started: field_update_10001
> [error] The SQL storage cannot change the schema for an existing field (field_XYZ in node entity) with data.
> [error] Update failed: field_update_10001
[error] Update aborted by: field_update_10001
[error] Finished performing updates.

The patch had been run before on the 10.1 site, so in theory the storage was already changed. Which is strange, because in the update routine (in the patch) there is a check for "if there is a change", and the routine only runs then.

What could cause this?

Do you think the jump in version is too big (10.1 to 10.3)? Should I upgrade to an intermediate version, like 10.2.x first ?

hoporr’s picture

The reason why the patches fail on 10.3.10 is because of this new core issue:

The SQL storage schema for an existing field can be changed
https://www.drupal.org/node/3397892

The install routine in the patch needs this additional line so that it can run:
$decimalFieldStorageEntity->setSetting('column_changes_handled', TRUE);

anybody’s picture

@hoporr thanks! Could you incorporate that into the MR please?

anybody’s picture

@alexpott could you or anyone else with the appropriate permissions please close the old MR!579 (9.2) to not confuse people? I don't have permission.
Thanks!

hoporr’s picture

Patch #341 updated with the additional line in "install":

$decimalFieldStorageEntity->setSetting('column_changes_handled', TRUE);

runs OK on my 10.3.10 system

anybody’s picture

Thanks @hoporr I incorporated that into the MR.

hoporr’s picture

There is another issue on the 10.3.10 systems.
The above patch allowed the "install" function to update the field setting, and change the precision of the field.

However, now on the "status" page, you get a bunch of warnings like this:

Mismatched entity and/or field definitions
The following changes were detected in the entity type and field definitions.
* The node.field_XYZ_val field needs to be updated.
* The node.field_XY_2_val field needs to be updated.

The normal way to solve this is to go into the edit screen of the decimal field and just save it again.
However, there the precision is now set to 16.

And the entity system throws this error:
Precision must be lower than or equal to 14.

So you cannot re-save the field with that higher precision.

It seems like somewhere we now would have to change the entity system as well, so that longer precision are not throwing this error?

Any ideas ?

hoporr’s picture

Further on #361:
Interestinly: I went into another drupal 10.3.10 system, without this patch, created a new digital field and was able to crank up the precission past 14 [to 16] and the system did not complain.

Which makes me think that this "limit to below 14"somehow comes from this patch?

I do see this comment in the patch:
+ * The value to normalize. If its a string, it must be formatted as an
+ * integer or float. Float with a higher number of significant decimals
+ * than the precision value from the PHP runtime configuration
+ * [default: 14] will lose the additional precision as PHP does not
+ * guarantee that precision value.

so the 14 seems to be important.

Is there anything in this patch that may cause the entity system limit the precision it to 14, whereas standard drupal does not?

hoporr’s picture

#361, #362 continued.
This issue will only occur if you had the fields set before to a value larger than the system "precision" max.
In our case, we have precision of 16, and that appears to be larger than what php can safely store (per comment in the code it is 14).

So, looking at the code, the "field_udpate_1001" should have caught this and reset those fields to that value 14.
Apparently it did not, so something else must still be going on...
I'll check deeper the next few days.

hoporr’s picture

#363 continued

I did some more digging. In our patches, the hook_update_n 10001, exists to reduce the the precision down to the system PHP max precision settings, which is 14 digits. In our case, we indeed had some fields that were 16, so the hook_update tried to reduce this.

I did find a solution for this, but I am not familiar enough with the whole config system etc to suggest that this would work for all. I would ask some of the other developers to review this before this should go into some patch.

In the current patch, we use this code to update the precision, which is also the correct suggested way here:
https://www.drupal.org/node/3397892

$decimalFieldStorageEntity->setSetting('precision', $newPrecisionValue);
$decimalFieldStorageEntity->setSetting('scale', $newScaleValue);
$decimalFieldStorageEntity->setSetting('column_changes_handled', TRUE);
// Update the field storage definition:
\Drupal::service('entity.definition_update_manager')->updateFieldStorageDefinition($decimalFieldStorageEntity);

However, I found that the patch ran without errors, but the value of the precision did not change. That is, the update does not do what it is supposed to do.

I then found another module called "Precision Modifier" and looked how they do it:
For a given field:
1) Grab the data in the DB tables, and store it
2) Empty the Table
3) run code to change precision (other code than what is in the patch)
4) Reinsert the data

I did not feel comfortable with "taking out the data in the tables", so I tried to do it without.

So I wrote a hook_update_n in one of my own modules, took the code from the current patch for the update, and replaced the part in the loop/if-statement that changes the field defintion like this with code similar to the PrecisionUpdate module ( in PrecisionUpdateService)

...
 foreach ($decimalFieldStorageEntities as $field => $decimalFieldStorageEntity) {
     /** @var \Drupal\field\Entity\FieldStorageConfig $decimalFieldStorageEntity*/

     $precision = (int) ini_get('precision');
     $precisionMax = max($precision, 10);

     // Adjust the precision value, if it is higher, than the allowed
     // initial precision configuration option value:
     $precisionValue = (int) $decimalFieldStorageEntity->getSetting('precision');
     $newPrecisionValue = $precisionValue > $precisionMax ? $precisionMax : $precisionValue;

     // Adjust the scale value, if it is higher, than the allowed
     // initial precision configuration option value:
     $scaleValue = (int) $decimalFieldStorageEntity->getSetting('scale');
     $newScaleValue = $scaleValue > $precisionMax ? $precisionMax : $scaleValue;

     // Set the new precision and scale values if they changed:
     if ($precisionValue !== $newPrecisionValue || $scaleValue !== $newScaleValue) {

       // code from patch, does apply but does not change
	   
       // $decimalFieldStorageEntity->setSetting('precision', $newPrecisionValue);
       // $decimalFieldStorageEntity->setSetting('scale', $newScaleValue);
       // $decimalFieldStorageEntity->setSetting('column_changes_handled', TRUE);
       // Update the field storage definition:
       // \Drupal::service('entity.definition_update_manager')->updateFieldStorageDefinition($decimalFieldStorageEntity);

       // Taken from module "Precision Motifier" : precisionModifierService:

       $settings = [
         'precision' =>  $newPrecisionValue,
         'scale' => $newScaleValue,
          // 'column_changes_handled' => TRUE,                //    appears not needed if you presave the config ?
       ];

       $config = \Drupal::configFactory()->getEditable('field.storage.node.'. $decimalFieldStorageEntity->get('field_name') );
       $config->set('settings', $settings)->save();

       $decimalFieldStorageEntity->set('settings', $settings);
       $decimalFieldStorageEntity->save();
     }
   }

   \Drupal::entityTypeManager()->clearCachedDefinitions();
....

With this code, the fields were updated correctly (at least it seems so far).

What I found, the part where it presaves the $config was crucial.
After that you did not even have to have the column_changed_handled.

Again, I found this code works (for me), but am not sure if this good for a general solution.

maxilein’s picture

Shouldn't this patch respect the setting of the php_max value of each system?
Maybe I misunderstood you. But for some people a precision of 14 may not be enough. E.g. scientific data ...

I have mentioned this before: https://www.drupal.org/project/drupal/issues/2230909#comment-15732574
And I don't think that the field's UI respects the backend setting of php max value.

oily changed the visibility of the branch 9.2.x to hidden.

keoal’s picture

I am experiencing this issue with Drupal 10.4.5 is there a patch available for this version. I have tried to apply the other two but they won't work.

heddn made their first commit to this issue’s fork.

heddn’s picture

Status: Needs work » Needs review

A couple things here. 1) We shouldn't do hook updates that look at environment specific changes. Localdev vs live site likely have different php.ini settings. We shouldn't depend on them to be the same. 2) Why do we care what the display precision is set to for data storage? We shouldn't.

I've removed the hook update.

heddn’s picture

I streamlined the approach here a lot. This issue is all about validation. So changes to widgets shouldn't be in scope. And the storage of the data should be limited by the DB engine not by the configuration of PHP. So a lot got stripped out. If we need to address those things, they should happen in a dedicated issue.

godotislate’s picture

Status: Needs review » Needs work

MR failed tests.

I added some comments as well. I think the biggest issue on the MR is that there is a confusion between "precision", which is documented as the number of significant digits, (the number of all digits before and after the decimal), vs "scale", which is the number of digits after the decimal.

maxilein’s picture

#372 I completely agree.
And I don't understand this from #370 "We shouldn't make this field value dependent on an environment specific setting. It needs to be distinct."

We have three layers:
Database - php - drupal/gui

Why should they be distinct from each other?!

if php sends something too large for the db -> error.
If drupal sends something wrongly scaled -> error.
...

In my opinion we should specify rules on how they need to be in sync to each other.

heddn’s picture

Re #373: sorry about my poorly worded and unclear statement there. I'm not sure how to word it but making something like this dependent on a PHP ini settings, which is regularly set differently on localdev vs other environments seems wrong. This is the type of thing we would regularly do a hook_requirements around if it were a feature of php for image quality or a feature of mysql for data storage. But we do nothing like that here.

To add to that though, this precision value is something we can control ourselves via an ini_set at runtime. If we have a certain value configured for the field, then we should set the value before invoking it.

maxilein’s picture

Thank you.
Let me explain my observation - an outside view so to say.

I added the field decimal, because I needed a place for huge numbers with not so many positions behind the comma.
Then I had to realize that a field called decimal is very much limited in comparison to what I expected, or from my experience with other decimal functionality in other system would have expected.
That was my confusion number one.

Then I learned I can configure the field upon its creation. And before this validation bug here - or better without realizing that again - it was not problem to set the gui to whatever I needed. A huge number with a few digits behind the comma.
Entering data caused numbers to be crunched ... we all know that story here.

Then you wonder what to do and how it all depends on each other.

The default database allows for much more digits before and after the comma than default Drupal.
In my opinion that is not good. It is confusion number two.
Then you realize that also php may have a limit inbetween. That is confusion number three.

In my opinion we should make that clear somehow.
I think Drupal should ask during install how the user wants it to be set.
e.g.
DB limit is ...
php limit is ...
Drupal default is ...
Would you like to use Drupal default precision/scale for decimal fields or would you like to increase the default to the php and DB limits.

Then at least it would be transparent what happens here.

The next problem is the intransparent limitation of this patch. I can set some precicion/scale during field creation and it may crash because of underlying system differences ... which were not obvious during field creation.

Why should this patch not be adjusted to system parameter settings? it does not make sence to me to keep an artificially limited number of digits before or after the comma if the system already provides for that bandwidth.

And btw: thank you all very much for your efforts!

heddn’s picture

Our logic around how we use https://www.php.net/manual/en/ini.core.php#ini.precision to get a number is also in error. Its value can be negative 1 (-1). Which really messes with things if we assume it is a positive length to use with number_format.

nicholass’s picture

Patch failing, do we just need to merge the latest 11.x into this branch?

joevagyok’s picture

I re-rolled the contents of the MR over the 11.2.x branch. Currently fails to apply over 11.2 release. Added no interdiff, since there was no change to the implementation itself, only resolved conflict with the new target branch of 11.2.x.

keszthelyi’s picture

StatusFileSize
new17.29 KB
new1.07 KB

For core versions below 11.2 our projects are still using the patch from #309. However, that patch is not applying with every patching methods (it is failing for example when used with cweagans/composer-patches version 2). The reason is that since the patch was published, one of the test methods got a void return type. This new patch is a reroll of #309 that adds that void return type.

maxilein’s picture

#378 works for me. Thank you

chewie’s picture

StatusFileSize
new16.94 KB
new5.96 KB

Added rerolled patch for 10.5.3 core version. Normally, it should work for other versions, but I've tested only with the mentioned.

julien tekrane made their first commit to this issue’s fork.

someshver’s picture

StatusFileSize
new16.81 KB

I have re-rolled patch #378 for the Drupal core version 11.2.4.

julien tekrane’s picture

Thank you @someshver
It works for me in 11.2.4

maxilein’s picture

Thank you #383 also applies in drupal/core (11.2.5)

weseze’s picture

Patch #383 causes issues for me for "weight" fields that are now invalid. Without the patch this is not an issue.

iaacristobal’s picture

Can confirm Patch #381 works for Drupal 10.5.4. Decimals above 3500000 now validates properly

spadxiii made their first commit to this issue’s fork.

spadxiii’s picture

Just did a rebase on 11.x, but the merge-request still doesn't apply to 11.2.5

spadxiii’s picture

StatusFileSize
new17.41 KB

Here's a patch that should apply to 11.2.5

maxilein’s picture

#390 does not apply to drupal/core (11.3.1).

carlitus’s picture

Same problem, cannot apply to 11.3.1

nagy.balint’s picture

StatusFileSize
new16.01 KB

Rerolled #383 for Drupal 11.3.1

fskreuz made their first commit to this issue’s fork.

fskreuz’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Needs work

MR appears to be opened against 10.6 but there was already a 11.x

fskreuz’s picture

The 11.x diff did not apply cleanly to 10.6.x, so I created a separate MR for anyone who wants to use it on 10.6.x.

godotislate’s picture

Looks like there's been some amount of work done on the 11.x MR since I moved this issue to Needs work in #372, but no one has moved it back to Needs review, and the "Needs tests" tag is still applied.

I think for this issue to go forward to NR, someone will need to check the state of the 11.x MR to check that adequate test coverage is in place and MR comments have been addressed.

maxilein’s picture

Here is the patch file for #400 which is the migrated #393 (from rerolled #380) to apply to 11.3.2.
It applies for me and large numbers seem to be working - although the pipeline still complains.
Needs work and proper investigation, why it fails.

maxilein’s picture

Now thinking about better tests:

1. Refined Problems Endangering the Solution (No BCMath)

The string-based scaling approach is fraught with edge cases.

The "Trailing Zero" Mismatch: Value: 1.20 (2 decimals -> scale 100)

Step: 0.1 (1 decimal -> scale 10)

If the logic blindly scales both by their own decimal count rather than the maximum of the two, the math breaks.

Correction required in code: The code must calculate $scale = max($value_decimals, $step_decimals) and apply that same scale to both. If the patch fails to do this, it is broken.

Scientific Notation Failure: If the code looks for a dot . to count decimals, it finds 1.

It scales by 10.

Result: 1.0E-7 * 10 = 0.000001.

This is clearly wrong (should be integer 1).

Risk: The regex used to count decimals must explicitly handle E- syntax, or the logic fails completely.

The PHP_INT_MAX Hard Ceiling

:

If the code calculates the multiplier based on string length or decimal count without normalization:

PHP casts (string) 1e-7 to "1.0E-7".

Without bcmath, we cannot solve the overflow issue. This is an accepted trade-off of this patch. However, the code must fail gracefully (return FALSE) rather than producing a PHP Fatal Error or a silent false-positive.

2. Analysis of Test Coverage & Suggestions

The existing tests are insufficient because they test "happy paths" of the fix. We need "destructive" tests that specifically target the weaknesses of string manipulation and floating-point modulo arithmetic.

This test suite should cover every weakness listed: binary incompatibility, integer overflow boundaries, scientific notation parsing, and sign-logic.

I will put them into:
core/tests/Drupal/Tests/Component/Utility/NumberTest.php

/**
   * Tests Number::validStep() with complex floating point edge cases.
   *
   * This data provider targets specific weaknesses in floating point math
   * (IEEE 754) and string-based validation logic (scaling/overflows).
   *
   * @dataProvider providerTestValidStepEdgeCases
   */
  public function testValidStepEdgeCases($value, $step, $offset, $expected, $message) {
    $valid = Number::validStep($value, $step, $offset);
    $this->assertEquals($expected, $valid, $message);
  }

  /**
   * Data provider for testValidStepEdgeCases.
   */
  public function providerTestValidStepEdgeCases() {
    return [
      // -----------------------------------------------------------------------
      // GROUP 1: Binary Incompatible Floats (The "0.1 + 0.2" problem)
      // -----------------------------------------------------------------------
      // 0.3 is not exactly representable in binary.
      // 0.3 / 0.1 often results in 2.999999... or 3.00000004 in float math.
      // A robust string-scaler must handle this.
      ['0.3', '0.1', 0.0, TRUE, 'Standard binary incompatibility: 0.3 step 0.1'],
      // 0.7 / 0.35 = 2.
      ['0.7', '0.35', 0.0, TRUE, 'Standard binary incompatibility: 0.7 step 0.35'],
      // The original issue report case.
      ['20.12345678', '0.00000001', 0.0, TRUE, 'Original issue #2230909 case'],

      // -----------------------------------------------------------------------
      // GROUP 2: Scientific Notation & String Parsing
      // -----------------------------------------------------------------------
      // PHP casts small floats to scientific strings (e.g. "1.0E-5").
      // The logic must detect the "E-5" and calculate scale = 5, not 1.
      ['0.00001', '1.0E-5', 0.0, TRUE, 'Scientific notation step matching value'],
      // Input value is scientific, step is standard decimal.
      ['1.0E-5', '0.00001', 0.0, TRUE, 'Scientific notation value matching step'],
      // Mismatch check: 1.0E-5 is NOT a multiple of 3.0E-6.
      ['1.0E-5', '3.0E-6', 0.0, FALSE, 'Scientific notation mismatch'],

      // -----------------------------------------------------------------------
      // GROUP 3: Trailing Zero Inconsistency & Scale Mismatch
      // -----------------------------------------------------------------------
      // If the code scales "1.00" by 100 and "0.5" by 10 separately, it fails.
      // It must normalize to the MAX precision (scale both by 100).
      ['1.00', '0.5', 0.0, TRUE, 'Trailing zeros in value (1.00) vs step (0.5)'],
      // Inverse case.
      ['1.0', '0.50', 0.0, TRUE, 'Trailing zeros in step (0.50) vs value (1.0)'],
      // High precision step vs Low precision value.
      ['10', '0.0001', 0.0, TRUE, 'Integer value (10) with high precision step (0.0001)'],

      // -----------------------------------------------------------------------
      // GROUP 4: Negative Operand Logic & Offset
      // -----------------------------------------------------------------------
      // (Value - Offset) % Step == 0
      // (-1.0 - (-2.0)) = 1.0. 1.0 / 0.5 = 2. Valid.
      ['-1.0', '0.5', '-2.0', TRUE, 'Negative value with negative offset (min)'],
      // (-1.2 - (-2.0)) = 0.8. 0.8 / 0.2 = 4. Valid.
      ['-1.2', '0.2', '-2.0', TRUE, 'Negative decimal value with negative offset'],
      // Sign flip check: -1.2 is NOT a valid step if offset is 0.
      ['-1.2', '0.5', 0.0, FALSE, 'Negative value mismatch against positive step'],

      // -----------------------------------------------------------------------
      // GROUP 5: Zero-Offset / Start Value Validation
      // -----------------------------------------------------------------------
      // Min: 1.1, Step: 2.0.
      // Valid series: 1.1, 3.1, 5.1...
      // Value 3.1: (3.1 - 1.1) = 2.0. Divisible by 2.0. Valid.
      ['3.1', '2.0', '1.1', TRUE, 'Non-zero offset validation (3.1 start 1.1 step 2.0)'],
      // Value 3.2: (3.2 - 1.1) = 2.1. Not divisible by 2.0. Invalid.
      ['3.2', '2.0', '1.1', FALSE, 'Non-zero offset invalid case'],

      // -----------------------------------------------------------------------
      // GROUP 6: Integer Overflow Boundaries (PHP_INT_MAX)
      // -----------------------------------------------------------------------
      // These tests verify if the "No BCMath" solution breaks gracefully or passes.
      // Note: On 64-bit systems, PHP_INT_MAX is ~9e18 (19 digits).

      // 1. Safe boundary (15 digits). Should Pass.
      ['99999999.9999999', '0.0000001', 0.0, TRUE, 'Large number within PHP_INT_MAX limits'],
      
      // 2. The Overflow. 
      // 100.00...01 (25 digits total). 
      // This CANNOT be converted to an int without losing the last digit.
      // If the code relies on (int) casting, this will fail or return a false result.
      // We explicitly expect it to likely FAIL or behave purely on float logic 
      // (which might incidentally pass due to precision loss, or fail).
      // However, for a Validator, we ideally want it to handle it.
      // '100.123456789012345678901'
      // This test serves as a warning. If the implementation casts to int, 
      // it will lose precision.
      // We expect TRUE here only if the system is perfect, but physically
      // without bcmath, it might fail.
      // Let's test a case that WOULD fail validation if precision is lost.
      // Value: 1.000...(20 zeros)...1
      // Step:  0.000...(20 zeros)...1
      // If treated as float, 1 + 1e-21 == 1. The tail is lost. 
      // It will look like a valid multiple of a larger step, or invalid.
    ];
  }

  /**
   * Tests behavior when the scaled integer exceeds PHP_INT_MAX.
   *
   * Without BCMath, this is the "Danger Zone".
   */
  public function testValidStepOverflow() {
    // A number with 22 digits. 
    // 9000000000000000000.99
    // Scaled by 100 -> 900000000000000000099 (21 digits).
    // PHP_INT_MAX is approx 9.22e18 (19 digits).
    // This value will overflow to float.
    $val = '9000000000000000000.99';
    $step = '0.01';
    
    // In a pure string/bcmath world, this is Valid.
    // In a float world, this is Valid.
    // In a "cast to int" world, this might overflow to a negative integer 
    // or capped max, causing the modulo to fail.
    
    // We assert TRUE, but this test is likely to fail if the patch 
    // blindly casts to (int).
    $this->assertTrue(Number::validStep($val, $step), 'Validation should succeed (or fail gracefully) for numbers exceeding PHP_INT_MAX');
  }
maxilein’s picture

Which brings us to the spots that will break by these tests:

While the patch fixes the original reported issue (simple decimals like 20.12345678), it introduces new bugs because it does not strictly handle the technical risks.

1. Scientific Notation Failure

The Code: The patch usually counts decimals by looking for a period (.).

// Typical logic in the patch
$decimals = strlen(substr(strrchr($value, "."), 1));

The Flaw:

  • Input: '1.0E-8' (Scientific notation for 0.00000001).
  • PHP String conversion: '1.0E-8' contains a dot, so it might count 1 decimal (0) or fail depending on implementation.
  • Input: '1E-8' (Valid float).
  • String check: No dot found.
  • Result: It calculates decimals = 0. It scales by 1.
  • Math: 1E-8 % 1 is not 0. Valid input fails validation.

2. Integer Overflow (The Fatal Error)

The Code: The patch performs math by casting to integer.

// Typical logic in the patch
$value_int = (int) ($value * $scale);

The Flaw:

  • Input: High precision or large value (e.g., 1234567890.1234567890).
  • Math: 12345678901234567890 (20 digits).
  • System Limit: 64-bit PHP int max is ~19 digits (9223372036854775807).
  • Result: The number overflows. PHP silently caps it at PHP_INT_MAX or flips it to a negative number.
  • Math: Wrong_Number % Step returns garbage. Valid input fails validation.

3. Trailing Zero Mismatch

// Typical logic
$value_scale = pow(10, $value_decimals);
$step_scale = pow(10, $step_decimals);

The Flaw:

  • Input: 1.00 (2 decimals) and Step 0.1 (1 decimal).
  • Value Scale: 100 $\rightarrow$ 100.
  • Step Scale: 10 $\rightarrow$ 1.
  • Math: It tries to compare integers derived from different scales (100 vs 1).
  • Result: 100 % 1 works by accident here, but complex offsets often fail this mismatched math.
maxilein’s picture

The Code in core\lib\Drupal\Component\Utility\Number.php in function validStep ~ line 117:
seems mathematically unsafe when an offset is involved. It uses a "heuristic" shortcut rule to fail fast if the value looks too precise. However, that shortcut causes false failures in valid scenarios

    // The step dictates the expected number of significant decimals.
    $expected_significant_decimals = static::countSignificantDecimals($step) + 1;

    // If the actual value has more significant decimals than expected, then it
    // has a higher precision than desired, so it isn't divisible by the step.

    $actual_significant_decimals = static::countSignificantDecimals($float_value);

    if ($actual_significant_decimals > $expected_significant_decimals) {
      return FALSE;
    }

Counter-Example (The "Offset" Problem): Imagine a form where:

  1. Step: 1 (Integer steps like 1, 2, 3...) -> countSignificantDecimals = 0.
  2. Offset (Min): 0.25 (Starts at 0.25).
  3. Valid Values: 0.25, 1.25, 2.25...

Applying the snippet's logic:

  1. Step Decimals: 0.
  2. Allowed Limit: 0 + 1 = 1 decimal place.
  3. User Input: 1.25 (This is a valid input: $1.25 - 0.25 = 1$).
  4. Actual Decimals: 2.
  5. Check: Is 2 > 1? YES.
  6. Result: return FALSE; (Invalid).

The proposed solution I will present below (it handles "too much precision" without needing this explicit check—if a value has extra, non-matching decimals, the modulo arithmetic will essentially return a non-zero remainder anyway) fixes this: it uses $max_decimals = max($value, $step, $offset). It scales everything to the precision of the most precise element (in this case, the offset 0.25).

  1. Scale everything by 100.
  2. Value: 125, Offset: 25, Step: 100.
  3. Math: (125 - 25) % 100 = 0.
  4. Result: TRUE (Valid).
maxilein’s picture

Here are the suggested changes in a patch. (Against the latest MR in https://git.drupalcode.org/issue/drupal-2230909/-/tree/2230909-testing-s... from #401)
I hope you all can review it thoroughly and we can finally put this into production very soon.

Besides addressing the problems discussed above:
It is also handling "Epsilon Neglect" (Floating Point Drift): if a calculation results in 4.00000000000001, a strict integer check often fails. The solution uses a "Two-Layer Defense" to stop this.
Defense A: The round() before (int) (Primary Path) In the standard scaling path, we do not just cast to int. We round first.

// If $value_scaled is 400.000000000001 due to float drift:
// (int) 400.000000000001 = 400 (Safe)
// (int) 399.999999999999 = 399 (unsafe truncation!)

// The solution does:
$value_int = (int) round($value_scaled); 
// round(399.999999999999) -> 400.0 -> (int) 400. Safe.

This ensures that microscopic floating-point noise is snapped to the nearest correct integer before the modulo check happens.

Defense B: Explicit Epsilon in Overflow Guard (Secondary Path)
If the numbers are too big for integers, we fall back to float math. Here, we cannot use == or %. Instead, we explicitly calculate the distance to the nearest valid step and check if it is within a tiny margin (1E-10).

// From the solution code:
return abs($double_value - $calculated_value) < 1E-10;

This effectively tells PHP: "If the difference is 0.00000000001, treat it as zero."

And it is Handling Negative Operand Logic
PHP's modulo operator % acts differently with negative numbers (e.g., -10 % 3 is -1, not 2 or 1). This causes sign-flip errors if not handled.

The solution neutralizes this by calculating the Absolute Distance rather than raw subtraction.

// We calculate the absolute distance between the value and the start (offset).
$numerator = abs($value_int - $offset_int);

// We check if that positive distance is divisible by the step.
return ($numerator % $step_int) === 0;

Works because:

  • Step: 10
  • Offset: 0
  • Value: -10
  • Raw Math: -10 % 10 = 0 (Passes).
  • Value: -12
  • Raw Math: -12 % 10 = -2 (Non-zero, but negative remainder).
  • Proposed Logic: abs(-12 - 0) = 12. 12 % 10 = 2. (Consistent positive remainder).

By forcing the numerator to be positive (abs), the modulo operation behaves consistently regardless of whether the user is typing negative values or using a negative offset.

maxilein’s picture

applied to 11.3.2 just fine. Thank you.

maxilein’s picture

Status: Needs work » Needs review

Since the patch is now on main, there is nothing holding us back testing and reviewing it.

smustgrave’s picture

Status: Needs review » Needs work

MRs need to be cleaned up before review, either closed or hidden.

Also none appear to be passing

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

rob230’s picture

The more recent patches / merge request cause integer fields to consider that a whole number below 10 is not valid.

Here's why (using 5 as the example):

$expected_significant_decimals = static::countSignificantDecimals($step) + 1;

Result: 1.

$actual_significant_decimals = static::countSignificantDecimals($float_value);

Result: 15

countSignificantDecimals(5.0) calls static::normalize(5.0), which does the following:

rtrim(number_format($number, 15, '.', ''), '0');

This generates the "number" (string): 5.000000000000001

rob230’s picture

The patch in #406 (applied on top of previous MR) does not have this problem, it works fine for integers less than 10 and higher precision numbers.

I think that's what's in merge request 8282? I'm applying it to 8.2 and it doesn't apply, but does look like it works if I backport it.

heddn’s picture

The last few comments are very confusing. Is all of the work in MR 8282? If it is, why is it still failing red and folks saying it works. Let's get everything added to an MR. Patches against an MR at this point just confuse things.

fskreuz changed the visibility of the branch 2230909-11.3.x to hidden.

fskreuz changed the visibility of the branch 2230909-testing-stable to hidden.

fskreuz changed the visibility of the branch 2230909-10.6.x to hidden.

fskreuz’s picture

- Rebased MR8282 with the latest upstream main branch. All focus should be on MR8282.
- Hid the MR14204 (was for 10.6.x), MR14280, MR14283 (Draft MRs), and the patches to simplify things.
- I could not cherry-pick over the changes from MR14280 and MR14283, they were conflicting when added to MR8282.
- I could not add the patch from #406, it's difficult to tell what it's changing from MR8282.

maxilein’s picture

"- I could not add the patch from #406, it's difficult to tell what it's changing from MR8282."

If you tell me how I could help ...

heddn’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests

This seemed like an excelent chance to have Claude help with re-adding the bits called out from #406 and beefing up test coverage. I've just pushed those changes. TDD is key to this work so I started with that first. Taking off the needs test tag.

berliner’s picture

StatusFileSize
new13.74 KB

Patch for 10.6.x based on MR 8282. Just for folks like myself who can't yet update to 11. The last patch I used based on MR 14204 broke the UI when using tabledrag with PHP 8.4, not sure why exactly, but I did find https://github.com/php/php-src/issues/18266 during my research and attributed it to that. MR 8242 seems to not be affected from what I see in my local setup.

anybody’s picture

Still one of the "most major" issues for any Drupal projects using Decimal fields definitely.

Just found #2413843: Drupal Decimal Field should support 'unsigned' setting. and I think not having a setting for UNSIGNED is also part of the (logical) issue.

Edit:
My assumption was wrong, I closed that issue WON'T FIX now, as:

The UNSIGNED attribute is deprecated for columns of type FLOAT, DOUBLE, and DECIMAL (and any synonyms); you should expect support for it to be removed in a future version of MySQL. Consider using a simple CHECK constraint instead for such columns.

https://dev.mysql.com/doc/refman/9.6/en/numeric-type-syntax.html

anybody’s picture

I fixed the merge conflicts. @godotislate there are some comments for you by @heddn, could you maybe have a look and reply?

Let's get this one fixed, we ran into it in a further project now, Decimal fields are really broken in Drupal due to this bug.

anybody’s picture

@heddn all green now!

The MR does not apply against 11.3.x, so I'm unsure what we should do in the meantime. #421 works, but I guess it's different from the MR implementation.

Should we create a separate MR against 11.3.x for the time being or try to get this merged into main and 11.3.x asap?

anybody changed the visibility of the branch 2230909-hacky-disable-validateNumber to hidden.

godotislate changed the visibility of the branch 2230909-simple-decimals-fail to hidden.

anybody changed the visibility of the branch 2230909-simple-decimals-fail to active.

anybody changed the visibility of the branch 11.3.x to hidden.

anybody’s picture

StatusFileSize
new1.03 KB

As the current MR against main does not apply again 11.3.x and #421 didn't work for me either (leading to other wrong validation errors) and I needed a solution, I prepared MR!15288 that simply disabled the number validation.

Patch attached until this is finally resolved. USE AT YOUR OWN RISK, KNOW WHAT YOU'RE DOING ;) This may result in database errors if invalid numbers are saved!

Anyway, let's get this one finished finally!

psf_’s picture

For D10.6.3 this work for me: (updated from issue description)

/**
 * Implements hook_field_widget_single_element_form_alter()
 *
 * Prevents validation of decimal numbers.
 *
 * @see https://www.drupal.org/node/2230909
 */
function innovapay_field_widget_single_element_form_alter(&$element, FormStateInterface $form_state, $context) {
  $field_definition = $context['items']->getFieldDefinition();
  if ($field_definition->getType() == 'decimal') {
    $element['value']['#step'] = 'any';
  }
}
hoporr’s picture

For 11.3, the warning by "anybody" about the quick patch sounded ominous, so I installed #421.

I did not notice any side-effects (on PHP 8.3). Very large decimals fail in the DB call, but the overall node-save did not proceed and blocked, so that was OK for me right now.

And look: we just now passed the 12 year anniversary of when this issue was opened...

maxilein’s picture

@anybody: I had to read 430 also twice: question for clarification:
This

Merge request !8282 Anybody updated 30 March 2026 at 18:47 #

added 5 commits

30f15ffb...02252678 - 4 commits from branch project:main
1387a5f9 - Merge branch drupal:main into 2230909-simple-decimals-fail

Compare with previous version

is the merge into D11.3 dev.
And the merge is green. So ok.

Can you confirm for everybody, please: THIS IS THE ENTIRE PATCH?

xjm’s picture

Let's hide approaches that we know are definitely not committable because of potential data integrity problems. ;)

xjm changed the visibility of the branch 2230909-11.3.x-hacky-disable-validation to hidden.

xjm’s picture

As far as I can see, MR 8282 does apply fine to main. We need the issue resolved in main before we can backport fixes to other branches, so let's please not confuse the issue with workarounds for older versions of Drupal. Instead, let's focus on review and testing of the current MR.

xjm’s picture

Status: Needs review » Needs work
Issue tags: +DrupalSouth 2026

Gave it an initial quick look with a couple points that can be addressed.