安全な文字列からBigDecimalへの変換


120

文字列からいくつかのBigDecimal値を読み取ろうとしています。たとえば、「1,000,000,000.999999999999999」という文字列があり、BigDecimalを取得したいとします。それを行う方法は何ですか?

まず第一に、文字列の置換(コンマの置き換えなど)を使用するソリューションは好きではありません。私にはその仕事をするためのきちんとしたフォーマッターがいるはずだと思います。

私はDecimalFormatterクラスを見つけましたが、それは2倍で動作するため、膨大な精度が失われます。

それで、どうすればそれを行うことができますか?


いくつかのカスタム形式が指定されているため、形式をBigDecimal互換形式に変換するのは面倒です。
bezmax

2
「カスタムフォーマットが与えられたので、それは苦痛です...」私は知らない、それは一種の問題ドメインを分離する。最初に文字列から人間が読める形式のものを削除し、次に結果を正しく効率的にに変換する方法を知っているものに引き渡しBigDecimalます。
TJクラウダー2010

回答:


90

setParseBigDecimalDecimalFormatで確認してください。このセッターを使用parseすると、BigDecimalが返されます。


1
BigDecimalクラスのコンストラクタは、カスタム形式をサポートしていません。質問で挙げた例では、カスタム形式(カンマ)を使用しています。コンストラクターを使用してそれをBigDecimalに解析することはできません。
bezmax

24
回答を完全に変更する場合は、回答でそれを言及することをお勧めします。そうでなければ、あなたの元の答えが意味をなさない理由を人々が指摘したとき、それは非常に奇妙に見えます。:-)
TJクラウダー

1
ええ、その方法に気づかなかった。ありがとう、それはそれを行うための最良の方法のようです。今すぐテストして、正しく動作するかどうかを確認してください。
bezmax

15
あなたは例を提供できますか?
Jウッドチャック、2016年

61
String value = "1,000,000,000.999999999999999";
BigDecimal money = new BigDecimal(value.replaceAll(",", ""));
System.out.println(money);

NumberFormatExceptionスローされないことを証明する完全なコード

import java.math.BigDecimal;

public class Tester {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String value = "1,000,000,000.999999999999999";
        BigDecimal money = new BigDecimal(value.replaceAll(",", ""));
        System.out.println(money);
    }
}

出力

1000000000.999999999999999


@Steve McLeod ....いや、私はちょうどそれをテストした....私の価値は1000000000.999999999999999
Buhake Sindi

10
ドイツのロケールで試して、
1.000.000.000,999999999999

@#TofuBeer ....例は1,000,000,000.999999999999999でした。私があなたのロケールをしなければならなかったならば、私はすべてのドットをスペースに置き換えなければなりません...
Buhake Sindi '20

14
したがって、安全ではありません。すべてのロケールを知っているわけではありません。
ymajoros 2014年

3
たとえばDecimalFormatSymbols.getInstance().getGroupingSeparator()、コンマの代わりに使用するだけで問題ありません。さまざまなロケールに悩まされる必要はありません。
Jason C

22

次のサンプルコードは適切に機能します(ロケールは動的に取得する必要があります)

import java.math.BigDecimal;
import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import java.util.Locale;

class TestBigDecimal {
    public static void main(String[] args) {

        String str = "0,00";
        Locale in_ID = new Locale("in","ID");
        //Locale in_ID = new Locale("en","US");

        DecimalFormat nf = (DecimalFormat)NumberFormat.getInstance(in_ID);
        nf.setParseBigDecimal(true);

        BigDecimal bd = (BigDecimal)nf.parse(str, new ParsePosition(0));

        System.out.println("bd value : " + bd);
    }
}

6

コードはもっとすっきりしているかもしれませんが、これはさまざまなロケールでうまくいくようです。

import java.math.BigDecimal;
import java.text.DecimalFormatSymbols;
import java.util.Locale;


public class Main
{
    public static void main(String[] args)
    {
        final BigDecimal numberA;
        final BigDecimal numberB;

        numberA = stringToBigDecimal("1,000,000,000.999999999999999", Locale.CANADA);
        numberB = stringToBigDecimal("1.000.000.000,999999999999999", Locale.GERMANY);
        System.out.println(numberA);
        System.out.println(numberB);
    }

    private static BigDecimal stringToBigDecimal(final String formattedString,
                                                 final Locale locale)
    {
        final DecimalFormatSymbols symbols;
        final char                 groupSeparatorChar;
        final String               groupSeparator;
        final char                 decimalSeparatorChar;
        final String               decimalSeparator;
        String                     fixedString;
        final BigDecimal           number;

        symbols              = new DecimalFormatSymbols(locale);
        groupSeparatorChar   = symbols.getGroupingSeparator();
        decimalSeparatorChar = symbols.getDecimalSeparator();

        if(groupSeparatorChar == '.')
        {
            groupSeparator = "\\" + groupSeparatorChar;
        }
        else
        {
            groupSeparator = Character.toString(groupSeparatorChar);
        }

        if(decimalSeparatorChar == '.')
        {
            decimalSeparator = "\\" + decimalSeparatorChar;
        }
        else
        {
            decimalSeparator = Character.toString(decimalSeparatorChar);
        }

        fixedString = formattedString.replaceAll(groupSeparator , "");
        fixedString = fixedString.replaceAll(decimalSeparator , ".");
        number      = new BigDecimal(fixedString);

        return (number);
    }
}

