web-dev-qa-db-ja.com

文字列が数値であるかどうかを判断し、Javaで変換しますか?

私はこの質問の変種が以前に頻繁に尋ねられたことを知っています(たとえば ここここ を参照)が、これはではありませんそれらのexact複製。

Stringが数値であるかどうかを確認したいので、数値である場合はdoubleとして保存します。これを行うにはいくつかの方法がありますが、それらはすべて私の目的には不適切のようです。

1つの解決策は、Double.parseDouble(s)または同様にnew BigDecimal(s)を使用することです。ただし、カンマが存在する場合、これらのソリューションは機能しません(したがって、「1,234」は例外を引き起こします)。もちろん、これらの手法を使用する前にすべてのコンマを取り除くことはできますが、それは他のロケールで多くの問題を引き起こすように思われます。

Apache Commons NumberUtils.isNumber(s)を見ましたが、同じコンマの問題があります。

NumberFormatまたはDecimalFormatを検討しましたが、それらはあまりにも寛大であるように見えました。たとえば、「1A」は数値ではないことを示すのではなく、「1」にフォーマットされます。さらに、「127.0.0.1」のようなものは、それが数字ではないことを示すのではなく、数字127としてカウントされます。

私の要件は私が最初にこれを行うほどエキゾチックではないように感じますが、どのソリューションも私が必要とするものを正確に実行しません。何が必要か正確にわからないと思いますが(そうでなければ、独自のパーサーを書くことができます)、上記の解決策が機能しないことはわかっています示された理由。解決策はありますか、それとも必要なものを正確に把握して独自のコードを作成する必要がありますか?

24
Michael McGowan

かなり奇妙に聞こえますが、私は この回答 をフォローして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");

1A127.0.0.11,2346.02e-23などの入力の場合、次の出力が得られます。

Not a number
Not a number
1234
6.02E-23

Scanner.useLocaleを使用して、目的のロケールに変更できます。

15

必要なロケールを指定できます。

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

ドイツ語ロケールには小数点記号としてコンマがあるため、これは例で機能するはずです。

4
adranale

ParsePositionは、NumberFormat.parse操作で文字列が完全に消費されているかどうかのチェックとして使用できます。文字列が消費されている場合、「1A」の状況はありません。そうでない場合、あなたはそうし、それに応じて行動することができます。ソリューションの概要については here を、ParsePositionオプションのために修正されないとしてクローズされた関連するJDKバグについては here を参照してください。

4
philwb

それがすべての要件を満たしているかどうかはわかりませんが、見つかったコード ここ は正しい方向を示している可能性がありますか?

記事から:

要約すると、適切な入力処理の手順は次のとおりです。

  1. 適切なNumberFormatを取得し、ParsePosition変数を定義します。
  2. ParsePositionインデックスをゼロに設定します。
  3. Parse(String source、ParsePosition parsePosition)を使用して入力値を解析します。
  4. 入力長とParsePositionインデックス値が一致しない場合、または解析された数値がnullの場合は、エラー操作を実行します。
  5. それ以外の場合、値は検証に合格しました。
3
Peter Svensson

私の理解では、可能な限り厳密な解釈を維持しながら、西ロマンス語/ラテン語をカバーしたいと考えています。したがって、ここで行っているのは、DecimalFormatSymbolsに、グループ化、10進数、負、およびゼロの区切り記号を教えてもらい、それらをDoubleが認識する記号と交換することです。

どのように機能しますか?

米国では、「1A」、「127.100.100.100」を拒否し、「1.47E-9」を受け入れます。

ドイツではまだ「1A」を拒否しています

「1,024.00」を受け入れますが、1.024として正しく解釈します。同様に、「127.100.100.100」を127100100100.0として受け入れます。

実際、ドイツ語のロケールは「1,47E-9」を正しく識別して解析します

別のロケールで問題が発生した場合はお知らせください。

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

}
3
Falkreon

これは興味深い問題です。しかし、おそらくそれは少しオープンエンドですか? 10進数、16進数、または何を識別するために特に探していますか?私は10進数を想定しています。通貨はどうですか?それは重要ですか?それとも単なる数字ですか。

いずれにせよ、数値フォーマットの欠点を生かすことができると思います。 「1A」のようなものは1と解釈されますので、フォーマットして元の文字列と比較して結果を確認してみませんか?

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

