Finding out whether a string is numeric or not
Asked Answered
M

19

95

How can we check if a string is made up of numbers only. I am taking out a substring from a string and want to check if it is a numeric substring or not.

NSString *newString = [myString substringWithRange:NSMakeRange(2,3)];
Monkery answered 22/5, 2011 at 23:4 Comment(0)
B
246

Here's one way that doesn't rely on the limited precision of attempting to parse the string as a number:

NSCharacterSet* notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
if ([newString rangeOfCharacterFromSet:notDigits].location == NSNotFound)
{
    // newString consists only of the digits 0 through 9
}

See +[NSCharacterSet decimalDigitCharacterSet] and -[NSString rangeOfCharacterFromSet:].

Brendabrendan answered 22/5, 2011 at 23:13 Comment(14)
this is true for @"231.123" so: // newString consists only of the digits 0 through 9 and the . characterFillander
NSMutableCharacterSet *digitsAndDots = [NSMutableCharacterSet decimalDigitCharacterSet]; [digitsAndDots addCharactersInString:@"."]; NSCharacterSet *notDigitsNorDots = [digitsAndDots invertedSet]; //also, thanx for bringing in "invertedSet". I didn't know about its existenceElectrostatics
Since you've got a mutable set there anyway, you could just call the 'invert' method on it rather than making a whole new set.Polyneuritis
note: this method considers @"" to be a number; if applicable, you may need to also check for [newString lenght] > 0. please let me know if i'm wrong.Sequential
@NicolasTyler Are you sure? This is only working for whole numbers in my case. For example 85, 342 and 2134 all work fine, but 234.525 does not.Scrivner
@Scrivner It seems this is something that has changed in IOS. This can be used instead to be sure: [[NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet]] Fillander
@NicolasTyler interesting but does that cover all locals though? Like don't some use a comma instead of a dot?Scrivner
@Scrivner At the time when I did this 3 years ago the character set included . That doesnt seem to be the case anymore. I never tested if commas were included on other locals. But using characterSetWithCharactersInString could be used with any characters, if you are worried.Fillander
Why is this page saying it's available from iOS 10+ if this answer in from 2011?Comitative
Why not just NSCharacterSet *areDigits = [NSCharacterSet decimalDigitCharacterSet] and then test for != NSNotFound?Clifford
@Clifford You'd encounter false positives for strings that contain numerical characters, but do not represent numerical values, such asg2gHohenzollern
invertedSet What is the cost of that operation? It should be a huge NSCharacterSet.Brunabrunch
@KMGorbunov I don't think that NSCharacterSet is actually storing a backing set that contains every single character in the set. I suspect invertedSet is returning a class that wraps the set it was created form, and returns the opposite value for all queries. But I don't know for certain.Brendabrendan
This solution will accept a variety of likely-unexpected strings: 𝟷𝟸𝟹, 𝟙𝟚𝟛, १२३, ١٢٣, and ১২৩, to name a few. My answer, among others, addresses this issue.Rags
S
35

I'd suggest using the numberFromString: method from the NSNumberFormatter class, as if the number is not valid, it will return nil; otherwise, it will return you an NSNumber.

NSNumberFormatter *nf = [[[NSNumberFormatter alloc] init] autorelease];
BOOL isDecimal = [nf numberFromString:newString] != nil;
Sob answered 22/5, 2011 at 23:10 Comment(6)
Are you sure it won't return a valid number e.g. for @"124sbd"?Goldin
No, both NSNumberFormatter and NSScanner would return NO for your string. Also worth knowing: they also return YES for numbers padded with whitespace. Btw, I just added some actual code snippet to your answer, hope you don't mind.Rudyrudyard
NSScanner should return 'YES' for that string, per the documentation, having found "a valid integer representation". Furthermore, it did exactly that in a test on iOS 4.3.2. However, NSNumberFormatter did return nil.Goldin
This doesn't catch '.' which is a requirement for me. Answer from @John Calsbeek worked nicely.Morphia
What happens for a string with hundreds of characters all digits? Is there a limit to the NSNumber created?Norvil
the best answer is the scanner (see Regexident's answer), it can work for numeric strings too (the formatter didn't work for numbers like -1.2 for me)Servomechanism
W
12