3

ここに私がそれをする方法があります:

public String cleanDecimalString(String input, boolean americanFormat) {
    if (americanFormat)
        return input.replaceAll(",", "");
    else
        return input.replaceAll(".", "");
}

明らかに、これが製品コードで行われている場合、それほど単純ではありません。

文字列からカンマを削除するだけで問題はありません。


文字列がアメリカ形式でない場合はどうなりますか?ドイツでは、1.000.000.000,999999999999999
Steve McLeod

1
ここでの主な問題は、番号がそれぞれ独自の形式を持つさまざまなソースからフェッチされる可能性があることです。したがって、この状況でそれを行うための最良の方法は、各ソースに対していくつかの「数値形式」を定義することです。1つのソースは「123 456 789」の形式で、もう1つのソースは「123.456.789」の形式である可能性があるため、単純な置き換えではできません。
bezmax

私はあなたが黙っ用語「1行法」... ;-)再定義されている参照
ペーテルTörök

@Steve:これは、明示的な処理を必要とするかなり基本的な違いであり、「正規表現はすべてに当てはまる」アプローチではありません。
Michael Borgwardt

2
@Max:「ここでの主な問題は、数値がそれぞれ独自の形式を持つさまざまなソースからフェッチされる可能性があることです。」これは制御するのではないコードに渡す前に、入力をクリーンアップするのはなく、を主張しています。
TJクラウダー2010

3

ロケールを知らず、ロケールに依存しないで文字列をBigDecimalに変換するソリューションが必要でした。この問題の標準的な解決策が見つからなかったため、独自のヘルパーメソッドを作成しました。それは他の人にも役立つかもしれません:

更新:警告!このヘルパーメソッドは10進数でのみ機能するため、常に小数点がある数値!そうでない場合、ヘルパーメソッドは1000〜999999(プラス/マイナス)の数値に対して誤った結果を提供する可能性があります。素晴らしい入力をしてくれたbezmaxに感謝します。

static final String EMPTY = "";
static final String POINT = '.';
static final String COMMA = ',';
static final String POINT_AS_STRING = ".";
static final String COMMA_AS_STRING = ",";

/**
     * Converts a String to a BigDecimal.
     *     if there is more than 1 '.', the points are interpreted as thousand-separator and will be removed for conversion
     *     if there is more than 1 ',', the commas are interpreted as thousand-separator and will be removed for conversion
     *  the last '.' or ',' will be interpreted as the separator for the decimal places
     *  () or - in front or in the end will be interpreted as negative number
     *
     * @param value
     * @return The BigDecimal expression of the given string
     */
    public static BigDecimal toBigDecimal(final String value) {
        if (value != null){
            boolean negativeNumber = false;

            if (value.containts("(") && value.contains(")"))
               negativeNumber = true;
            if (value.endsWith("-") || value.startsWith("-"))
               negativeNumber = true;

            String parsedValue = value.replaceAll("[^0-9\\,\\.]", EMPTY);

            if (negativeNumber)
               parsedValue = "-" + parsedValue;

            int lastPointPosition = parsedValue.lastIndexOf(POINT);
            int lastCommaPosition = parsedValue.lastIndexOf(COMMA);

            //handle '1423' case, just a simple number
            if (lastPointPosition == -1 && lastCommaPosition == -1)
                return new BigDecimal(parsedValue);
            //handle '45.3' and '4.550.000' case, only points are in the given String
            if (lastPointPosition > -1 && lastCommaPosition == -1){
                int firstPointPosition = parsedValue.indexOf(POINT);
                if (firstPointPosition != lastPointPosition)
                    return new BigDecimal(parsedValue.replace(POINT_AS_STRING, EMPTY));
                else
                    return new BigDecimal(parsedValue);
            }
            //handle '45,3' and '4,550,000' case, only commas are in the given String
            if (lastPointPosition == -1 && lastCommaPosition > -1){
                int firstCommaPosition = parsedValue.indexOf(COMMA);
                if (firstCommaPosition != lastCommaPosition)
                    return new BigDecimal(parsedValue.replace(COMMA_AS_STRING, EMPTY));
                else
                    return new BigDecimal(parsedValue.replace(COMMA, POINT));
            }
            //handle '2.345,04' case, points are in front of commas
            if (lastPointPosition < lastCommaPosition){
                parsedValue = parsedValue.replace(POINT_AS_STRING, EMPTY);
                return new BigDecimal(parsedValue.replace(COMMA, POINT));
            }
            //handle '2,345.04' case, commas are in front of points
            if (lastCommaPosition < lastPointPosition){
                parsedValue = parsedValue.replace(COMMA_AS_STRING, EMPTY);
                return new BigDecimal(parsedValue);
            }
            throw new NumberFormatException("Unexpected number format. Cannot convert '" + value + "' to BigDecimal.");
        }
        return null;
    }

