文字列が数字であるかどうかを判断し、Javaで変換しますか?
-
28-10-2019 - |
質問
私はこの質問のバリエーションが以前に頻繁に尋ねられたことを知っています(参照してください ここ と ここ たとえば)、しかしこれはです いいえ an ちょうど それらの複製。
aを確認したいのですが String
数字です。もしそうなら、私はそれを double
. 。これを行うにはいくつかの方法がありますが、それらはすべて私の目的には不適切と思われます。
1つのソリューションは使用することです Double.parseDouble(s)
または同様に new BigDecimal(s)
. 。ただし、コンマが存在する場合、これらのソリューションは機能しません(したがって、「1,234」は例外を引き起こします)。もちろん、これらのテクニックを使用する前にすべてのコンマを取り除くこともできますが、それは他のロケールに多くの問題をもたらすように思われます。
私はアパッチのコモンズを見ました NumberUtils.isNumber(s)
, 、しかし、それは同じコンマの問題に苦しんでいます。
私は考慮した NumberFormat
また DecimalFormat
, 、しかし、それらはあまりにも寛大すぎるように見えました。たとえば、「1a」は、それが数ではないことを示すのではなく、「1」にフォーマットされます。さらに、「127.0.0.1」のようなものは、それが数ではないことを示すのではなく、数字127としてカウントされます。
私の要件はそれほどエキゾチックではないので、私がこれを最初に行うのはそうではないと感じていますが、ソリューションはどれも正確に必要なことをしていません。私も知らないと思います まさに 必要なもの(そうでなければ自分のパーサーを書くことができます)が、上記のソリューションは示されている理由で機能しないことを知っています。解決策は存在しますか、それとも自分が必要なものを正確に把握し、自分のコードを書く必要がありますか?
解決
非常に奇妙に聞こえますが、私はフォローしようとします この答え そして使用します 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");
などの入力用 1A
, 127.0.0.1
, 1,234
, 6.02e-23
次の出力を取得します。
Not a number
Not a number
1234
6.02E-23
Scanner.useLocale
目的のロケールに変更するために使用できます。
他のヒント
必要なロケールを指定できます。
NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN);
double myNumber = nf.parse(myString).doubleValue();
これは、ドイツのロケールには小桁分離器としてコンマがあるため、これはあなたの例で機能するはずです。
残念ながら、double.parsedouble(s)または新しいbigdecimal(s)があなたの最良の選択肢のようです。
ローカリゼーションの懸念を引用しますが、残念ながら、とにかくユーザーが仕様を備えたすべてのロケールを確実にサポートする方法はありません。不可能です。
カンマとピリオドが最初に使用されるかどうか、両方が使用されている場合に使用されるスキームについて推論することができる場合がありますが、これは常に可能ではないので、なぜ試してみてください。特定の状況で確実に機能するシステムを持っている方が、より多くの状況で動作するかもしれないが悪い結果をもたらす可能性があるものに依存しようとするよりも...
123,456番は何を表していますか? 123456または123.456?
ユーザーが指定したロケールに応じて、コンマ、スペース、または期間をストリップするだけです。デフォルトの剥離スペースとコンマ。より厳格にしたい場合は、ストリップのコンマまたはスペースのみではなく、ある場合にのみ、ある場合は期間の前にのみ。また、3つで適切に間隔を空けている場合は、手動でチェックするのが非常に簡単です。実際、ここではカスタムパーサーが最も簡単かもしれません。
ここに少し概念の証明があります。それは少し(非常に)乱雑ですが、私はそれが機能すると思います、そしてあなたはとにかくアイデアを得る:)。
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()と、望ましい出力形式を提供するためのセットセパレーターを持ってください...この種のローカリゼーションの世話をしてください。
すべての要件を満たしているかどうかはわかりませんが、コードが見つかりました ここ あなたを正しい方向に向けるかもしれませんか?
記事から:
要約すると、適切な入力処理の手順は次のとおりです。
- 適切なナンバーフォームを取得し、パーセポジション変数を定義します。
- パーセポジションインデックスをゼロに設定します。
- 入力値をparse(string source、parsoposition are position)で解析します。
- 入力長とパーセポジションインデックス値が一致しない場合、または解析された数値がnullの場合、エラー操作を実行します。
- それ以外の場合、値は検証に合格しました。
これは興味深い問題です。しかし、おそらくそれは少しオープンエンドですか?基本-10の数字、または六角形を特定するために特別に探していますか?私はBase-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;
}
}
どう思いますか?
これは本当に興味深いものであり、人々はそれを過度に複雑にしようとしていると思います。私は本当にこれをルールによって分解するだけです:
1)科学的表記を確認します(すべての数字、コンマ、ピリオド、 - /+であるというパターンと一致しますか?
2)有効な数値文字(0-9、。- +)(1。 - または +のみ)のregexpと一致します。もしそうなら、桁ではないものをすべて取り除き、適切に解析し、それ以外の場合は失敗します。
ここで機能するショートカットは見えません。ブルートフォースのアプローチをとるだけで、プログラミングのすべてが完全にエレガントである(または必要な)ことはありません。
私の理解では、可能な限り厳格な解釈を保持しながら、西洋/ラテン語をカバーしたいということです。それで、私がここでやっていることは、Decimalformatsymbolsにグループ化、10進数、負、およびゼロセパレーターが何であるかを教えてくれるように頼み、シンボルを2倍に交換することが認識されます。
どのように機能しますか?
米国では、「1A」、「127.100.100.100」を拒否し、「1.47E-9」を受け入れます。
ドイツでは、まだ「1a」を拒否します
「1,024.00」を受け入れますが、1.024として正しく解釈します。同様に、127100100100.0として「127.100.100.100」を受け入れます
実際、ドイツのロケールは「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;
}
}
あなたは手動でそれをするのが最善です。あなたが数として受け入れることができるものを理解し、他のすべてを無視します:
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);
}
}
ロケールを正しく設定した場合、組み込まれています parseDouble
コンマで動作します。例です ここ.
結果を受け入れたくない場合は、カスタムソリューションでここで処理するためのマルチステッププロセスがあると思います。 DecimalFormat
または、答えがすでにリンクされています。
1)小数とグループ化セパレータを特定します。他のフォーマット記号(科学表記指標など)を識別する必要がある場合があります。
2)すべてのグループ化シンボルを取り除きます(または、正規表現を作成します。1進数など、受け入れる他のシンボルに注意してください)。次に、最初の小数シンボルを取り除きます。必要に応じて他のシンボル。
3)電話してください parse
また isNumber
.
簡単なハックの1つは使用することです replaceFirst
文字列の場合は、ダブルであるかどうかにかかわらず、新しい文字列を確認します。それがダブルである場合に備えて - 戻って(必要に応じて)
小数を2倍に分離しているコンマのいくつかの文字列番号を変換する場合は、10進数小数体 +小数点の微調整を使用できます。
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
これは、文字列を取り、小数とコンマを数え、コンマを取り外し、有効な小数を保存します(これは米国の標準化に基づいていることに注意してください。コンマハンドリングが切り替えられました)、構造が有効かどうかを判断し、ダブルを返します。文字列を変換できなかった場合、nullを返します。 編集: :Internationalまたは米国のサポートを追加しました。 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;
}
このコードは、すべての桁のグループが3つのIPアドレスを除き、ほとんどの入力を処理する必要があります(例:255.255.255.255は有効ですが、255.1.255.255ではなく)。また、科学的表記もサポートしていません
セパレーターのほとんどのバリエーション( "、"、 "。"またはスペース)で動作します。複数のセパレーターが検出された場合、1つ目は数千のセパレーターであると想定され、追加のチェック(有効性など)
編集: prevdigitは、数字が1000のセパレーターを正しく使用することを確認するために使用されます。数千のグループが複数ある場合、最初のグループを除くすべてが3のグループでなければなりません。「3」が魔法の数字ではなく定数であるように、コードを明確にするために修正しました。
編集2: ダウン投票をあまり気にしませんが、誰かが問題が何であるかを説明できますか?
/* 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;
}
テストコード:
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
}