If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Question Engine 2:Numerical formats

From MoodleDocs

The Question Engine 2 structure allows implementation of new features for numerical question types ( numerical and calculated).

This page describes a possible implementation and its rationale.

The text should be readed as a personal summary of the work in progress and not as a textbook on computer language and real numbers representation.

It is related to an initial quiz forum discussion

Please put your comments on the forum.

See also

See also Question Engine 2:Numerical questions

Numerical question grading

There are 3 elements that can be graded in a numerical question

  1. the numeric value
  2. the unit used in relation to the numeric value
  3. the number format used to express the value

1 and 2 are addressed in Moodle 2,0 for questions created from edit_numerical_form.php.

3 can give a 0 grade if the student does not use one of the number formats allowed but there is no specific grading designed as the unit penalty since Moodle 2,0.

How to design the code flow for grading a numerical question

Ideally we should be able to sinulate the teacher grading approach which is about the following

  1. Find the numerical characters (+-123456...) and translate them in a number following locale numbering style(s) allowed to the students response. To be fair the style should be known to the students if they are different from their usual langage number style.
  2. Compare the translated number to the numerical good answer or the equivalent if they are expressed in authorized units allowed.
  3. Identify the units part of the response and verify if it is well written and one of the in authorized units allowed
  4. Verify if the unit is the correct one to use so that the response express the numerical value of the answer.

Identify the number in the response

The more variant ways you allow in expressing the numerical value, the more complex will be the decoding process.

When a teacher decode a student response, he can pinpoint errors in the number writing and exptract the most plausible numerical value from student response and grade separately the numerical value from the errors in the number syntax using a more or less complex grading approach.

Compare the response numerical value to answer numerical value

This comparison is easily done in computer code and the moodle code allow to set different tolerance and can even the teacher can even set another answer related to a common error (i.e. 2 * ) and give a specific grade.

Identify the unit in the response

Here the actual moodle code is quite limited as are limited the unit syntax that can be used. Just think of expressing a surface in squared mm This is why the multiple choice variant was added in 2,0 as the teacher could set complex math formula in the question text that the student could choose.

The numerical syntax is not the main objective of the numerical question and given the limits of the pattern recognition performance of the code, the use of two input fields (number and unit) to help the student to express the two part of the response as it helps moodle to identify them correctly.

It allows also the

  1. the use of more variant forms of expressing the numerical values ( fractions, time, degress)
  2. development of specific tool to express complex unities.

The events in the decoding steps

Given the new opportunities of the new engine qtype_numerical_walkthrough that extends qbehaviour_walkthrough, the code should reflect the different results obtained in the response analysis.

This is not an easy task that will complexify each time we will allow more flexibility in number and units formats.

Identify the number in the response

Find the numerical characters (+-123456...) and translate them in a number following locale numbering style(s) allowed to the students response. To be fair the style should be known to the students if it's different from their current language usage ( help icon).

Given the identified response input element , the code can detect that there are additional characters that are not used in the extracted number but this could result from accidental typing error but most often from badly written number which most will have its own penalty when grading for the numerical value.

Identify the unit in the response

Identify the units part of the response. There is no easy syntax analysis to detect syntax errors.

  1. field empty
  2. the correspondance with one units set in the question can be (and is)done later.

Compare the response numerical value to answer numerical value using the units factor

Compare the translated number to the numerical good answer or the equivalent if they are expressed in authorized units allowed.

This is done with the result that report the grade and the unit used to set the grade.

Verify if the unit from the response is the one used in setting the numerical grade


  1. if the unit is well written and one of the in authorized units allowed.
  2. if the unit is the correct one to use so that the response express the numerical value of the answer.

The unit_penalty reflects this step but a different message should be given following the 2 cases although the second one cannot happen with multichoice option.

The different number formats

Numerical quantity can be expresed as

  1. Integer without decsep and with or without thousandseps (many combinations related to number locale) as 1234 1 234
  2. Decimal with a decsep ( either , or . ) and with or without thousandseps (many combinations related to number locale) as 1234.456 1 234,456
  3. Exponent form with a decsep ( either , or . ) and without thousandseps ? as 1.23456 E3
  4. Fractional form the dessep is ' ' and without thousandseps as 1234 456/1000
  5. Angles as degree minute second
  6. Time as hour:minute:second
  7. Etc.

The international locale parameters defined are mainly related to the decimal form and constitue a long list as they are related mostly to the language.