Validate by regular expression, by pattern "^[0-9]+$", with following method -validateString:withPattern:.

[self validateString:"12345" withPattern:"^[0-9]+$"];
  1. If "123.123" is considered
    • With pattern "^[0-9]+(.{1}[0-9]+)?$"
  2. If exactly 4 digit numbers, without ".".
    • With pattern "^[0-9]{4}$".
  3. If digit numbers without ".", and the length is between 2 ~ 5.
    • With pattern "^[0-9]{2,5}$".
  4. With minus sign: "^-?\d+$"

The regular expression can be checked in the online web site.

The helper function is as following.

// Validate the input string with the given pattern and
// return the result as a boolean
- (BOOL)validateString:(NSString *)string withPattern:(NSString *)pattern
{
    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];

    NSAssert(regex, @"Unable to create regular expression");

    NSRange textRange = NSMakeRange(0, string.length);
    NSRange matchRange = [regex rangeOfFirstMatchInString:string options:NSMatchingReportProgress range:textRange];

    BOOL didValidate = NO;

    // Did we find a matching range
    if (matchRange.location != NSNotFound)
        didValidate = YES;

    return didValidate;
}

Swift 3 version:

Test in playground.

import UIKit
import Foundation

func validate(_ str: String, pattern: String) -> Bool {
    if let range = str.range(of: pattern, options: .regularExpression) {
        let result = str.substring(with: range)
        print(result)
        return true
    }
    return false
}

let a = validate("123", pattern: "^-?[0-9]+")
print(a)
Waisted answered 16/2, 2016 at 7:19 Comment(5)
I tried like this for Swift 3. func numberOnly(string: String) -> Int { let expression = "" let regex = NSRegularExpression.init(pattern: expression, options: .caseInsensitive) let numberOfMatches = regex.numberOfMatches(in: string, options: .reportProgress, range: NSRange.init(location: 0, length: string.characters.count)) if numberOfMatches == 0 { return Int(string)! } return 0 } And I got error Playground execution aborted: error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).Malposition
I don't know that correct one for swift 3. Correct me if I wrong.Malposition
How about just return matchRange.location != NSNotFound;Nosebleed
what about the minus sign?Servomechanism
@Servomechanism With ^-?\d+$, I verified it on the site: regex101.comWaisted
R
9

You could create an NSScanner and simply scan the string:

NSDecimal decimalValue;
NSScanner *sc = [NSScanner scannerWithString:newString];
[sc scanDecimal:&decimalValue];
BOOL isDecimal = [sc isAtEnd];

Check out NSScanner's documentation for more methods to choose from.

Rudyrudyard answered 22/5, 2011 at 23:8 Comment(2)
Wouldn't that just tell you whether at least the first character is numeric?Goldin
My bad. Forgot the final call to isAtEnd.Rudyrudyard
G
7

I think the easiest way to check that every character within a given string is numeric is probably:

NSString *trimmedString = [newString stringByTrimmingCharactersInSet:[NSCharacterSet decimalDigitCharacterSet]];

if([trimmedString length])
{
    NSLog(@"some characters outside of the decimal character set found");
}
else
{
    NSLog(@"all characters were in the decimal character set");
}

Use one of the other NSCharacterSet factory methods if you want complete control over acceptable characters.

