Peter
Peter Campbell Smith

Strong and valid

Weekly challenge 287 — 16 September 2024

Week 287: 16 Sep 2024

Task 2

Task — Valid number

You are given a string, $str. Write a script to find if it is a valid number.

Conditions for a valid number:

  • An integer number followed by an optional exponent.
  • A decimal number followed by an optional exponent.
  • An integer number is defined as an optional sign '-' or '+' followed by digits.

A decimal number is defined as an optional sign '-' or '+' followed by one of the following:

  • Digits followed by a dot '.'.
  • Digits followed by a dot '.' followed by digits.
  • A dot '.' followed by digits.

An exponent is defined as 'e' or 'E' followed by an integer number.

Examples


Example 1
Input: $str = "1"
Output: true

Example 2
Input: $str = "a"
Output: false

Example 3
Input: $str = "."
Output: false

Example 4
Input: $str = "1.2e4.2"
Output: false

Example 5
Input: $str = "-1."
Output: true

Example 6
Input: $str = "+1E-8"
Output: true

Example 7
Input: $str = ".44"
Output: true

Analysis

No doubt someone will come up with one regular expression that does all of that, but I'm not that clever.

I have a main regular expression that more or less does it, but is too generous in two ways:

  • It allows almost anything after the [Ee]
  • It allows '.' as well as '5.' or '.5'

Each of these is then dealt with in separate regexes.

Try it 

Try running the script with any input:



example: 123.456e-99

Script


#!/usr/bin/perl

# Blog: http://ccgi.campbellsmiths.force9.co.uk/challenge

use v5.26;    # The Weekly Challenge - 2024-09-16
use utf8;     # Week 287 - task 2 - Valid number
use warnings; # Peter Campbell Smith
binmode STDOUT, ':utf8';

my @tests;

say qq[\nValid numbers:\n      \$str     Valid?\n     -----     ------];
@tests = ('23', '-23', '+23', '34.1', '-34.1', '+34.1', '.7', '-.7', '23e0', '23e-1', '23E+2', '-23e10', '+34.5e-10');
valid_number($_) for @tests;

say qq[\nInvalid numbers:\n      \$str     Valid?\n     -----     ------];
@tests = ('two', '23-', '23.1.2', 'e', 'e1', '34e1.2', '34e++6', '.e5', '+.e5', '123E', '.', '.e6' );
valid_number($_) for @tests;

sub valid_number {
    
    my ($str, $str2, $valid, $a, $b);
    
    $str = $_[0];
    $valid = 0;
    
    # basic match              $a------------  $b-----
    if (($a, $b) = $str =~ m|^([-+]?\d*\.?\d*)([Ee]?.*)$|) {

        # but if [Ee] is present it must be followed by optional sign and 1+ digits
        $valid = 1 if ($b eq '' or ($a ne '' and $b =~ m|^[Ee][-+]?\d+$|));
    }
    
    # decimal point must be preceded or followed by a digit (or both)
    if ($valid and $str =~ m|\.|) {
        $valid = 0 unless ($str =~ m|\d\.| or $str =~ m|\.\d|);
    }
    
    printf("%10s %10s\n", $str, ('false', 'true')[$valid]);
}

Output


Valid numbers:
      $str     Valid?
     -----     ------
        23       true
       -23       true
       +23       true
      34.1       true
     -34.1       true
     +34.1       true
        .7       true
       -.7       true
      23e0       true
     23e-1       true
     23E+2       true
    -23e10       true
 +34.5e-10       true

Invalid numbers:
      $str     Valid?
     -----     ------
       two      false
       23-      false
    23.1.2      false
         e      false
        e1      false
    34e1.2      false
    34e++6      false
       .e5      false
      +.e5      false
      123E      false
         .      false
       .e6      false

 

Any content of this website which has been created by Peter Campbell Smith is in the public domain