How to write isNumeric function in Golang?

Viewed 17279

I want to check if a string is numeric.

For example:

  • "abcd123" should return false.
  • "1.4" or "240" should return true.

I thought about using ParseInt and ParseFloat (from the strconv package), but am not sure if that is the right way.

6 Answers

I tried to comment on Adrian's answer but I guess I don't have enough reputation points. Building on his excellent response, here is a variation using PCRE. Some brief explanation on the symbols if you are unfamiliar with regular expressions:

"^" matches the start of input (i.e. beginning of your string)

"$" matches the end of input (i.e. the end of your string)

"()" are grouping operators

"*" matches 0 or more

"+" matches 1 or more

"?" matches exactly 0 or 1

"\d" is a character class which represents the character values 0 through 9

So, the following would require at least a leading 0, permit "0.", and everything else that is normally identified as a floating point value. You can experiment with this a bit.

func isFloat(s string) bool {
    return regexp.MatchString(`^\d+(\.\d*)?$`, s)
}

Naturally, if you are calling this function to validate data, it should be cleaned:

str := strings.TrimSpace(someString)
if isFloat(str) {
  ...
}

That only works on ASCII characters. If you are dealing with UTF8 or another multi-byte character set (MBCS), it can be done with regexp but more work would be required and perhaps another approach altogether.

I hit this in a high-throughput system today and did a benchmark of the three suggestions. Results:

BenchmarkNumDot-4: 657966132: 18.2 ns/op
BenchmarkNumeric-4: 49575919: 226 ns/op
BenchmarkRegexp-4: 18817201: 628 ns/op

Code follows since the playground does not support benchmarking.

package main

import (
    "regexp"
    "strconv"
    "testing"
)


func BenchmarkNumDot(b *testing.B) {
    for i := 0; i < b.N; i++ {
        isNumDot("abc")
    isNumDot("123")
    isNumDot("12.34")
    isNumDot("1.2.3.4")
    }
}

func BenchmarkNumeric(b *testing.B) {
    for i := 0; i < b.N; i++ {
        isNumeric("abc")
    isNumeric("123")
    isNumeric("12.34")
    isNumeric("1.2.3.4")
    }
}

func BenchmarkRegexp(b *testing.B) {
    re := regexp.MustCompile(`^[0-9]+(\.[0-9]+)?$`)

    for i := 0; i < b.N; i++ {
        isNumReg("abc", re)
        isNumReg("123", re)
        isNumReg("12.34", re)
        isNumReg("1.2.3.4", re)
    }
}

func isNumDot(s string) bool {
    dotFound := false
    for _, v := range s {
        if v == '.' {
            if dotFound {
                return false
            }
            dotFound = true
        } else if v < '0' || v > '9' {
            return false
        }
    }
    return true
}

func isNumeric(s string) bool {
    _, err := strconv.ParseFloat(s, 64)
    return err == nil
}

func isNumReg(s string, re *regexp.Regexp) bool {
    return re.Match([]byte(s))
}
Related