Goldin answered 22/5, 2011 at 23:14 Comment(4)
I like this way. Although it doesn't seem very clean, it's a very easy and core foundation-friendly way of checking if there are "non-digits" in a NSString. + I think that it's much faster than any other robot-based ways ( although we're probably not looking much at performance here ).Illinium
I believe that stringByTrimmingCharactersInSet only trims the begin and end of the StringRoadster
@BrodyRobertson: it does. Which matters not in the slightest for this answer. What example do you think this test would fail for?Goldin
@Tommy, After reevaluating I understand your answer and it is valid and will upvote but you need you to edit to allow me to revoteRoadster
T
7

Swift 3 solution if need to verify that the string has only digits:

CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: myString))
Twostep answered 31/3, 2017 at 11:15 Comment(2)
Clean and nice, best solution. I like that this does not do unnecessary .inverted and other actions.Crippen
Super clean solution, really like it !Dogcart
J
6

This original question was about Objective-C, but it was also posted years before Swift was announced. So, if you're coming here from Google and are looking for a solution that uses Swift, here you go:

let testString = "12345"
let badCharacters = NSCharacterSet.decimalDigitCharacterSet().invertedSet

if testString.rangeOfCharacterFromSet(badCharacters) == nil {
    print("Test string was a number")
} else {
    print("Test string contained non-digit characters.")
}
Jaclyn answered 18/12, 2015 at 11:49 Comment(0)
G
4

to be clear, this functions for integers in strings.

heres a little helper category based off of John's answer above:

in .h file

@interface NSString (NumberChecking)

+(bool)isNumber:(NSString *)string;

@end

in .m file

#import "NSString+NumberChecking.h"

@implementation NSString (NumberChecking)

+(bool)isNumber {
    if([self rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location == NSNotFound) {
        return YES;
    }else {
        return NO;
    }
}

@end

usage:

#import "NSString+NumberChecking.h"

if([someString isNumber]) {
    NSLog(@"is a number");
}else {
    NSLog(@"not a number");
}
Godown answered 4/2, 2014 at 17:59 Comment(0)
B
4

Swift 3 solution could be like:

extension String {

    var doubleValue:Double? {
        return NumberFormatter().number(from:self)?.doubleValue
    }

    var integerValue:Int? {
        return NumberFormatter().number(from:self)?.intValue
    }

    var isNumber:Bool {
        get {
            let badCharacters = NSCharacterSet.decimalDigits.inverted
            return (self.rangeOfCharacter(from: badCharacters) == nil)
        }
    }
}
Brickkiln answered 28/10, 2016 at 11:13 Comment(0)
S
2

An extension of @John Calsbeek's answer, and clarification of @Jeff and @gyratory circus's comments.

+ (BOOL)doesContainDigitsOnly:(NSString *)string
{
    NSCharacterSet *nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];

    BOOL containsDigitsOnly = [string rangeOfCharacterFromSet:nonDigits].location == NSNotFound;

    return containsDigitsOnly;
}

+ (BOOL)doesContainNonDigitsOnly:(NSString *)string
{
    NSCharacterSet *digits = [NSCharacterSet decimalDigitCharacterSet];

    BOOL containsNonDigitsOnly = [string rangeOfCharacterFromSet:digits].location == NSNotFound;

    return containsNonDigitsOnly;
}

The following can be added as category methods for NSString

- (BOOL)doesContainDigitsOnly
{
    NSCharacterSet *nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];

    BOOL containsDigitsOnly = [self rangeOfCharacterFromSet:nonDigits].location == NSNotFound;

    return containsDigitsOnly;
}

- (BOOL)doesContainNonDigitsOnly
{
    NSCharacterSet *digits = [NSCharacterSet decimalDigitCharacterSet];

    BOOL containsNonDigitsOnly = [self rangeOfCharacterFromSet:digits].location == NSNotFound;

    return containsNonDigitsOnly;
}
Starlet answered 27/7, 2017 at 16:43 Comment(0)
R
2

John Calsbeek's answer is nearly correct but omits some Unicode edge cases.