I propose that we do not define our number parameters to these locale although in the help we could relate the decsep and tousand to the international locales.

The 2,0 formats allowed simultaneously by apply_unit() function are

  1. Integer without decsep and with or without thousandseps (many combinations related to number locale) as 1234 1 234
  2. Decimal with a decsep ( either , or . ) and with or without thousandseps ( ' ' or ',' if decsep == '.' ) as 1234.456 1 234,456
  3. Exponent form with a decsep ( either , or . ) and with thousandseps ( ' ' or ',' if decsep == '.' ) as 1.23456 E3

in 1,9 the decsep ',' was not allowed

Identify the number in the response

The function apply_value($response,$unit) used to identify the number unit combination and retrun the numerical value for grading as evolved in the different moodle version allowing more complete number analysis but without a significant output to apply a number format penalty other than 0 or 1 value.

Grading the different formats can be applied either at the

the question level

using a more complex number analysis with an output that can be used to apply a number format penalty

the answer level.

As grading is done at the answer level adding an additional element to the answer (i.e. tolerance) was first explored as a solution.see lower in this page However this solution implies that the user set the number format analysis pattern to apply.

So improving the flexibility of the number analysis at the question level appers as a better solution

Improving the number analysis at the question level

To apply a more complete number analysis we need at least the following parameters that should be stored in a new database table.

  1. usual id and question
  2. the name of the analysis (either specific as 'decimal' or a mixed combination )
  3. the order in this variant in the analysis and the grade associated with the result as
    1. decsep = . with thousand = , and grade 100% of the given answer fraction
    2. decsep = . with thousand = ' ' and grade 90% of the given answer fraction
    3. decsep all and and grade 80% of the given answer fraction
  4. params used in this numerical variant most often decsep and thousands but could be also the exponential form (alloed or not) or new ones like / for fraction.

The field 'name' should be related to a number class that can handle the necessary grading, saving and retrieving steps.

