24

我知道这个问题的变体以前经常被问到(例如,请参见此处此处),但这并不是这些问题的完全重复。

我想检查 aString是否是数字,如果是,我想将其存储为double. 有几种方法可以做到这一点,但它们似乎都不适合我的目的。

一种解决方案是使用Double.parseDouble(s)或类似new BigDecimal(s)的 . 但是,如果存在逗号,这些解决方案将不起作用(因此“1,234”会导致异常)。我当然可以在使用这些技术之前去掉所有逗号,但这似乎在其他语言环境中会带来很多问题。

我查看了 Apache Commons NumberUtils.isNumber(s),但也遇到了同样的逗号问题。

我考虑过NumberFormator DecimalFormat,但那些似乎太宽容了。例如,“1A”被格式化为“1”,而不是表示它不是一个数字。此外,诸如“127.0.0.1”之类的内容将被计为数字 127,而不是表示它不是数字。

我觉得我的要求并没有那么奇特,以至于我是第一个这样做的人,但是没有一个解决方案完全符合我的需要。我想即使我也不确切知道我需要什么(否则我可以编写自己的解析器),但我知道上述解决方案由于所示原因不起作用。是否存在任何解决方案,或者我是否需要准确地弄清楚我需要什么并为此编写自己的代码?

4

15 回答 15

16

听起来很奇怪,但我会尝试遵循这个答案并使用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,2346.02e-23我得到以下输出:

Not a number
Not a number
1234
6.02E-23

Scanner.useLocale可用于更改为所需的语言环境。

于 2012-02-08T00:21:23.840 回答
4

您可以指定所需的语言环境:

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

这应该在您的示例中有效,因为德语区域设置有逗号作为小数分隔符。

于 2012-02-07T09:11:39.240 回答
4

您可以使用 ParsePosition 作为检查 NumberFormat.parse 操作中字符串的完全消耗。如果字符串被消耗,那么你就没有“1A”的情况。如果不是,您可以这样做并且可以采取相应的行动。有关解决方案的快速概述,请参见此处,有关由于 ParsePosition 选项而关闭的相关 JDK 错误,请参见此处。

于 2012-02-07T15:59:04.610 回答
3

这真的很有趣,我认为人们正试图将其复杂化。我真的会按规则分解:

1) 检查科学记数法(它是否与所有数字、逗号、句点、-/+ 和其中包含“e”的模式匹配?)——如果是,请根据需要进行解析

2) 它是否与有效数字字符 (0-9 , . - +) 的正则表达式匹配(仅允许 1 . - 或 +),如果匹配,则去掉所有不是数字的内容并进行适当的解析,否则失败。

我看不到在这里可行的捷径,只是采用蛮力方法,并非编程中的所有内容都可以(或需要)完全优雅。

于 2012-02-11T19:32:26.833 回答
3

不确定它是否满足您的所有要求,但此处找到的代码可能会为您指明正确的方向?

来自文章:

总而言之,正确输入处理的步骤是:

  1. 获取适当的 NumberFormat 并定义 ParsePosition 变量。
  2. 将 ParsePosition 索引设置为零。
  3. 使用 parse(String source, ParsePosition parsePosition) 解析输入值。
  4. 如果输入长度和 ParsePosition 索引值不匹配或解析的 Number 为空,则执行错误操作。
  5. 否则,该值通过验证。
于 2012-02-08T21:28:45.733 回答
3

我的理解是,您希望涵盖西方/拉丁语言,同时保留尽可能严格的解释。所以我在这里做的是让 DecimalFormatSymbols 告诉我分组、小数、负数和零分隔符是什么,并将它们换成 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;
}

}
于 2012-02-11T21:35:47.287 回答
3

这是一个有趣的问题。但也许它有点开放?您是否正在专门寻找以 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;
    }
} 

你怎么看?

于 2012-02-11T15:39:18.873 回答
3

不幸的是 Double.parseDouble(s) 或 new BigDecimal(s) 似乎是你最好的选择。

您引用了本地化问题,但不幸的是,无论如何都无法可靠地支持所有没有用户指定的语言环境。这是不可能的。

有时您可以通过查看是否首先使用逗号或句点来推断所使用的方案,如果两者都使用,但这并不总是可能的,那么为什么还要尝试呢?最好拥有一个您知道在某些情况下可靠工作的系统,而不是尝试依赖一个可能在更多情况下工作但也会产生不良结果的系统......

123,456 这个数字代表什么?123456 还是 123.456?

只需根据用户指定的区域设置去除逗号、空格或句点。默认去除空格和逗号。如果你想让它更严格,只去除逗号或空格,而不是两者,如果有的话,只在句点之前。如果它们以三分之二的间隔正确排列,也应该很容易手动检查。事实上,自定义解析器在这里可能是最简单的。

这是一个概念证明。这有点(非常)凌乱,但我认为它有效,无论如何你都明白了:)。

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 ......这种处理本地化,但需要做更多的工作来支持小数的“,”......

于 2012-02-10T11:33:02.860 回答
3

你最好手动做。找出您可以接受的数字并忽略其他所有内容:

   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);
   }
   }
于 2012-02-12T11:31:44.063 回答
1

如果要将一些以逗号分隔的十进制数字转换为双精度数字,可以使用 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
于 2012-02-10T09:48:13.353 回答
1

一个简单的技巧是使用replaceFirst你得到的 String 并检查新的 String 是否是双精度的。万一它是双重转换回来(如果需要)

于 2012-02-07T16:04:06.390 回答
1

这将采用一个字符串,计算其小数点和逗号,删除逗号,保存一个有效的小数点(请注意,这是基于美国标准化 - 为了将 1.000.000,00 处理为 100 万,此过程必须具有小数点和逗号处理切换),确定结构是否有效,然后返回一个双精度。如果无法转换字符串,则返回 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;
}
于 2012-02-10T23:02:24.893 回答
1

如果您不愿意接受结果DecimalFormat或已经链接的答案,我认为您有一个多步骤的过程来处理自定义解决方案。

1) 识别小数和分组分隔符。您可能需要识别其他格式符号(例如科学记数法指示符)。

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

2)去掉所有分组符号(或制作一个正则表达式,如果你这样做,请注意你接受的其他符号,例如小数)。然后去掉第一个十进制符号。其他符号根据需要。

3) 呼叫parseisNumber

于 2011-05-04T20:11:56.703 回答
1

如果您设置正确的语言环境,内置parseDouble将使用逗号。例子在这里

于 2011-05-04T19:47:52.327 回答
-2

此代码应处理大多数输入,除了所有数字组均为三位的 IP 地址(例如:255.255.255.255 有效,但 255.1.255.255 无效)。它也不支持科学记数法

它适用于大多数分隔符变体(“,”,“。”或空格)。如果检测到多个分隔符,则假定第一个分隔符为千位分隔符,并附加检查(有效性等)

编辑: prevDigit 用于检查数字是否正确使用千位分隔符。如果有超过一组数千个,则除第一个之外的所有组都必须以 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       
}
于 2012-02-10T22:11:50.693 回答