どう思いますか?

3
jharig23

残念ながら、Double.parseDouble(s)または新しいBigDecimal(s)が最良のオプションのようです。

ローカリゼーションの懸念を引用しましたが、残念ながら、ユーザーによる指定なしですべてのロケールを確実にサポートする方法はありません。それは不可能です。

カンマとピリオドのどちらが最初に使用されているかを調べることで、使用されているスキームについて推論できる場合がありますが、両方が使用されている場合、これが常に可能であるとは限りません。より多くの状況で機能する可能性があるが、悪い結果をもたらす可能性があるシステムに依存しようとするよりも、特定の状況で確実に機能することがわかっているシステムを用意することをお勧めします...

123,456という数字は何を表していますか? 123456または123.456?

ユーザーが指定したロケールに応じて、コンマ、スペース、またはピリオドを削除するだけです。デフォルトでは、スペースとコンマを削除します。厳密にしたい場合は、カンマのみを削除しますORスペース、両方ではなく、ピリオドがある場合はピリオドの前のみ。また、適切な間隔で配置されているかどうかを手動で確認するのは非常に簡単です。実際、ここではカスタムパーサーが最も簡単かもしれません。

これが概念実証のビットです。それは少し(非常に)厄介ですが、私はそれがうまくいくと思います、そしてあなたはとにかくアイデアを得るでしょう:)。

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

編集:明らかにこれは科学的記数法を認識するために拡張する必要がありますが、これは十分に単純である必要があります。特に、eの後に実際に何も検証する必要がないため、形式が正しくない場合はparseDoubleを失敗させることができます。

また、これを使用してNumberFormatを適切に拡張することもお勧めします。解析された数値用のgetSeparator()と目的の出力形式を提供するためのsetSeparatorがあります...この種のローカリゼーションは処理されますが、小数の '、'をサポートするにはさらに作業が必要になります...

3
Elias Vasylenko

手動で行うのが最善です。あなたが数として受け入れることができるものを理解し、他のすべてを無視してください:

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

これは本当に面白いです、そして私は人々がそれを過度に複雑にしようとしていると思います。私は本当にこれをルールで分解します:

1)科学的記数法を確認します(すべての数字、コンマ、ピリオド、-/ +であり、「e」が含まれているパターンと一致しますか?)-ある場合は、必要に応じて解析します

2)有効な数字(0〜9 、。

ここで機能するショートカットがわかりません。力ずくのアプローチを取るだけです。プログラミングのすべてが完全にエレガントであるとは限りません(またはそうする必要があります)。

3
Rick Mangi

ロケールを正しく設定すると、組み込みのparseDoubleはカンマで機能します。例は ここ です。

1
Marek Sapota

カンマ区切りの10進数である文字列番号をdoubleに変換する場合は、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;
}

さて、それをテストしましょう:

    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, ','));

上記のコードを実行すると、次のように表示されます。

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

これは文字列を取り、その小数とコンマを数え、コンマを削除し、有効な小数を保存します(これは米国の標準化に基づいていることに注意してください-1.000.000,00を100万として処理するには、このプロセスは小数とカンマ処理が切り替えられました)、構造が有効かどうかを判断してから、doubleを返します。文字列を変換できなかった場合はnullを返します。 編集:国際または米国のサポートが追加されました。米国の場合はconvertStoD(string、true)、米国以外の場合はconvertStoD(string、false)。コメントは米国版用になりました。

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;
}
1
Travis J

簡単なハックの1つは、取得した文字列にreplaceFirstを使用し、新しい文字列がdoubleであるかどうかを確認することです。ダブルの場合-変換し直します(必要な場合)

1

DecimalFormatの結果またはすでにリンクされている回答を受け入れたくない場合は、カスタムソリューションでここで処理するためのマルチステッププロセスがあると思います。

1)小数とグループ化の区切り文字を特定します。他のフォーマット記号(科学的記数法など)を識別する必要がある場合があります。

http://download.Oracle.com/javase/1.4.2/docs/api/Java/text/DecimalFormat.html#getDecimalFormatSymbols ()

2)すべてのグループ化記号を削除します(または正規表現を作成します。受け入れる場合は、小数などの他の記号に注意してください)。次に、最初の10進記号を取り除きます。必要に応じて他の記号。

3)parseまたはisNumberを呼び出します。

1
Thomas Langston