もちろん、私はメソッドをテストしました:

@Test(dataProvider = "testBigDecimals")
    public void toBigDecimal_defaultLocaleTest(String stringValue, BigDecimal bigDecimalValue){
        BigDecimal convertedBigDecimal = DecimalHelper.toBigDecimal(stringValue);
        Assert.assertEquals(convertedBigDecimal, bigDecimalValue);
    }
    @DataProvider(name = "testBigDecimals")
    public static Object[][] bigDecimalConvertionTestValues() {
        return new Object[][] {
                {"5", new BigDecimal(5)},
                {"5,3", new BigDecimal("5.3")},
                {"5.3", new BigDecimal("5.3")},
                {"5.000,3", new BigDecimal("5000.3")},
                {"5.000.000,3", new BigDecimal("5000000.3")},
                {"5.000.000", new BigDecimal("5000000")},
                {"5,000.3", new BigDecimal("5000.3")},
                {"5,000,000.3", new BigDecimal("5000000.3")},
                {"5,000,000", new BigDecimal("5000000")},
                {"+5", new BigDecimal("5")},
                {"+5,3", new BigDecimal("5.3")},
                {"+5.3", new BigDecimal("5.3")},
                {"+5.000,3", new BigDecimal("5000.3")},
                {"+5.000.000,3", new BigDecimal("5000000.3")},
                {"+5.000.000", new BigDecimal("5000000")},
                {"+5,000.3", new BigDecimal("5000.3")},
                {"+5,000,000.3", new BigDecimal("5000000.3")},
                {"+5,000,000", new BigDecimal("5000000")},
                {"-5", new BigDecimal("-5")},
                {"-5,3", new BigDecimal("-5.3")},
                {"-5.3", new BigDecimal("-5.3")},
                {"-5.000,3", new BigDecimal("-5000.3")},
                {"-5.000.000,3", new BigDecimal("-5000000.3")},
                {"-5.000.000", new BigDecimal("-5000000")},
                {"-5,000.3", new BigDecimal("-5000.3")},
                {"-5,000,000.3", new BigDecimal("-5000000.3")},
                {"-5,000,000", new BigDecimal("-5000000")},
                {null, null}
        };
    }

1
申し訳ありませんが、エッジケースがあるため、これがどのように役立つかわかりません。たとえば、何が何を7,333表すかをどのようにして知るのですか?7333または7と1/3(7.333)ですか?ロケールが異なれば、その数は異なる方法で解析されます。概念実証は次のとおり
bezmax

良い入力、実際にはこの問題はありませんでした。私は私のポストを更新します、ありがとうございました!そして、どこで問題が発生するかを知るために、これらのロケールスペシャルのテストを改善します。
user3227576 2017年

1
さまざまなロケールとさまざまな数値についての解決策を確認しました...小数点付きの数値が常にあるため(テキストファイルからMoneyの値を読み取っているため)、すべての作業が正常に機能します。回答に警告を追加しました。
user3227576 2017年

2
resultString = subjectString.replaceAll("[^.\\d]", "");

文字列から数字とドットを除くすべての文字を削除します。

ロケール対応にするためにgetDecimalSeparator()、from を使用することができますjava.text.DecimalFormatSymbols。Javaは知りませんが、次のようになります。

sep = getDecimalSeparator()
resultString = subjectString.replaceAll("[^"+sep+"\\d]", "");

これは、アメリカ人以外の番号に対して予期しない結果をもたらします-ジャスティンの答えへのコメントを参照してください。
ペーテルTörök

2

古いトピックですが、おそらく最も簡単なのは、createBigDecimal(文字列値)メソッドを持つApache commons NumberUtilsを使用することです。

ロケールを考慮に入れていると思います(そうしないと)、そうでなければ、かなり役に立たなくなるでしょう。


ロケールを考慮しないNumberUtilsソースコードで述べられているように、createBigDecimalは新しいBigDecimal(string)のラッパーにすぎませんAFAIK
Mar Bar

1

これを試してみてください

BigDecimal bd ;
String value = "2000.00";

bd = new BigDecimal(value);
BigDecimal currency = bd;

2
この方法は、小数点およびththhandsグループのカスタム区切り文字の指定をサポートしていません。
bezmax

はい、ただし、これはHashMapなどのオブジェクトから文字列BigDecimal値を取得し、Bigdecimal値に格納して他のサービスを呼び出すためのものです
Rajkumar Teckraft

1
はい、しかしそれは問題ではありませんでした。
bezmax 2015年

私にも
有効
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.