Pregunta

Sé que las variantes de esta pregunta se han hecho con frecuencia antes (ver aquí y aquí por ejemplo), pero esto es no un exacto duplicado de esos.

Me gustaría verificar si un String es un número, y de ser así, me gustaría almacenarlo como un double. Hay varias formas de hacer esto, pero todas parecen inapropiadas para mis propósitos.

Una solución sería usar Double.parseDouble(s) o de manera similar new BigDecimal(s). Sin embargo, esas soluciones no funcionan si hay comas presentes (por lo que "1,234" causaría una excepción). Por supuesto, podría eliminar todas las comas antes de usar estas técnicas, pero eso parece plantear muchos problemas en otros lugares.

Miré a Apache Commons NumberUtils.isNumber(s), pero eso sufre el mismo problema de coma.

yo considere NumberFormat o DecimalFormat, pero esos parecían demasiado indulgentes. Por ejemplo, "1a" está formateado a "1" en lugar de indicar que no es un número. Además, algo como "127.0.0.1" se contará como el número 127 en lugar de indicar que no es un número.

Siento que mis requisitos no son tan exóticos que soy el primero en hacer esto, pero ninguna de las soluciones hace exactamente lo que necesito. Supongo que incluso yo no lo sé exactamente Lo que necesito (de lo contrario podría escribir mi propio analizador), pero sé que las soluciones anteriores no funcionan por las razones indicadas. ¿Existe alguna solución, o necesito descubrir con precisión lo que necesito y escribir mi propio código para ello?

¿Fue útil?

Solución

Suena bastante extraño, pero intentaría seguir esta respuesta y use 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");

Para entradas como 1A, 127.0.0.1, 1,234, 6.02e-23 Obtengo la siguiente salida:

Not a number
Not a number
1234
6.02E-23

Scanner.useLocale se puede usar para cambiar a la ubicación deseada.

Otros consejos

Puede especificar el local que necesita:

NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN);
double myNumber = nf.parse(myString).doubleValue();

Esto debería funcionar en su ejemplo, ya que la ubicación alemana tiene comas como separador decimal.

Puede usar la parseposición como verificación para ver el consumo completo de la cadena en una operación numiFormat.Parse. Si se consume la cadena, entonces no tiene una situación "1A". Si no, usted lo hace y puede comportarse en consecuencia. Ver aquí para un resumen rápido de la solución y aquí Para el error JDK relacionado que se cierra como no se solucionará debido a la opción de parseposición.

Desafortunadamente, el doble. ParsedOuble (s) o los nuevos BigDecimal (s) parecen ser sus mejores opciones.

Cita las preocupaciones de localización, pero desafortunadamente no hay forma de que el usuario no sea de manera confiable. Es simplemente imposible.

A veces puede razonar sobre el esquema utilizado al observar si las comas o los períodos se usan primero, si ambos se usan, pero esto no siempre es posible, entonces, ¿por qué intentarlo? Es mejor tener un sistema que sabe que funciona de manera confiable en ciertas situaciones que tratar de confiar en uno que pueda funcionar en más situaciones pero que también pueda dar malos resultados ...

¿Qué representa el número 123,456? 123456 o 123.456?

Simplemente despliegue de comas, espacios o períodos, dependiendo de la localidad especificada por el usuario. Predeterminado a los espacios y comas de desnudez. Si desea hacerlo más estricto, solo despliegue las comas o los espacios, no ambos, y solo antes del período si hay uno. También debe ser bastante fácil de comprobar manualmente si están espaciados correctamente en los tres. De hecho, un analizador personalizado puede ser más fácil aquí.

Aquí hay un poco de prueba de concepto. Es un poco desordenado (muy), pero creo que funciona, y de todos modos se entiende la idea :).

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");
  }
}

EDITAR: Obviamente, esto debería extenderse para reconocer la notación científica, pero esto debería ser bastante simple, especialmente porque no tiene que validar nada después de la E, puede dejar que el parsedobro falle si está mal formado.

También podría ser una buena idea extender correctamente NumberFormat con esto. tener un getseparator () para números analizados y un setseparator para dar el formato de salida deseado ... este tipo de se encarga de la localización, pero nuevamente se necesitaría hacer más trabajo para apoyar ',' para decimales ...

No estoy seguro si cumple con todos sus requisitos, pero el código se encuentra aquí ¿Podría señalarlo en la dirección correcta?

Del artículo:

Para resumir, los pasos para el procesamiento de entrada adecuado son:

  1. Obtenga un NumberFormat apropiado y defina una variable de posposición.
  2. Establezca el índice de parseposición en cero.
  3. Analice el valor de entrada con PARSE (fuente de cadena, parseposición parseposición).
  4. Realice las operaciones de error si la longitud de entrada y el valor del índice de parseposición no coinciden o si el número analizado es nulo.
  5. De lo contrario, el valor se aprobó la validación.

