Déterminer si une chaîne de caractères est un nombre et convertir en Java?
je sais que des variantes de cette question ont été posées fréquemment auparavant (voir ici et ici par exemple), mais c'est pas an exact duplicate de ceux-ci.
je voudrais vérifier si un String
est un numéro, et si oui, je voudrais le stocker comme un double
. Il y a plusieurs façons de le faire, mais tous semblent inappropriés pour mes fins.
une solution serait d'utiliser Double.parseDouble(s)
ou de manière similaire new BigDecimal(s)
. Cependant, ces solutions ne fonctionnent pas s'il y a des virgules présentes (donc "1,234" causerait une exception). Je pourrais bien sûr enlever toutes les virgules avant d'utiliser ces techniques, mais cela semblerait poser des tas de problèmes dans d'autres endroits.
J'ai regardé Apache Commons NumberUtils.isNumber(s)
, mais cela souffre de la même virgule.
j'ai considéré NumberFormat
ou DecimalFormat
, mais ceux-ci semblaient beaucoup trop indulgents. Par exemple," 1A "est formaté en" 1 " au lieu d'indiquer qu'il ne s'agit pas d'un nombre. De plus, quelque chose comme "127.0.0.1" sera compté comme le nombre 127 au lieu d'indiquer qu'il ne s'agit pas d'un nombre.
j'ai l'impression que mes exigences ne sont pas si exotiques que je suis le premier à le faire, mais aucune des solutions ne fait exactement ce dont j'ai besoin. Je suppose que même Je ne sais pas exactement quoi J'ai besoin (sinon je pourrais écrire mon propre analyseur), mais je sais que les solutions ci-dessus ne fonctionnent pas pour les raisons indiquées. Y a-t-il une solution, ou dois-je trouver exactement ce dont j'ai besoin et écrire mon propre code?
15 réponses
semble assez étrange, mais je voudrais essayer de suivre cette réponse et utiliser java.util.Scanner
.
Scanner scanner = new Scanner(input);
if (scanner.hasNextInt())
System.out.println(scanner.nextInt());
else if (scanner.hasNextDouble())
System.out.println(scanner.nextDouble());
else
System.out.println("Not a number");
pour les intrants tels que 1A
, 127.0.0.1
, 1,234
, 6.02e-23
je reçois la sortie suivante:
Not a number
Not a number
1234
6.02E-23
Scanner.useLocale
peut être utilisé pour changer à la locale désirée.
vous pouvez spécifier la localisation dont vous avez besoin:
NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN);
double myNumber = nf.parse(myString).doubleValue();
cela devrait fonctionner dans votre exemple puisque la Locale allemande a des virgules comme séparateur décimal.
vous pouvez utiliser la ParsePosition comme un contrôle de la consommation complète de la corde dans un Formatnuméreux.opération d'analyse. Si la corde est consommée, alors vous n'avez pas de situation "1A". Si non, vous ne et peut se comporter en conséquence. Voir ici pour un aperçu rapide de la solution et ici pour le bogue JDK associé qui est fermé comme fix wont en raison de l'option ParsePosition.
Double Malheureusement.parseDouble(s) ou new BigDecimal (s) semblent être vos meilleures options.
vous citez des problèmes de localisation, mais malheureusement il n'y a aucune façon fiable prise en charge toutes les locales sans spécification par l'utilisateur de toute façon. Il est tout simplement impossible.
parfois, vous pouvez raisonner sur le schéma utilisé en regardant si les virgules ou les périodes sont utilisées en premier, si les deux sont utilisées, mais ce n'est pas toujours possible, alors pourquoi même essayer? Mieux vaut avoir un système qui fonctionne de manière fiable dans certaines situations que d'essayer de s'appuyer sur celui qui peut travailler dans des situations plus mais peut également donner de mauvais résultats...
que représente le nombre 123 456? 123456 ou 123,456?
il suffit de rayer des virgules, ou des espaces, ou des périodes, selon la localisation spécifiée par l'utilisateur. Par défaut pour le décapage des espaces et les virgules. Si vous voulez le rendre plus strict, seulement bandes de virgules ou Espaces, pas les deux, et seulement avant la période Si Il ya un. Aussi devrait être assez facile à vérifier manuellement s'ils sont correctement espacés dans trois. En fait, un analyseur personnalisé pourrait être plus facile ici.
voici un peu une preuve de concept. C'est un peu (très) désordonné mais je pense que ça marche, et vous obtenez l'idée de toute façon :).
public class StrictNumberParser {
public double parse(String numberString) throws NumberFormatException {
numberString = numberString.trim();
char[] numberChars = numberString.toCharArray();
Character separator = null;
int separatorCount = 0;
boolean noMoreSeparators = false;
for (int index = 1; index < numberChars.length; index++) {
char character = numberChars[index];
if (noMoreSeparators || separatorCount < 3) {
if (character == '.') {
if (separator != null) {
throw new NumberFormatException();
} else {
noMoreSeparators = true;
}
} else if (separator == null && (character == ',' || character == ' ')) {
if (noMoreSeparators) {
throw new NumberFormatException();
}
separator = new Character(character);
separatorCount = -1;
} else if (!Character.isDigit(character)) {
throw new NumberFormatException();
}
separatorCount++;
} else {
if (character == '.') {
noMoreSeparators = true;
} else if (separator == null) {
if (Character.isDigit(character)) {
noMoreSeparators = true;
} else if (character == ',' || character == ' ') {
separator = new Character(character);
} else {
throw new NumberFormatException();
}
} else if (!separator.equals(character)) {
throw new NumberFormatException();
}
separatorCount = 0;
}
}
if (separator != null) {
if (!noMoreSeparators && separatorCount != 3) {
throw new NumberFormatException();
}
numberString = numberString.replaceAll(separator.toString(), "");
}
return Double.parseDouble(numberString);
}
public void testParse(String testString) {
try {
System.out.println("result: " + parse(testString));
} catch (NumberFormatException e) {
System.out.println("Couldn't parse number!");
}
}
public static void main(String[] args) {
StrictNumberParser p = new StrictNumberParser();
p.testParse("123 45.6");
p.testParse("123 4567.8");
p.testParse("123 4567");
p.testParse("12 45");
p.testParse("123 456 45");
p.testParse("345.562,346");
p.testParse("123 456,789");
p.testParse("123,456,789");
p.testParse("123 456 789.52");
p.testParse("23,456,789");
p.testParse("3,456,789");
p.testParse("123 456.12");
p.testParse("1234567.8");
}
}
EDIT: évidemment cela devrait être étendu pour la reconnaissance de la notation scientifique, mais cela devrait être assez simple, d'autant plus que vous n'avez pas à réellement valider quoi que ce soit après le e, vous pouvez simplement laisser parseDouble échouer si elle est mal formé.
pourrait également être une bonne idée d'étendre correctement NumberFormat avec cela. avoir un getSeparator () pour les nombres parsés et un setSeparator pour donner le format de sortie désiré... Ce type de travail s'occupe de la localisation, mais il faudrait encore plus de travail pour soutenir", " pour les décimales...
pas sûr qu'il réponde à toutes vos exigences, mais le code trouvé ici pourrait vous indiquer la bonne direction?
De l'article:
en résumé, les étapes d'un traitement adéquat des entrées sont les suivantes:
- obtenir un Numérotformat approprié et définir une variable de Parséposition.
- règle l'indice de Parséposition à zéro.
- discute la valeur d'entrée avec parse (String source, ParsePosition parsePosition).
- effectuer des opérations d'erreur si la longueur d'entrée et la valeur de L'indice de ParsePosition ne correspondent pas ou si le nombre analysé est nul.
- sinon, la valeur a passé la validation.
C'est un problème intéressant. Mais c'est peut-être un peu ouvert? Vous cherchez à identifier les numéros de la base 10, ou les hexagones, ou quoi? Je présume que c'est la base 10. Ce sujet de la monnaie? Est-ce important? Ou est-il seulement des chiffres.
dans tous les cas, je pense que vous pouvez utiliser les déficiences du format de nombre à votre avantage. Puisque vous non que quelque chose comme "1A", sera interprété comme 1, Pourquoi ne pas vérifier le résultat en le formatant et en comparant la chaîne d'origine?
public static boolean isNumber(String s){
try{
Locale l = Locale.getDefault();
DecimalFormat df = new DecimalFormat("###.##;-##.##");
Number n = df.parse(s);
String sb = df.format(n);
return sb.equals(s);
}
catch(Exception e){
return false;
}
}
Qu'en pensez-vous?
c'est vraiment intéressant, et je pense que les gens essaient de le compliquer. Je voudrais vraiment décomposer cela par des règles:
1) Vérifier s'il y a une notation scientifique (est-ce que cela correspond au fait d'être tous les nombres, virgule, périodes, - / + et d'y avoir un "e"? cents cinquante et une million neuf cent dix mille neuf cent vingt"
2) correspond-il au regexp pour les caractères numériques valides (0-9 , . - + ) (seulement 1 . - ou + permis) si oui, dépouiller tout ce qui n'est pas un chiffres et d'analyser de manière appropriée, sinon l'échec.
Je ne peux pas voir un raccourci qui va fonctionner ici, il suffit de prendre l'approche de la force brute, tout dans la programmation ne peut pas être (ou doit être) complètement élégant.
je crois comprendre que vous voulez couvrir les langues occidentales / latines tout en conservant une interprétation aussi stricte que possible. Ce que je fais ici, c'est de demander à DecimalFormatSymbols de me dire ce que sont les séparateurs de groupes, décimaux, négatifs et zéro, et de les échanger contre des symboles que Double reconnaîtra.
Comment fonctionne-t-il?
aux USA, il rejette: "1A", " 127.100.100.100" et accepte "1.47 E-9"
en Allemagne il rejette encore "1A"
it ACCEPTS " 1,024.00" mais l'interprète correctement comme 1.024. De même, il accepte "127.100.100.100" comme 127100100.0
en fait, le local Allemand identifie et analyse correctement "1,47 E-9"
faites-moi savoir si vous avez des problèmes dans un endroit différent.
import java.util.Locale;
import java.text.DecimalFormatSymbols;
public class StrictNumberFormat {
public static boolean isDouble(String s, Locale l) {
String clean = convertLocaleCharacters(s,l);
try {
Double.valueOf(clean);
return true;
} catch (NumberFormatException nfe) {
return false;
}
}
public static double doubleValue(String s, Locale l) {
return Double.valueOf(convertLocaleCharacters(s,l));
}
public static boolean isDouble(String s) {
return isDouble(s,Locale.getDefault());
}
public static double doubleValue(String s) {
return doubleValue(s,Locale.getDefault());
}
private static String convertLocaleCharacters(String number, Locale l) {
DecimalFormatSymbols symbols = new DecimalFormatSymbols(l);
String grouping = getUnicodeRepresentation( symbols.getGroupingSeparator() );
String decimal = getUnicodeRepresentation( symbols.getDecimalSeparator() );
String negative = getUnicodeRepresentation( symbols.getMinusSign() );
String zero = getUnicodeRepresentation( symbols.getZeroDigit() );
String clean = number.replaceAll(grouping, "");
clean = clean.replaceAll(decimal, ".");
clean = clean.replaceAll(negative, "-");
clean = clean.replaceAll(zero, "0");
return clean;
}
private static String getUnicodeRepresentation(char ch) {
String unicodeString = Integer.toHexString(ch); //ch implicitly promoted to int
while(unicodeString.length()<4) unicodeString = "0"+unicodeString;
return "\u"+unicodeString;
}
}
vous feriez mieux de le faire manuellement. Trouvez ce que vous pouvez accepter comme un nombre et ne tenez pas compte de tout le reste:
import java.lang.NumberFormatException;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class ParseDouble {
public static void main(String[] argv) {
String line = "$$$|%|#|1A|127.0.0.1|1,344|95|99.64";
for (String s : line.split("\|")) {
try {
System.out.println("parsed: " +
any2double(s)
);
}catch (NumberFormatException ne) {
System.out.println(ne.getMessage());
}
}
}
public static double any2double(String input) throws NumberFormatException {
double out =0d;
Pattern special = Pattern.compile("[^a-zA-Z0-9\.,]+");
Pattern letters = Pattern.compile("[a-zA-Z]+");
Pattern comma = Pattern.compile(",");
Pattern allDigits = Pattern.compile("^[0-9]+$");
Pattern singleDouble = Pattern.compile("^[0-9]+\.[0-9]+$");
Matcher[] goodCases = new Matcher[]{
allDigits.matcher(input),
singleDouble.matcher(input)
};
Matcher[] nanCases = new Matcher[]{
special.matcher(input),
letters.matcher(input)
};
// maybe cases
if (comma.matcher(input).find()){
out = Double.parseDouble(
comma.matcher(input).replaceFirst("."));
return out;
}
for (Matcher m : nanCases) {
if (m.find()) {
throw new NumberFormatException("Bad input "+input);
}
}
for (Matcher m : goodCases) {
if (m.find()) {
try {
out = Double.parseDouble(input);
return out;
} catch (NumberFormatException ne){
System.out.println(ne.getMessage());
}
}
}
throw new NumberFormatException("Could not parse "+input);
}
}
si vous réglez votre locale, construit dans parseDouble
fonctionnera avec des virgules. Exemple: ici .
je pense que vous avez un processus en plusieurs étapes à gérer ici avec une solution personnalisée, si vous n'êtes pas prêt à accepter les résultats de DecimalFormat
ou les réponses déjà liées.
1) Identifier les séparateurs décimaux et de regroupement. Vous devrez peut-être identifier d'autres symboles DE format (tels que les indicateurs de notation scientifique).
2) Enlevez tous les symboles de groupement (ou créez un regex, faites attention aux autres symboles que vous acceptez tels que le décimal si vous le faites). Ensuite dépouiller le premier symbole décimal. D'autres symboles en tant que de besoin.
3) appeler parse
ou isNumber
.
un des hacks faciles serait d'utiliser replaceFirst
pour la corde que vous obtenez et vérifiez la nouvelle corde si elle est un double ou non. Dans le cas où il s'agit d'une double conversion de retour (si nécessaire)
si vous voulez convertir un nombre de chaîne de caractères décimal à double, vous pouvez utiliser DecimalSeparator + DecimalFormalSymbols:
final double strToDouble(String str, char separator){
DecimalFormatSymbols s = new DecimalFormatSymbols();
s.setDecimalSeparator(separator);
DecimalFormat df = new DecimalFormat();
double num = 0;
df.setDecimalFormatSymbols(s);
try{
num = ((Double) df.parse(str)).doubleValue();
}catch(ClassCastException | ParseException ex){
// if you want, you could add something here to
// indicate the string is not double
}
return num;
}
Eh bien, permet de le tester:
String a = "1.2";
String b = "2,3";
String c = "A1";
String d = "127.0.0.1";
System.out.println("\"" + a + "\" = " + strToDouble(a, ','));
System.out.println("\"" + a + "\" (with '.' as separator) = "
+ strToDouble(a, '.'));
System.out.println("\"" + b + "\" = " + strToDouble(b, ','));
System.out.println("\"" + c + "\" = " + strToDouble(c, ','));
System.out.println("\"" + d + "\" = " + strToDouble(d, ','));
si vous exécutez le code ci-dessus, vous verrez:
"1.2" = 0.0
"1.2" (with '.' as separator) = 1.2
"2,3" = 2.3
"A1" = 0.0
"127.0.0.1" = 0.0
cela prendra une chaîne, compter ses décimales et les virgules, supprimer les virgules, conserver une décimale valide (notez que ceci est basé sur la normalisation américaine - pour traiter 1.000.000,00 comme 1 million ce processus devrait avoir la décimale et la manipulation de virgule commuté), déterminer si la structure est valide, puis retourner un double. Retourne null si la chaîne n'a pas pu être converti. Edit : ajout de support pour international ou US. convertStoD (string,true) pour nous, convertStoD(string,false) pour les non NOUS. Les commentaires sont maintenant pour la version américaine.
public double convertStoD(string s,bool isUS){
//string s = "some string or number, something dynamic";
bool isNegative = false;
if(s.charAt(0)== '-')
{
s = s.subString(1);
isNegative = true;
}
string ValidNumberArguements = new string();
if(isUS)
{
ValidNumberArguements = ",.";
}else{
ValidNumberArguements = ".,";
}
int length = s.length;
int currentCommas = 0;
int currentDecimals = 0;
for(int i = 0; i < length; i++){
if(s.charAt(i) == ValidNumberArguements.charAt(0))//charAt(0) = ,
{
currentCommas++;
continue;
}
if(s.charAt(i) == ValidNumberArguements.charAt(1))//charAt(1) = .
{
currentDec++;
continue;
}
if(s.charAt(i).matches("\D"))return null;//remove 1 A
}
if(currentDecimals > 1)return null;//remove 1.00.00
string decimalValue = "";
if(currentDecimals > 0)
{
int index = s.indexOf(ValidNumberArguements.charAt(1));
decimalValue += s.substring(index);
s = s.substring(0,index);
if(decimalValue.indexOf(ValidNumberArguements.charAt(0)) != -1)return null;//remove 1.00,000
}
int allowedCommas = (s.length-1) / 3;
if(currentCommas > allowedCommas)return null;//remove 10,00,000
String[] NumberParser = s.split(ValidNumberArguements.charAt(0));
length = NumberParser.length;
StringBuilder returnString = new StringBuilder();
for(int i = 0; i < length; i++)
{
if(i == 0)
{
if(NumberParser[i].length > 3 && length > 1)return null;//remove 1234,0,000
returnString.append(NumberParser[i]);
continue;
}
if(NumberParser[i].length != 3)return null;//ensure proper 1,000,000
returnString.append(NumberParser[i]);
}
returnString.append(decimalValue);
double answer = Double.parseDouble(returnString);
if(isNegative)answer *= -1;
return answer;
}
ce code devrait traiter la plupart des entrées, sauf les adresses IP où tous les groupes de chiffres sont en trois (ex: 255.255.255.255 est valide, mais pas 255.1.255.255). Il ne supporte pas non plus la notation scientifique
Il fonctionne avec la plupart des variantes de séparateurs (",", "."ou de l'espace). Si plus d'un séparateur est détecté, le premier est supposé être le séparateur des milliers, avec des contrôles supplémentaires (validité etc.)
Edit: prevDigit est utilisé pour vérifier que le nombre utilise des séparateurs de milliers correctement. S'il y a plus d'un groupe de milliers, Tous sauf le premier doivent être en groupes de 3. J'ai modifié le code pour le rendre plus clair afin que "3" ne soit pas un nombre magique mais une constante.
Edit 2: les votes négatifs ne me dérangent pas beaucoup, mais quelqu'un peut-il expliquer ce qu'est le problème?
/* A number using thousand separator must have
groups of 3 digits, except the first one.
Numbers following the decimal separator can
of course be unlimited. */
private final static int GROUP_SIZE=3;
public static boolean isNumber(String input) {
boolean inThousandSep = false;
boolean inDecimalSep = false;
boolean endsWithDigit = false;
char thousandSep = '"151900920"';
int prevDigits = 0;
for(int i=0; i < input.length(); i++) {
char c = input.charAt(i);
switch(c) {
case ',':
case '.':
case ' ':
endsWithDigit = false;
if(inDecimalSep)
return false;
else if(inThousandSep) {
if(c != thousandSep)
inDecimalSep = true;
if(prevDigits != GROUP_SIZE)
return false; // Invalid use of separator
}
else {
if(prevDigits > GROUP_SIZE || prevDigits == 0)
return false;
thousandSep = c;
inThousandSep = true;
}
prevDigits = 0;
break;
default:
if(Character.isDigit(c)) {
prevDigits++;
endsWithDigit = true;
}
else {
return false;
}
}
}
return endsWithDigit;
}
code D'essai:
public static void main(String[] args) {
System.out.println(isNumber("100")); // true
System.out.println(isNumber("100.00")); // true
System.out.println(isNumber("1,5")); // true
System.out.println(isNumber("1,000,000.00.")); // false
System.out.println(isNumber("100,00,2")); // false
System.out.println(isNumber("123.123.23.123")); // false
System.out.println(isNumber("123.123.123.123")); // true
}