如何确定字符串中包含的值是否为双精度浮点型或非双精度浮点型。

28
在Java中,我正尝试确定字符串中包含的值是否为double类型?

2
你的双精度标准是什么?例如,如果你的字符串是 100,它既可以是双精度浮点数也可以是长整型。 - corsiKa
1
“double or not”是指双精度浮点数还是整数或其他什么类型? - akf
2
“double”是什么意思?你的意思是它是一个需要双精度的浮点数(与我们可以简单地称为“float”的单精度相对)?还是任何可以成为double的数字?(因此,Double.parseDouble不会在例如5上失败) - ShinTakezou
你可以尝试使用正则表达式进行匹配,具体方法请参考这里:http://www.regular-expressions.info/floatingpoint.html 相关问题:https://dev59.com/dUjSa4cB1Zd3GeqPJ_IX https://dev59.com/ZXE95IYBdhLWcg3wft1w - Ori Pessach
2
在你看来,“Infinity”字符串是一个double类型吗?顺便说一下,Double.parseDouble方法可以将其转换为double类型。 - aioobe
补充其他答案/评论:请记住,“表示双精度的字符串”也可能取决于所在地区。例如,在某些地区,逗号是小数分隔符。因此,在明确陈述您要做什么时,请小心。 - leonbloy
14个回答

