Konversi String Aman ke BigDecimal

120

Saya mencoba membaca beberapa nilai BigDecimal dari string. Katakanlah saya memiliki String ini: "1,000,000,000.999999999999999" dan saya ingin mendapatkan BigDecimal darinya. Bagaimana cara melakukannya?

Pertama-tama, saya tidak suka solusi yang menggunakan penggantian string (mengganti koma, dll.). Saya pikir harus ada beberapa pembuat format yang rapi untuk melakukan pekerjaan itu untuk saya.

Saya telah menemukan kelas DecimalFormatter, namun karena beroperasi melalui dua kali lipat - jumlah presisi yang sangat besar akan hilang.

Jadi, bagaimana caranya?

bezmax.dll
sumber
Karena diberikan beberapa format kustom, sulit untuk mengubahnya menjadi format yang kompatibel dengan BigDecimal.
bezmax
2
"Karena diberi beberapa format kustom, itu merepotkan ..." Entahlah, itu semacam memisahkan domain yang bermasalah. Pertama Anda membersihkan barang-barang yang dapat dibaca manusia dari string, kemudian menyerahkan ke sesuatu yang tahu bagaimana cara yang benar dan efisien mengubah hasilnya menjadi BigDecimal.
TJ Crowder

Jawaban:

90

Lihat setParseBigDecimaldi DecimalFormat. Dengan setter ini, parseakan mengembalikan BigDecimal untuk Anda.

Jeroen Rosenberg
sumber
1
Konstruktor di kelas BigDecimal tidak mendukung format kustom. Contoh yang saya berikan dalam pertanyaan menggunakan format khusus (koma). Anda tidak dapat menguraikannya ke BigDecimal menggunakan konstruktornya.
bezmax
24
Jika Anda akan mengubah jawaban Anda sepenuhnya , saya sarankan untuk menyebutkannya dalam jawaban. Jika tidak, akan terlihat sangat aneh ketika orang menunjukkan mengapa jawaban awal Anda tidak masuk akal. :-)
TJ Crowder
1
Ya, tidak memperhatikan metode itu. Terima kasih, sepertinya itu cara terbaik untuk melakukannya. Akan mengujinya sekarang dan jika berfungsi dengan baik - terima jawabannya.
bezmax
15
dapatkah anda memberikan contoh?
J Woodchuck
61
String value = "1,000,000,000.999999999999999";
BigDecimal money = new BigDecimal(value.replaceAll(",", ""));
System.out.println(money);

Kode lengkap untuk membuktikan bahwa tidak ada NumberFormatExceptionyang dibuang:

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

Keluaran

1000000000.999999999999999

Buhake Sindi
sumber
@Steve McLeod .... tidak, saya baru saja mengujinya .... nilai saya adalah1000000000.999999999999999
Buhake Sindi
10
Coba dengan lokal JERMAN, dan 1.000.000.000,999999999999
TofuBeer
@ # TofuBeer .... contohnya adalah 1.000.000.000.999999999999999. Jika saya harus
menentukan
14
Jadi, ini tidak aman. Anda tidak tahu semua lokal.
ymajoros
3
Tidak apa-apa jika Anda hanya menggunakan mis, DecimalFormatSymbols.getInstance().getGroupingSeparator()bukan koma, tidak perlu panik tentang berbagai lokal.
Jason C
22

Kode contoh berikut berfungsi dengan baik (lokal perlu diperoleh secara dinamis)

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);
    }
}
Ebith
sumber
6

Kode mungkin lebih bersih, tetapi ini tampaknya melakukan trik untuk lokal yang berbeda.

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);
    }
}
TofuBeer
sumber
3

Inilah cara saya melakukannya:

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

Jelas, jika ini menggunakan kode produksi, tidak akan sesederhana itu.

Saya tidak melihat masalah hanya dengan menghapus koma dari String.