We need to define these number classes before going further Pierre Pichet 15:30, 31 May 2011 (WST)

 if ($oldversion < 2011052200 ) { //New version in version.php

    /// Define table question_numerical_options to be created
        $table = new xmldb_table('question_numerical_formats');

    /// Adding fields to table question_numerical_formats 
       $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
        $table->add_field('question', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
        $table->add_field('name', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
        $table->add_field('order', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
        $table->add_field('grade', XMLDB_TYPE_NUMBER, '12, 7', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
        $table->add_field('params', XMLDB_TYPE_TEXT, 'small', null, null, null, null);

    /// Adding keys to table question_numerical_formats
        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
        $table->add_key('question', XMLDB_KEY_FOREIGN, array('question'), 'question', array('id'));
    /// Conditionally launch create table for question_calculated_options
        if (!$dbman->table_exists($table)) {
            // $dbman->create_table doesnt return a result, we just have to trust it
        upgrade_plugin_savepoint(true, 2011052200, 'qtype', 'numerical');

The info about the different parameters ( like the official locales decsep amd thousandsep ) and analysis coding could be stored in an additional numerical/number_analysis.php file

When sufficiently developped, this number analysis code should become a question engine function or type i.e graded as ...

Implementing in 2,1 release

Given the delays we need to set the minimal changes that will set the necessary code structure for allowing the various aspects discussed in the upper part of this page even if as a first version we do not all of them.

Locale ( i.e decsep) is defined at the question level not at the site level

We need

  1. set the locale parameters in a question related database table (i.e. as unit are)
    1. create the database structure
    2. the save and read functions
    3. create the edit_numererical_fomr code to set the paramters at the question level
    4. eliminate the construct functions that defined the locale default as site locale default
    5. set the apply_value code to multiple decseps and or thousandseps
    6. implement a minimal numerical format grading
      1. a first choice as either . or , decsep grade 100%
      2. a second choice as the other decsep graded as desired (0-100%)
    7. default the pretty number decsep to PHP standard i.e. '.' or the first choice .


As the unit uses already a specific input field in 2,0 , we should continue this in 2,1 as in the future this will allow us maximum flexibility.

Technically, we should use the coding structure developed by Tim in the multianswer renderer to continue the use of multichoice regular display as it allows HTML and even Latex rendering.

We should implement unit grading as done in 2,0 i.e. the response numerical value is tested against the answer value using all defined units.

We need to create the corresponding response states.



The real numbers include all of the measuring numbers. Real numbers are usually written using decimal numerals, in which a decimal point is placed to the right of the digit with place value one. Each digit to the right of the decimal point has a place value one-tenth of the place value of the digit to its left.



represents 1 hundred, 2 tens, 3 ones, 4 tenths, 5 hundredths, and 6 thousandths.

In the US and UK and a number of other countries, the decimal point is represented by a period, whereas in continental Europe and certain other countries the decimal point is represented by a comma.

Zero is often written as 0.0 when it must be treated as a real number rather than an integer.

In the US and UK a number between −1 and 1 is always written with a leading zero to emphasize the decimal.

Negative real numbers are written with a preceding minus sign: -123.456.

Every rational number is also a real number. It is not the case, however, that every real number is rational. If a real number cannot be written as a fraction of two integers, it is called irrational number.

A decimal that can be written as a fraction either ends (terminates) or forever repeating decimal, because it is the answer to a problem in division.

Thus the real number 0.5 can be written as 1/2 and the real number 0.333... (forever repeating threes, otherwise written 0.overline|3}}) can be written as 1/3. On the other hand, the real number π , the ratio of the circumference of any circle to its diameter, is pi = 3.14159265358979 Since the decimal neither ends nor forever repeats, it cannot be written as a fraction, and is an example of an irrational number.

Thus 1.0 and 0.999... are two different decimal numerals representing the natural number 1.

There are infinitely many other ways of representing the number 1, for example 2/2, 3/3, 1.00, 1.000, and so on.

Every real number is either rational or irrational. Every real number corresponds to a point on the number line.

When a real number represents a measurement, there is always a margin of error. This is often indicated by rounding or truncate|truncating a decimal, so that digits that suggest a greater accuracy than the measurement itself are removed. The remaining digits are called significant digits.

For example, measurements with a ruler can seldom be made without a margin of error of at least 0.01 meters. If the sides of a rectangle are measured as 1.23 meters and 4.56 meters, then multiplication gives an area for the rectangle of 5.6088 square meters. Since only the first two digits after the decimal place are significant, this is usually rounded to 5.61.

see also

Designing a number format penalty

We have already 2 ways to grade the student response available in the edit_numerical_form.php. i.e.

  • the answer-tolerance combination
  • the detailed unit grading

We need flexibility to take in account that number format can vary even at a same location i.e. in Canada there are two locales as there are two official languages (french and english).

Teacher sometimes ask for specific number formats as fraction that cannot be allowed simultanuously to other grading.

The most universal solution is to associate a grade to a specific answer numerical format as the tolerance is used.

For example in calculated the tolerance can be set as relative or absolute.

So my proposal is to add the number format as a new answer parameter.

To an official locales list of available formats we could add

  • specific Moodle formats as the one used for 1,9 2,0 ,
  • a fraction format,
  • time format,
  • degree, minute, second
  • etc.

Numbers as written by human and readed by computer language

The main feature of numerical question type is to ask the student to give a numerical answer i.e. a number. Most often this means a numerical value that is not an integer ( dates are a current example of integer value response) but a real number which value is expressed most often as a decimal number i.e 1.234 .

Computer languages ( i.e PHP used in Moodle) store real numbers in a different way than human do (decimal part and exponent similar to 1.234 E00) and humans do not expressed real nmumbers in an universal format.

The separator between the unit and the decimal fraction is often either a . or a ,  
1.234   1,234

Furthermore to help reading large numbers, most language add another separator for thousands often space or , if it is not used already as unit separator.

123 456.78     123,456.78   123 456,78 

PHP as a computer language use space to separate the language components so cannot use space as a thousand separator.

, is also used to separate variables so PHP use a simpler syntax 123456.78.

As this is stored in the computer as 2 parts (number and exponent).

1.2345678+E05 will be a structure that is well recognized by a computer language as PHP.

Computers as dummy readers of human number writing

Ordinary peoples know from their experiences with hand calculators or even sophisticated spreadsheets that computer are more or less "stupid" to recognized correctly the numbers or numerical values typed by humans.

We as human need to know the number style that the computer will recognize as this can vary among the different computer usages.

Typing a number in your on-line tax report is not the same as typing a number inside a complex math formula. Tax reports will handle well various number formats i.e.

  1. with or without the $ sign,
  2. with or without the space or , as thousand separator
  3. will not accept exponential formating as $ 1e6 etc,

However when writing a mathematical formula you cannot

  1. cannot put units i.e $
  2. space or , as thousands sep in numbers etc.

Even if you type a number in a format, the computer could display it in another format.

You type in a spreadsheet =1e-3

On return the display could be 0.001

Number formats can be different following their usage : i.e tax reports and mathematical formula

Humans have to learn which number format to use in a  given computer software case

Moodle 1,9 and 2,0 users and numbers in a numerical and calculated question types

There are two main cases

The teacher setting the answer

in a numerical question answer
numerical questiontype IS NOT a shortanswer questiontype
At first sight we think that the number should be typed as the response that the student will type to have a full grade.
If you want to do this, then you should use shortanswer questiontype.
In numerical questiontype the answer is a numerical value or quantity.
This is why we add tolerance grading to the numerical quantity.
There is no case sensitivity in numerical question, case sensitivity in shortanswer question being the analogous to the numerical tolerance or vice versa...
So when a teacher set the answer field ( and the tolerance field) in a numerical question he sets a value or a numerical quantity and PHP will understood this numercial quantity using a decimal format as 1234.56 or in exponential format as 1.23456 E3
notice that PHP in moodle always use . as decimal point.
in the edit_numerical_form or Gift import form these format with . are mandatory.
in cloze or other import the decimal point can be , in moodle 2,0.
in a calculated question formula answer
In calculated questiontype the numerical value or a numerical quantity will be obtained using a mathematical equation that among other element ( function, math symbol +-*/ etc.) will contain numerical quantities expressed as number.
The number in the equation is written in strict PHP rule decimal format as 1234.56 or in exponential format as 1.23456 E3.

The student typing its response in numerical or calculated question

in 1,9 the only decimal point allowed was .
in 2,0 the decimal point could be . or ,
in 1,9 and 2,0 the thousands separator space or , (when decsep is .) where removed as need by PHP syntax.
ALWAYS USE PHP DECIMAL FORMAT as 1234.56 or as 1.23456 E3;

This also means that in the actual code when a teacher set the answer field , he DOES NOT set the response format

Grading the response format : truth table

In the actual code 2,0 if the the decimal point and thousands sep are not set correctly by the student, he could have a zero grade to a correct numerical response.

However a clever student will never use thousand separators and will learn once for all if he can use , or . as unit separator.

The actual 2,1 proposal is to grade the numerical format separately from the numerical quantity.

On analysis it appears that the best way to do this as there is already a unit penalty applied , is to add at least the decimal point as an additional answer parameter alongside the tolerance.

A complete grading should also allow the teacher to specify the thousand separator as mandatory or control the use of the exponential format.

given the time delays for 2,1, let's design a decimal decimal point option.

The following table describe the results of the various versions

Moodle Version decsep thousand sep 1 234.56 1,234.56 1234.56 1.23456E3 1 234,56 1.234,56 1234,56 1,23456E3
2,1 . space OK NO OK OK NO NO NO NO
2,1 , space NO NO NO NO OK NO OK OK
2,1 proposal OK OK OK OK OK OK OK OK
2,1 proposal . OK OK OK OK NO NO NO NO
2,1proposal , NO NO NO NO OK OK OK OK


Moodle Version decsep thousand sep 1 234.56 1,234.56 1234.56 1.23456E3 1 234,56 1.234,56 1234,56 1,23456E3
2,1 . , OK OK OK
2,1 . space OK OK OK
2,1 , space OK OK OK
2,1 , . OK OK OK
2,1 proposal OK OK OK OK OK OK OK OK
2,1 proposal . OK OK OK OK
2,1proposal , OK OK OK OK

In 2,1 there is no 'universal option' that allow most of the known options and in 2,0 the german language 1.234,56 is not recognized.

In 1,9 and 2,0 the PHP structure is always good In 2,1 the PHP structure is good if you use the good thousands sep

So there is no real control on the thousand sep as long as the students learn to not using it.

As a first step in grading the response format in 2,1 , I propose that we limit the case to grade the decsep allowing either , or . and using a modified 2,0 version to handle the german . ,

From the the table we can see, with the proposal, that upgrading or importing from 1,9 the default decsep should be set to . and upgrading upgrading or importing from 2,0 the default decsep should be set to nil i.e. universal.

We could as set in 2,0 help warning the students that is they use , or . as thousand separators they MUST put the decimal separator.

In 2,2 we could develop grading of thousand sep along with the control of the exponential form.

In all cases we need an "universal" solution for Cloze questiontype. In calculated question the interface is already quite complex and perhaps things will not be implemented for 2,1 .

How to grade a numerical response format

Feasability of adding number format as an additional numerical answer table

The answer field being set as TEXT can easily store a numerical value in any format that can be validated easily in edit_numerical_form.php and the

     * Get an answer that contains the feedback and fraction that should be
     * awarded for this resonse.
     * @param number $value the numerical value of a response.
     * @return question_answer the matching answer.
    public function get_matching_answer($value) {
        foreach ($this->answers as $aid => $answer) {
            if ($answer->within_tolerance($value)) {
                $answer->id = $aid;
                return $answer;
        return null;

modified accordingly.

This is similar to tolerance handling which is another answer parameter .

The code flow should allow to compare each answers to the different values that result from the response text.

Aswers need to be compared with the value converted using the apply_unit() which could be different form the decsep value used, then either the $value is an array of all the values from the different decseps or the numerical value is computed inside this function from the student text response. This later option could be the most flexible if we add different number formats as fractional.

Keeping a separate input for the numerical response

  1. Questions should be explicit for the student.A separate input field for numerical and unit is the most evident way to do this and it is not teacher dependent although, later, this could be set as an option.
  2. Splitting in two fields
    1. simplify the coding
    2. allow more control on the number format i.e. mandatory exponent format
    3. development of other numericals fields

Adding number format specification to the numerical answer

A first version is under development with the following design

  1. add a locale parameter to the numerical_answer
  2. define three initial moodle internal locales
    1. "decimal_point_._,",
    2. "decimal_point_.",
    3. "decimal_point_,"

This locale parameter could also be used later to store the official locale names.

It allows the teacher to set different grade to different format

grading numerical format.jpg Two answers were defined

  • one with locale "decimal_point_," and fraction = 1
  • one with locale "decimal_point_." and fraction = 0.8

P.S. The correct format response display function code is not updated

How to grade the numerical response

There are 3 elements that should be graded in a numerical question

  1. the numerical value
  2. the unit used in relation to the numeric value
  3. the number format used to express the value

the numerical value

We need to compare using the tolerance value the numerical answer with the numerical response obtained from locale translated response as typed by the student.

This translation process associated by the various locales set 
by the teacher offer a possible grading of the third grading element
i.e. number format.

If there are no unit set with a multiply factor different from 1 , the numerical values to test are the different answers.

grading the combination numerical value : unit

However if there are units with factor different from unity, these unit factors generate other numerical values for each answer that when assoicate with the rigth unit, will be identical to of the answers.

i.e. answer 15 cm ( unit factor 1 ) if expressed as m the good response is 0.15 m

So the possible good numerical responses are 15 or 0.15

If the student response is 15 or .15 its numerical grade is 100%

He will have a unit penalty if

  1. he gave an unrecognized unit although this should not happen as the defined units should be clearly identify in the question text
  2. if he does associate the number with the rigth unit

The actual code does not handle differently the 2 cases.

The question::get_matching_answer() and the question:get_matching_answer should be modified to handle the unit penalty as it is handle in 2,0.

As these functions should be in question.php the question->units should be set on initialise_question_instance

So for each answer we need to test for each non unity factor unit.

... Pierre Pichet 01:01, 18 May 2011 (WST)

Setting the answer when creating the question

A proposal

The teacher should be able to set

  1. the numerical value using the PHP convention
  2. the response format , various interface can be used
  3. the tolerance
  4. the feedback

Here a simplified proposal

numerical answer interface proposal.jpg

1,9 and 2,0 interface

In Moodle 1,9 and 2,0 in the edit_numerical_form.php, the number enter by the teacher (although student could be allowed to create question, I will use teacher for text clarity) must be conform to the PHP syntax (no thousand separator or space and . as decimal separator. E syntax is allowed.

                if (!(is_numeric($trimmedanswer) || $trimmedanswer == '*')) {
                    $errors['answer[' . $key . ']'] =
                            get_string('answermustbenumberorstar', 'qtype_numerical');

So the teacher must know the PHP specific number syntax.

In the numerical/questiontype.php function save_question_options($question) there is an additional verification mostly for numerical questions imported through various formats or inside a Cloze numerical multianswer question.

                $answer->answer = $this->apply_unit($answerdata, $units);
                if ($answer->answer === false) {
                    $result->notice = get_string('invalidnumericanswer', 'quiz');

the Moodle 2,0 apply_unit

     * Checks if the $rawresponse has a unit and applys it if appropriate.
     * @param string $rawresponse  The response string to be converted to a float.
     * @param array $units         An array with the defined units, where the
     *                             unit is the key and the multiplier the value.
     * @return float               The rawresponse with the unit taken into
     *                             account as a float.
    function apply_unit($rawresponse, $units) {

        // Make units more useful
        $tmpunits = array();
        foreach ($units as $unit) {
            $tmpunits[$unit->unit] = $unit->multiplier;
        // remove spaces and normalise decimal places.
        $rawresponse = trim($rawresponse) ;
        $search  = array(' ', ',');
        // test if a . is present or there are multiple , (i.e. 2,456,789 ) so that we don't need spaces and ,
        if ( strpos($rawresponse,'.' ) !== false || substr_count($rawresponse,',') > 1 ) {
            $replace = array('', '');
        }else { // remove spaces and normalise , to a . .
            $replace = array('', '.');
        $rawresponse = str_replace($search, $replace, $rawresponse);

        // Apply any unit that is present.
        if (ereg('^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$',
                $rawresponse, $responseparts)) {
                echo"<p> responseparts <pre>";print_r($responseparts) ;echo"</pre></p>";

            if (!empty($responseparts[5])) {

                if (isset($tmpunits[$responseparts[5]])) {
                    // Valid number with unit.
                    return (float)$responseparts[1] / $tmpunits[$responseparts[5]];
                } else {
                    // Valid number with invalid unit. Must be wrong.
                    return false;

            } else {
                // Valid number without unit.
                return (float)$responseparts[1];
        // Invalid number. Must be wrong.
        return false;

The 2,0 apply_unit allows more number formats than the test in the editing form.

  • regular numbers 13500.67 : 13 500.67 : 13500,67: 13 500,67
  • if you use , as thousand separator *always* put the decimal . as in 13,500.67 : 13,500.
  • for exponent form, say 1.350067 * 104, use 1.350067 E4 : 1.350067 E04 ';

The 1,9 apply_unit is more restrictive as it does not support , as decimal separator.

More formats were allowed in 2,0 as the main objective in a numerical question is the numerical value. More about this further in the page (todo)

Converting number in a string to a double or float variable

The answer is being stored in a TEXT database field or in a string in import-export files.

However in numerical questiontype code it is used a numeric PHP parameter (float or double). From

String conversion to numbers

When a string is evaluated in a numeric context, the resulting value and type are determined as follows.

If the string *does not contain* any of the characters '.', 'e', or 'E' and the numeric value fits into integer type limits (as defined by PHP_INT_MAX), the string will be evaluated as an integer. In all other cases it will be evaluated as a float.

The value is given by the initial portion of the string. If the string starts with valid numeric data, this will be the value used. Otherwise, the value will be 0 (zero). Valid numeric data is an optional sign, followed by one or more digits (optionally containing a decimal point), followed by an optional exponent. The exponent is an 'e' or 'E' followed by one or more digits.

For more information on this conversion, see the Unix manual page for strtod(3).

Unix manual page for strtod DESCRIPTION

    The strtod(), strtof(), and strtold() functions convert  the
    initial  portion of the string pointed to by nptr to double,
    float, and long double representation,  respectively.  First
    they decompose the input string into three parts:
    1.  An initial,  possibly  empty,  sequence  of  white-space
        characters (as specified by isspace(3C))
    2.  A subject sequence interpreted as a floating-point  con-
        stant or representing infinity or NaN
    3.  A final string of one or more  unrecognized  characters,
        including the terminating null byte of the input string.
    Then they attempt to  convert  the  subject  sequence  to  a
    floating-point number, and return the result.
    The expected form of the subject  sequence  is  an  optional
    plus or minus sign, then one of the following:
      o  A non-empty sequence of digits optionally containing  a
         radix character, then an optional exponent part
      o  A 0x or 0X, then a non-empty  sequence  of  hexadecimal
         digits optionally containing a radix character, then an
         optional binary exponent part
      o  One of INF or INFINITY, ignoring case


    The radix character  is  defined  in  the  program's  locale
    (category  LC_NUMERIC).  In the POSIX locale, or in a locale
    where the radix character is not defined, the radix  charac-
    ter defaults to a period ('.').

So if the locale LC_NUMERIC i.e.

    [decimal_point] => .
    [thousands_sep] => 
    [int_curr_symbol] => 
    [currency_symbol] => 
    [mon_decimal_point] => 
    [mon_thousands_sep] => 
    [positive_sign] => 
    [negative_sign] => 
    [int_frac_digits] => 127
    [frac_digits] => 127
    [p_cs_precedes] => 127
    [p_sep_by_space] => 127
    [n_cs_precedes] => 127
    [n_sep_by_space] => 127
    [p_sign_posn] => 127
    [n_sign_posn] => 127
    [grouping] => Array

    [mon_grouping] => Array


define decimal_point not as . as here but as , then 1,234.56 will be recognized as 1,234 i.e. smaller than 2 although the number written is greater than 1000.

PHP 5.36 zend_strtod()

However in PHP 5.3.6 strtod() is replaced by zend_strtod()

 ZEND_API double zend_strtod (CONST char *s00, char **se)
	int bb2, bb5, bbe, bd2, bd5, bbbits, bs2, c, dsign,
		e, e1, esign, i, j, k, nd, nd0, nf, nz, nz0, sign;
	CONST char *s, *s0, *s1;
	volatile double aadj, aadj1, adj;
	volatile _double rv, rv0;
	Long L;
	ULong y, z;
	Bigint *bb, *bb1, *bd, *bd0, *bs, *delta, *tmp;
	double result;

	'''CONST char decimal_point = '.';'''
			z = 10*z + c - '0';
	nd0 = nd;
	if (c == decimal_point) {
		c = *++s;
		if (!nd) {
			for(; c == '0'; c = *++s)
			if (c > '0' && c <= '9') {
				s0 = s;
				nf += nz;
				nz = 0;
				goto have_dig;

So in PHP 5.3.6 the zend_strtod() ALWAYS use . as decimal_point

Other functions allow the user to define the decimal_point or use the locale defined decimal_point.


 * Checks whether the string "str" with length "length" is numeric. The value
 * of allow_errors determines whether it's required to be entirely numeric, or
 * just its prefix. Leading whitespace is allowed.
 * The function returns 0 if the string did not contain a valid number; IS_LONG
 * if it contained a number that fits within the range of a long; or IS_DOUBLE
 * if the number was out of long range or contained a decimal point/exponent.
 * The number's value is returned into the respective pointer, *lval or *dval,
 * if that pointer is not NULL.

static inline zend_uchar is_numeric_string(const char *str, int length, long *lval, double *dval, int allow_errors)
	const char *ptr;
	int base = 10, digits = 0, dp_or_e = 0;
	double local_dval;
	zend_uchar type;

	if (!length) {
		return 0;

	/* Skip any whitespace
	 * This is much faster than the isspace() function */
	while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r' || *str == '\v' || *str == '\f') {
	ptr = str;

	if (*ptr == '-' || *ptr == '+') {

	if (ZEND_IS_DIGIT(*ptr)) {
		/* Handle hex numbers
		 * str is used instead of ptr to disallow signs and keep old behavior */
		if (length > 2 && *str == '0' && (str[1] == 'x' || str[1] == 'X')) {
			base = 16;
			ptr += 2;

		/* Skip any leading 0s */
		while (*ptr == '0') {

		/* Count the number of digits. If a decimal point/exponent is found,
		 * it's a double. Otherwise, if there's a dval or no need to check for
		 * a full match, stop when there are too many digits for a long */
		for (type = IS_LONG; !(digits >= MAX_LENGTH_OF_LONG && (dval || allow_errors == 1)); digits++, ptr++) {
			if (ZEND_IS_DIGIT(*ptr) || (base == 16 && ZEND_IS_XDIGIT(*ptr))) {
			} else if (base == 10) {
				if (*ptr == '.' && dp_or_e < 1) {
					goto process_double;
				} else if ((*ptr == 'e' || *ptr == 'E') && dp_or_e < 2) {
					const char *e = ptr + 1;

					if (*e == '-' || *e == '+') {
						ptr = e++;
					if (ZEND_IS_DIGIT(*e)) {
						goto process_double;


The decimal point is hard written in the code if (*ptr == '.' && dp_or_e < 1), so the test will not be locale dependent.

Note that this function allows hexadecimal written numbers so in numerical we need another function (like apply_unit() to only use decimals.