17
\$\begingroup\$

Prelude:

I wanted to train making test cases, so I'm gonna try them on something easy.

Challenge:

Take any given (string) input (within visible ASCII range) and deduce if it's a number and output something that could be used to evaluate.

Rules:

  • A number will only contain the characters -0123456789,.
  • Answers are only required to recognise numbers between -1000000000 and 1000000000 (exclusive), but may recognise arbitrarily large numbers.
  • You can write a full program or a function.
  • If a number, return anything that could be used to discern it and document the output in the description (ex. My program outputs T if a number, F if not.).
  • Input will be any amount of characters within ASCII range or empty (if empty return whatever you'd output if not a number).
  • Numbers could include a decimal point (ex. 3.14). If they do, they must have at least one digit before the decimal point and at least one after it.
  • Numbers could have leading or trailing zeros (ex. 000001.00000).
  • The integer part of a number could be divided for readability into chunks of three digits with commas (ex. 1,000.23456). In this case they must be divided every three digits from right to left (ex. 1,234,567, 10,000.202, 123,234.00, 0,123.293).
  • Negatives numbers are indicated by a leading - (ex. -1.23). A leading + to indicate a positive number is not permitted, and should result in the falsy output.
  • Exceptions are NOT counted as valid and discernible output (except when they can pass outputs to standard out stream (ex. Exception on line N [...] can be put in as output for a number/nonnumber if the string is put to standard out stream.

Test cases:

(assuming the My program outputs T if a number, F if not. version)

123 -> T [on integer]
-123 -> T [negative numbers need to be handled]
0 -> T [on zero]
123.456 -> T [on floating point]
123,456.789 -> T [on evenly divided by 3 digits with comas]
123456789 -> T [thousand separators are not required]
0000001.00000 -> T [on leading/trailing zeros]
00.00 -> T [on zero and leading/trailing zeros]
999999999.9999999999999999999999999999999999999999999999999999 -> T [on close to the maximum value]
-999999999.9999999999999999999999999999999999999999999999999999 -> T [on close to the minimum value]
 -> F [on empty]
lolz -> F [on non-number value]
n4melyh4xor -> F [on non-number with digits]
  1.2 -> F [on space-padded]
9.3 1.3 -> F [on anyhow separated multiple numbers]
1e5 -> F [no scientific notation]
50cl05e.buty3ts0f4r -> F [on input with letters obscuring the number]
1,2,3,4.5678 -> F [on badly readability-divided number]
1,234.5,678 -> F [on readability-divided floating point part]
.234 -> F [on no leading zero]
+1 -> F [no leading + sign]
1.234.3 -> F [only one decimal point]
12345,678 -> F [on separator stopping mid-way]

, least characters is the winner.

\$\endgroup\$
11
  • \$\begingroup\$ Can we assume the input is always ASCII? \$\endgroup\$ Dec 17, 2016 at 17:04
  • \$\begingroup\$ @BradGilbertb2gills, yes. \$\endgroup\$ Dec 17, 2016 at 17:08
  • \$\begingroup\$ If -123 is OK, what about +456 --> good or bad. Or is + left out of the party? \$\endgroup\$ Dec 17, 2016 at 17:39
  • \$\begingroup\$ @chux, the party train left before the + got there. Better luck next time, +. \$\endgroup\$ Dec 17, 2016 at 17:42
  • 2
    \$\begingroup\$ When people ask for clarifications in comments, you should edit the question. It should never be necessary to read the comments in order to know the spec. I've edited to include the answers and some test cases. \$\endgroup\$ Dec 17, 2016 at 23:04

9 Answers 9

10
\$\begingroup\$

Retina, 28 29 31 40 34 bytes

^-?\d{1,3}((,\d{3})*|\d*)(\.\d+)?$

Outputs 1 if truthy, 0 otherwise. As far as I understand, in this case Retina outputs the number of matches the regex has on the input.

Try it online!

Test Suite

Edits

  • Fixed to comply with the "there should be digits after the decimal place" rule
  • Fixed to comply with the "handle negatives" rules
  • Fixed to comply with the optional comma separators
  • Fixed bug with thousands separators as noted by @MartinEnder
  • Golfed by removing conditional lookahead

RegExplanation

^-?\d{1,3}((,\d{3})*|\d*)(\.\d+)?$
^                                $  This marks the start and end of regex
 -?                                 Optional hyphen for negative number
   \d{1,3}                          Matches between 1 and 3 digits
          (             )           Capturing group
           (,\d{3})*                 Matches at least zero of comma and three numbers (in the case of separators)
                    |                OR
                     \d*             Matches at least zero digits (in the case of no separators)
                        (\.\d+)?    Matches an optional . and at least one digit (for decimal numbers)
\$\endgroup\$
2
  • \$\begingroup\$ @MartinEnder Fixed! Also, how do you specify the flags (say flags for m and g) in Retina? \$\endgroup\$
    – user41805
    Dec 17, 2016 at 15:05
  • \$\begingroup\$ Put a ` in front of the regex, and then the modifiers go in front of that, e.g. m`^.$. g does not exist in .NET, matches are global by default. \$\endgroup\$ Dec 17, 2016 at 15:14
3
\$\begingroup\$

JavaScript, 46 49 bytes

This is a direct port of my Retina answer. The only reason I used JS is so that there is an easy way to test the regex using the Snack Snippet below

f=s=>/^-?\d{1,3}((,\d{3})*|\d*)(\.\d+)?$/.test(s)

f=s=>/^-?\d{1,3}((,\d{3})*|\d*)(\.\d+)?$/.test(s)
<input oninput=p.innerText=f(value)><p id=p></p>

\$\endgroup\$
1
  • \$\begingroup\$ If it weren't for some of the rules (such as the comma rule), one could simply do isNaN(+prompt()) for 16 characters. Such is life, I suppose \$\endgroup\$ Apr 6, 2017 at 1:25
2
\$\begingroup\$

Perl 6, 42 bytes

{?/^\-?[\d**1..3[\,\d**3]+|\d+][\.\d+]?$/}

Try it

Expanded:

{  # bare block lambda with implicit parameter 「$_」

  ? # turn the following into a Bool result

  /  # match implicitly against 「$_」

    ^                 # beginning of string

    \- ?              # optional leading 「-」

    [

      \d ** 1..3      # 1 to 3 digits
      [
        \,            # comma
        \d ** 3       # three digits
      ]+              # repeat at least once

    |                 # or

      \d+             # at least one digit

    ]

    [ \. \d+ ]?       # 「.」 followed by at least one digit optionally

    $  # end of string

  /
}
\$\endgroup\$
1
\$\begingroup\$

PHP, 62 bytes

<?=preg_match("#^-?(\d+|\d{1,3}(,\d{3})*)(\.\d+)?$",$argv[1]);

The builtin cannot handle commas and it accepts scientific notation; so I had to walk the regex way. <?=is_numeric($argv[1]); would have been 24 bytes.

\$\endgroup\$
0
\$\begingroup\$

bash/Unix tools, 64 bytes

egrep -c '^-?([0-9]+|[0-9]{1,3}(,[0-9]{3})+)(\.[0-9]+)?$'<<<"$1"

Save this as a file, and pass the string to be tested as the first argument to the command.

Output is 0 for a non-number, 1 for a number.

If you're willing to accept input from stdin and if you can guarantee that the input consists of just one line, then you can remove the <<<"$1" at the end, resulting in 57 bytes.

As for the code itself, the -c option to egrep counts the number of matching lines (which will be either 0 or 1).

\$\endgroup\$
0
\$\begingroup\$

Pyth, 25 characters

:zCiCM"૧򡊪񛨮򶟉񠫊򆻛񓰚祥񯯏󪬡򢝉񁘍񲲂쪤"^T6

Compresses Kritixi Lithos’ regex.

Try it online. Uses Unicode characters outside the Basic Multilingual Plane, which TIO apparently can’t handle? This little app reports the wrong size for the string, though. This character/byte counter gets it right.

\$\endgroup\$
0
\$\begingroup\$

C89, 195 bytes

b,d,c,i;f(char*s){b=*s;for(d=c=i=0;i++,*(s++),d+=*s=='.',c+=*s==',',b=c?i&(i%4!=3)&*s==','?0:b:b;)if(*s==45&!i);else if((*s==32)|(*s==46)|*s==43|!isdigit(*s)&*s!=46&*s!=44)||!(d-1))b=0;return b;}

Ungolfed:

bool is_base10_number (const char* const str) {

  if(!str[0])return false;

  size_t
    dpts = 0,
    cmas = 0;

  for (size_t i = 0; i < strlen(str); i++) {

    if ((str[i] == '-') && (!i)){
      continue;
    }

    if (
      (str[i] == ' ')
      || (str[0] == '.')
      || (str[0] == '+')
      ||
      (
        (!isdigit(str[i]))
        && (str[i] != '.')
        && (str[i] != ',')
      )
    ) {
      return false;
    }

    dpts += str[i] == '.';
    cmas += str[i] == ',';

    if (cmas) {
      if (i & ((i % 4) != 3) & str[i] == ',') {
        return false;
      }
    }

  }

  if (dpts > 1) { return false; }

  return true;
}
\$\endgroup\$
0
\$\begingroup\$

Python 2, 79 bytes

Regex solution

import re
lambda s:[s]==re.findall('-?(?:\d{1,3}(?:,\d{3}\d+)(?:\.?\d+)?',s)


-?                                          | Starts with 0-1 dashes
  (?:\d{1,3}(?:,\d{3})+                     | Have 1-3 digits and 1+ (comma followed by 3 digits)
                       |\d+)                | or 1+ digits
                            (?:\.?\d+)?     | Ends with dot and 1+ digits or nothing

Try it online

\$\endgroup\$
0
\$\begingroup\$

c#, 75 bytes

bool t(string n){double v;return double.TryParse(n.Replace(",",""),out v);}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.