jjnguy
sumber
Dan apakah string tersebut bukan dalam format Amerika? Di Jerman, itu akan menjadi 1.000.000.000,999999999999999
Steve McLeod
1
Masalah utama di sini adalah bahwa nomor dapat diambil dari sumber yang berbeda, masing-masing memiliki formatnya sendiri. Jadi cara terbaik untuk melakukannya dalam situasi ini adalah dengan mendefinisikan beberapa "format angka" untuk masing-masing sumber. Saya tidak dapat melakukannya dengan penggantian sederhana karena satu sumber dapat memiliki format "123 456 789" dan yang lainnya "123.456.789".
bezmax
Saya melihat Anda diam-diam mendefinisikan ulang istilah "metode satu baris" ... ;-)
Péter Török
@ Steve: itu perbedaan yang cukup mendasar yang memerlukan penanganan eksplisit, bukan pendekatan "regexp cocok untuk semua".
Michael Borgwardt
2
@Max: "Masalah utama di sini adalah bahwa nomor dapat diambil dari sumber yang berbeda masing-masing memiliki formatnya sendiri." Yang berpendapat untuk , bukan melawan, Anda membersihkan masukan sebelum menyerahkan off untuk kode Anda tidak mengontrol.
TJ Crowder
3

Saya membutuhkan solusi untuk mengonversi String ke BigDecimal tanpa mengetahui lokal dan menjadi independen lokal. Saya tidak dapat menemukan solusi standar untuk masalah ini jadi saya menulis metode pembantu saya sendiri. Mungkin itu membantu orang lain juga:

Pembaruan: Peringatan! Metode pembantu ini hanya berfungsi untuk bilangan desimal, jadi bilangan yang selalu memiliki titik desimal! Jika tidak, metode helper bisa memberikan hasil yang salah untuk angka antara 1000 dan 999999 (plus / minus). Terima kasih kepada bezmax atas masukannya yang luar biasa!

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

Tentu saja saya sudah menguji metodenya:

@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}
        };
    }
pengguna3227576
sumber
1
Maaf, tapi saya tidak melihat bagaimana ini bisa berguna karena kasus tepi. Misalnya, bagaimana Anda tahu apa yang 7,333mewakili? Apakah 7333 atau 7 dan 1/3 (7,333)? Di berbagai lokal, nomor itu akan diuraikan secara berbeda. Berikut bukti dari konsep: ideone.com/Ue9rT8
bezmax
Masukan yang bagus, tidak pernah ada masalah ini dalam praktiknya. Saya akan memperbarui posting saya, terima kasih banyak! Dan saya akan meningkatkan pengujian untuk spesial Lokal ini hanya untuk mengetahui di mana akan menerima masalah.
pengguna3227576
1
Saya telah memeriksa solusi untuk Lokal yang berbeda dan angka yang berbeda ... dan untuk kita semua bekerja, karena kita selalu memiliki angka dengan titik desimal (kita membaca nilai uang dari file teks). Saya telah menambahkan peringatan ke jawabannya.
pengguna3227576
2
resultString = subjectString.replaceAll("[^.\\d]", "");

akan menghapus semua karakter kecuali angka dan titik dari string Anda.

Untuk membuatnya sadar-lokal, Anda mungkin ingin menggunakan getDecimalSeparator()dari java.text.DecimalFormatSymbols. Saya tidak tahu Java, tetapi tampilannya mungkin seperti ini:

sep = getDecimalSeparator()
resultString = subjectString.replaceAll("[^"+sep+"\\d]", "");
Tim Pietzcker
sumber
Yang akan memberikan hasil yang tidak terduga untuk nomor non-Amerika - lihat komentar untuk jawaban Justin.
Péter Török
2

Topik lama tapi mungkin yang paling mudah adalah menggunakan Apache commons NumberUtils yang memiliki metode createBigDecimal (String value) ....

Saya kira (harap) ini memperhitungkan lokal atau itu akan menjadi agak tidak berguna.

Lawrence
sumber
createBigDecimal hanya pembungkus untuk BigDecimal baru (string), seperti yang dinyatakan dalam NumberUtils Source Code yang menganggap tidak ada Lokal AFAIK
Mar Bar
1

Silakan coba ini berhasil untuk saya

BigDecimal bd ;
String value = "2000.00";

bd = new BigDecimal(value);
BigDecimal currency = bd;
Rajkumar Teckraft
sumber
2
Cara ini tidak mendukung penentuan pembatas kustom untuk grup titik desimal dan tanda tangan.
bezmax
Ya, tetapi ini untuk mendapatkan nilai string BigDecimal dari objek seperti HashMap dan menyimpannya ke dalam nilai Bigdecimal untuk memanggil layanan lain
Rajkumar Teckraft
1
Ya, tapi bukan itu pertanyaannya.
bezmax
Bekerja untuk saya juga
Sathesh