Este es un problema interesante. ¿Pero tal vez es un poco abierto? ¿Estás buscando específicamente para identificar los números de base 10, o hex, o qué? Supongo que base-10. ¿Qué pasa con la moneda? ¿Es eso importante? O son solo números.

En cualquier caso, creo que puede usar las deficiencias del formato de número para su ventaja. Como no es que algo como "1A", se interpretará como 1, ¿por qué no verificar el resultado formatándolo y comparando con la cadena original?

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é piensas?

Esto es realmente interesante, y creo que la gente está tratando de complicarlo demasiado. Realmente solo desglosaría esto por reglas:

1) Verifique la notación científica (¿coincide con el patrón de ser todos los números, comas, períodos, -/+ y tener una 'e' en ella?) -Si es así, analice como desee como desee

2) ¿coincide con el regexp para caracteres numéricos válidos (0-9 ,. - +) (solo 1. - o + permitido) Si es así, elimine todo lo que no sea un dígito y analice adecuadamente, de lo contrario falla.

No puedo ver un atajo que funcionará aquí, solo adopta el enfoque de fuerza bruta, no todo en la programación puede ser (o debe ser) completamente elegante.

Entiendo que desea cubrir los idiomas occidentales/latinos mientras conserva la mayor interpretación estricta posible. Entonces, lo que estoy haciendo aquí es pedirle decimalFormatsymbols que me digan cuál es la agrupación, el decimal, los separadores negativos y cero, y los intercambiarán por símbolos que el doble reconocerá.

¿Cómo funciona?

En los Estados Unidos, rechaza: "1a", "127.100.100.100" y acepta "1.47e-9"

En Alemania todavía rechaza "1a"

Acepta "1,024.00" pero lo interpreta correctamente como 1.024. Del mismo modo, acepta "127.100.100.100" como 127100100100.0

De hecho, la ubicación alemana identifica y analiza correctamente "1,47E-9"

Avísame si tienes algún problema en un lugar diferente.

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;
}

}

Lo mejor es hacerlo manualmente. Averigüe lo que puede aceptar como un número y no tenga en cuenta todo lo demás:

   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 establece su localidad correcta, incorporada parseDouble Trabajará con comas. El ejemplo es aquí.

Creo que tiene un proceso de múltiples pasos para manejar aquí con una solución personalizada, si no está dispuesto a aceptar los resultados de DecimalFormat o las respuestas ya vinculadas.

1) Identifique los separadores decimales y de agrupación. Es posible que deba identificar otros símbolos de formato (como indicadores de notación científica).

http://download.oracle.com/javase/1.4.2/docs/api/java/text/decimalformat.html#getdecimalformatsymbols()

2) Desprote todos los símbolos de agrupación (o cree una regex, tenga cuidado con otros símbolos que acepte, como el decimal si lo hace). Luego elimine el primer símbolo decimal. Otros símbolos según sea necesario.

3) llamar parse o isNumber.

Uno de los hacks fáciles sería usar replaceFirst Para la cadena obtienes y verifica la nueva cadena, ya sea un doble o no. En caso de que sea un doble: volver (si es necesario)

Si desea convertir algún número de cadena que sea decimal separado por comas al doble, puede usar 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;
}

Bueno, vamos a probarlo:

    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 ejecuta el código anterior, verá:

"1.2" = 0.0
"1.2" (with '.' as separator) = 1.2
"2,3" = 2.3
"A1" = 0.0
"127.0.0.1" = 0.0

Esto tomará una cadena, contará sus decimales y comas, eliminará las comas, conservará un decimal válido (tenga en cuenta que esto se basa en la estandarización de los Estados Unidos, para manejar 1.000.000,00 como 1 millón de este proceso tendría que tener el decimal y Manejo de coma conmutado), determine si la estructura es válida y luego devuelve un doble. Devuelve nulo si la cadena no se puede convertir. Editar: Se agregó apoyo para International o de EE. UU. ConvertStod (String, True) para nosotros, ConvertStod (String, False) para Non Us. Los comentarios son ahora para la versión de EE. UU.

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;
}

Este código debe manejar la mayoría de las entradas, excepto las direcciones IP donde todos los grupos de dígitos están en tres (Ej: 255.255.255.255 es válido, pero no 255.1.255.255). Tampoco es compatible con la notación científica

Funcionará con la mayoría de las variantes de los separadores (",", "." O espacio). Si se detecta más de un separador, se supone que el primero es el separador de miles, con cheques adicionales (validez, etc.)

Editar: Prevdigit se usa para verificar que el número usa mil separadores correctamente. Si hay más de un grupo de miles, todos menos el primero deben estar en grupos de 3. Modifiqué el código para dejarlo más claro para que "3" no sea un número mágico sino una constante.

Editar 2: No me importan mucho los votos bajos, pero ¿alguien puede explicar cuál es el problema?

/* 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 = '\0';
    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;
}

Código de prueba:

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       
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top