35
    boolean isDouble(String str) {
        try {
            Double.parseDouble(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

8
这并非完全有效。例如,49d或49.d在此处返回true,这通常不是所期望的情况。 - ulrich
我们能否这样写: if(Double.isNaN(Double.parseDouble(str))){ return false; } else { return true; } - Balban

16

Double源代码中有一条注释:

[...] 为避免在无效的字符串上调用此方法并抛出NumberFormatException,可以使用下面的正则表达式过滤输入字符串: [...]

最终形式的正则表达式相当冗长:

[\x00-\x20]*[+-]?(NaN|Infinity|((((\p{Digit}+)(\.)?((\p{Digit}+)?)([eE][+-]?(\p{Digit}+))?)|(\.((\p{Digit}+))([eE][+-]?(\p{Digit}+))?)|(((0[xX](\p{XDigit}+)(\.)?)|(0[xX](\p{XDigit}+)?(\.)(\p{XDigit}+)))[pP][+-]?(\p{Digit}+)))[fFdD]?))[\x00-\x20]*

然而,使用此方法,您可以轻松排除一些特殊的双精度浮点数,例如InfinityNaN,它们都被Double.parseDouble接受。例如:

String regExp = "[\\x00-\\x20]*[+-]?(((((\\p{Digit}+)(\\.)?((\\p{Digit}+)?)([eE][+-]?(\\p{Digit}+))?)|(\\.((\\p{Digit}+))([eE][+-]?(\\p{Digit}+))?)|(((0[xX](\\p{XDigit}+)(\\.)?)|(0[xX](\\p{XDigit}+)?(\\.)(\\p{XDigit}+)))[pP][+-]?(\\p{Digit}+)))[fFdD]?))[\\x00-\\x20]*";
boolean matches = yourString.matches(regExp);

我将这个添加到基准测试中,但它比 Double.parseDouble(s) 解决方案稍微慢一些。我想在解析之前在防卫块中检查特殊的双精度值可能是最好的方法。 - Bill the Lizard
是的,我同意。parseDouble看起来相当优化:http://www.docjar.com/html/api/sun/misc/FloatingDecimal.java.html(readJavaFormatString) - aioobe
4
现在我已经预编译了正则表达式,它的性能超过了Double.parseDouble(s)方法。 - Bill the Lizard
@aioobe 真的很深入地挖掘了问题。 - Anand Kadhi

10

使用Scanner比使用Double.parseDouble(String s)慢得多。

private static Random rand = new Random();
private static final String regExp = "[\\x00-\\x20]*[+-]?(((((\\p{Digit}+)(\\.)?((\\p{Digit}+)?)([eE][+-]?(\\p{Digit}+))?)|(\\.((\\p{Digit}+))([eE][+-]?(\\p{Digit}+))?)|(((0[xX](\\p{XDigit}+)(\\.)?)|(0[xX](\\p{XDigit}+)?(\\.)(\\p{XDigit}+)))[pP][+-]?(\\p{Digit}+)))[fFdD]?))[\\x00-\\x20]*";
private static final Pattern pattern = Pattern.compile(regExp);

public static void main(String[] args) {

    int trials = 50000;
    String[] values = new String[trials];

    // initialize the array
    // about half the values will be parsable as double
    for( int i = 0; i < trials; ++i ) {
        double d = rand.nextDouble();
        boolean b = rand.nextBoolean();

        values[i] = (b ? "" : "abc") + d;
    }

    long start = System.currentTimeMillis();

    int parseCount = 0;
    for( int i = 0; i < trials; ++i ) {
        if( isDoubleParse(values[i]) ) {
            parseCount++;
        }
    }

    long end = System.currentTimeMillis();
    long elapsed = end - start;

    System.out.println("Elapsed time parsing: " + elapsed + " ms");
    System.out.println("Doubles: " + parseCount);

    // reset the timer for the next run
    start = System.currentTimeMillis();

    int scanCount = 0;
    for( int i = 0; i < trials; ++i ) {
        if( isDoubleScan(values[i]) ) {
            scanCount++;
        }
    }

    end = System.currentTimeMillis();
    elapsed = end - start;

    System.out.println("Elapsed time scanning: " + elapsed + " ms");
    System.out.println("Doubles: " + scanCount);


    // reset the timer for the next run
    start = System.currentTimeMillis();

    int regexCount = 0;
    for( int i = 0; i < trials; ++i ) {
        if( isDoubleRegex(values[i]) ) {
            regexCount++;
        }
    }

    end = System.currentTimeMillis();
    elapsed = end - start;

    System.out.println("Elapsed time regex (naive): " + elapsed + " ms");
    System.out.println("Doubles: " + naiveRegexCount);


    // reset the timer for the next run
    start = System.currentTimeMillis();

    int compiledRegexCount = 0;
    for( int i = 0; i < trials; ++i ) {
        if( isDoubleCompiledRegex(values[i]) ) {
            compiledRegexCount++;
        }
    }

    end = System.currentTimeMillis();
    elapsed = end - start;

    System.out.println("Elapsed time regex (compiled): " + elapsed + " ms");
    System.out.println("Doubles: " + compiledRegexCount);
}


public static boolean isDoubleParse(String s) {
    if( s == null ) return false;
    try {
        Double.parseDouble(s);
        return true;
    } catch (NumberFormatException e) {
        return false;
    }
}

public static boolean isDoubleScan(String s) {
    Scanner scanner = new Scanner(s);
    return scanner.hasNextDouble();
}

public static boolean isDoubleRegex(String s) {
    return s.matches(regExp);
}

public static boolean isDoubleCompiledRegex(String s) {
    Matcher m = pattern.matcher(s);
    return m.matches();
}

当我运行上面的代码时,我得到了以下输出:

解析所需时间:235毫秒
双精度数:24966
扫描所需时间:31358毫秒
双精度数:24966
正则表达式(朴素)所需时间:1829毫秒
双精度数:24966
正则表达式(编译)所需时间:109毫秒
双精度数:24966

给定正则表达式的复杂性,正则表达式方法运行得相当快,但仍然不如简单地使用 Double.parseDouble(s) 解析快。正如评论中指出的那样,有一些像 NaN 这样的值可以通过解析器而不应该通过。

更新:

如 @Gabe 所建议的那样预编译正则表达式会有很大的改进。编译后的正则表达式方法现在是明显的优胜者。

你可以再用正则表达式的方法运行一次吗?(https://dev59.com/v3A75IYBdhLWcg3wubun#3133917) - Gabe
是的,正则表达式有点复杂,但是当你从文档中获取它(http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Double.html)时,就会更容易理解。它还可以让你灵活地排除诸如“NaN”之类的内容。 - Gabe
@Gabe:这是一个不错的功能。我会尝试简化它并使用守卫子句来捕获一些特殊情况,但我担心我已经深陷“过早优化”的领域了。 - Bill the Lizard
4
我猜想,如果你使用Pattern.compile对正则表达式进行编译,并将其赋值给一个静态变量,那么模式匹配器在每次匹配时就不必重新编译它,这样正则表达式的版本就会更快。当然,它也可能在幕后执行一些已编译模式缓存的操作,因此可能不会有任何差别。 - Gabe
有关微基准测试的评论:您确实需要使用不同的运行时间来计时每种方法。基准代码应该看起来像真正的代码-没有巨大的方法(这似乎不像以前那样重要,但这只是我的印象)。System.nanoTime将提供更精细的计时。 - Tom Hawtin - tackline
显示剩余5条评论

7
您可以创建一个Scanner(String)对象,并使用hasNextDouble()方法。根据其javadoc所述,如果这个扫描器的下一个标记可以使用nextDouble()方法解释为double值,则返回true。扫描器不会超过任何输入。例如,以下代码片段:
List<String> values = Arrays.asList("foo", "1", "2.3", "1f", "0.2d", "3.14");
for (String source : values) {
    Scanner scanner = new Scanner(source);
    System.out.println(String.format("%4s: %s", source, scanner.hasNextDouble()));
}

会产生以下输出:

 foo: 假
   1: 真
 2.3: 真
  1f: 假
0.2d: 假
3.14: 真

1
使用Scanner比Double.parseDouble(s)慢得多(数量级之差,否则我不会提到它)。也许您可以查看下面的代码并建议加速的方法。我知道每次创建新的Scanner都不好。 - Bill the Lizard
1
@Bill 嗯,说实话,这个循环只是为了举例(而且要求不太清楚)。不过,我没想到会有这么大的差异。我会去看一下的。 - Pascal Thivent
我也真的期望这个能更快一些。扫描器在解析之前会检查输入,我认为这会给它一个优势,因为不会抛出异常。 - Bill the Lizard
谢谢你的出色回答,非常有帮助。 - Shridutt Kothari

6
public boolean isDouble(String value) {
    try {
        Double.parseDouble(value);
        return true;
    } catch (NumberFormatException e) {
        return false;
    }
}

如果您知道该值不能为 null,那就没问题了。Null(与 Integer.parseInt 不同)会抛出空指针异常,至少在我尝试的 JDK 版本中是这样。 - Yishai
6
添加空值检查真的那么难吗? - ILMTitan
@Yishai:或者,OP可能意味着有一个实际的字符串,而不仅仅是可以是字符串或null的东西。由于问题含糊不清,很难说。 - David Thornley
1
@ILMTitan,不是的,如果你了解它的话。Integer.parseInt的行为可能会让你误以为你不需要这样做。 - Yishai
@Yishai,原帖中提到的是“字符串中包含的值”,而不是可能为空的变量。您似乎只是在挑毛病。 - user207421

5

您可以使用Apache Commons Lang中的util类:

NumberUtils.isNumber(aString);

它是空值安全的,不需要使用try-catch块。

注意:对于解析双精度浮点数,如果小数分隔符是点.,则可以正常工作。

编辑:isNumber已被弃用并将在Lang 4.0中被移除

最好使用:

NumberUtils.isCreatable(aString);

3
我建议这样做:
try {
  d = Double.parseDouble(myString);
}
catch (NumberFormatException ex) {
    // Do something smart here...
}

3

您可以尝试使用Double.parseDouble(String s)解析它。

如果解析成功,它将返回double值;如果无法解析,则会抛出异常。

因此,您可以将整个过程包装在一个try-catch函数中,在获取实际值时返回true,在遇到异常时返回false。


如果该值是整数,则返回true。 - akf
3
@akf之所以如此说,是因为任何一个“int”值也可以是一个“double”。 - unbeli
我同意,我在想OP想要测试的区别是否是输入值是double还是整数。 - akf

2
我们必须处理NumberFormatException和null pointer exception来检查给定的字符串是数字还是字母数字混合。
public static boolean isNumeric(String strNum) {
        try {
           Double.parseDouble(strNum);
        } catch (NumberFormatException | NullPointerException nfe) {
            return false;
        }
        return true;
    }

1

其他人猜测您可能还想知道输入不是表示为整数。根据您的要求,这可能会快速且简单地完成工作:

public static void main(String[] args) throws Exception {
    System.out.println(isNonIntegerDouble("12"));  //false
    System.out.println(isNonIntegerDouble("12.1")); //true
    System.out.println(isNonIntegerDouble("12.0")); //true
}

public static boolean isNonIntegerDouble(String in) {
    try {
        Double.parseDouble(in);
    } catch (NumberFormatException nfe) {
        return false;
    }
    try {
        new BigInteger(in);
    } catch (NumberFormatException nfe) {
        return true;
    }
    return false;
}

目前我觉得字符串匹配会是一个更合适的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接