Per the documentation for decimalDigitCharacterSet, that set includes all characters categorized by Unicode as Nd. Thus their answer will accept, among others:

  • (U+0967 DEVANAGARI DIGIT ONE)
  • (U+1811 MONGOLIAN DIGIT ONE)
  • 𝟙 (U+1D7D9 MATHEMATICAL DOUBLE-STRUCK DIGIT ONE)

While in some sense this is correct — each character in Nd does map to a decimal digit — it's almost certainly not what the asker expected. As of this writing there are 610 code points categorized as Nd, only ten of which are the expected characters 0 (U+0030) through 9 (U+0039).

To fix the issue, simply specify exactly those characters that are acceptable:

NSCharacterSet* notDigits = 
    [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
if ([newString rangeOfCharacterFromSet:notDigits].location == NSNotFound)
{
    // newString consists only of the digits 0 through 9
}
Rags answered 25/12, 2018 at 23:36 Comment(0)
E
1

Yet another option:

- (BOOL)isValidNumber:(NSString*)text regex:(NSString*)regex {
    @try {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
        return [predicate evaluateWithObject:text];
    }
    @catch (NSException *exception) {
        assert(false); 
        return NO;
    }
}

Usage example:

BOOL isValid = [self isValidNumber:@"1234" regex:@"^[0-9]+$"];
Eva answered 9/3, 2017 at 3:52 Comment(0)
W
0

Test if a string is a number Might be Helpful

int i = [@"12.3" rangeOfCharacterFromSet: [ [NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet] ].location;

if (i == NSNotFound) {
     //is a number
}
Welsh answered 27/9, 2014 at 11:47 Comment(0)
K
0

Swift extension :

extension NSString {
func isNumString() -> Bool {
    let numbers = NSCharacterSet(charactersInString: "0123456789.").invertedSet
    let range = self.rangeOfCharacterFromSet(numbers).location
    if range == NSNotFound {
        return true
    }
    return false
}  }
Kinaesthesia answered 17/10, 2016 at 7:46 Comment(0)
S
0

For Swift 3

var onlyDigits: CharacterSet = CharacterSet.decimalDigits.inverted
if testString.rangeOfCharacter(from: onlyDigits) == nil {
  // String only consist digits 0-9
}
Stanleigh answered 16/3, 2017 at 14:21 Comment(0)
C
0

When you have digits that are from mixed languages, that use (or don't) the 0-9 digits formats, you will need to run a regex that will look for any number, next thing is to convert all digits to be 0-9 format (if you need the actual value):

// Will look for any language digits
let regex = try NSRegularExpression(pattern: "[^[:digit:]]", options: .caseInsensitive)
let digitsString = regex.stringByReplacingMatches(in: string,
                                                         options: NSRegularExpression.MatchingOptions(rawValue: 0),
                                                         range: NSMakeRange(0, string.count), withTemplate: "")
// Converting the digits to be 0-9 format
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: "EN")
let finalValue = numberFormatter.number(from: digitsString)

if let finalValue = finalValue {
  let actualValue = finalValue.doubleValue
}
Curia answered 7/1, 2019 at 11:18 Comment(0)
F
0

The easiest and most reliable way is trying to cast as Double, if the result is nil - it can't be formed into a legit number.

let strings = ["test", "123", "123.2", "-123", "123-3", "123..22", ".02"]
let validNumbers = strings.compactMap(Double.init)

print(validNumbers)
// prints [123.0, 123.2, -123.0, 0.02]

More info in the documentation: https://developer.apple.com/documentation/swift/double/2926277-init

Filariasis answered 28/4, 2019 at 13:35 Comment(0)
P
0

Simple wrote the string extension:

extension String {
    var isNumeric: Bool {
        return self.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil
    }
}
Peyter answered 29/6, 2020 at 9:18 Comment(0)
T
0

Objective-C solution:

[NSCharacterSet.decimalDigitCharacterSet isSupersetOfSet:[NSCharacterSet characterSetWithCharactersInString:field1]]

Tercet answered 29/5, 2023 at 7:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.