Güvenli String'den BigDecimal'e dönüştürme


120

Dizeden bazı BigDecimal değerleri okumaya çalışıyorum. Şu dizeye sahip olduğumu varsayalım: "1,000,000,000.999999999999999" ve bundan bir BigDecimal almak istiyorum. Bunu yapmanın yolu nedir?

Her şeyden önce, dize değiştirmeleri kullanan çözümleri (virgül değiştirme vb.) Sevmiyorum. Bu işi benim için yapacak düzgün bir formatlayıcı olması gerektiğini düşünüyorum.

Bir DecimalFormatter sınıfı buldum, ancak iki katına kadar çalıştığı için çok büyük miktarlarda hassasiyet kayboluyor.

Peki bunu nasıl yapabilirim?


Çünkü bazı özel formatlar verildiğinde, formatınızı BigDecimal uyumlu formata dönüştürmek zor.
bezmax

2
"Çünkü bazı özel formatlar verildiğinde, bu bir acıdır ..." Bilmiyorum, problem alanlarını bir şekilde ayırıyor. Önce insan tarafından okunabilen şeyleri dizeden temizlersiniz, ardından sonucu doğru ve verimli bir şekilde nasıl a'ya dönüştürebileceğini bilen bir şeye aktarırsınız BigDecimal.
TJ Crowder

Yanıtlar:


90

setParseBigDecimalDecimalFormat'ta kontrol edin . Bu ayarlayıcıyla, parsesizin için bir BigDecimal döndürür.


1
BigDecimal sınıfındaki yapıcı özel biçimleri desteklemez. Soruda verdiğim örnek özel formatı (virgül) kullanıyor. Bunu yapıcısını kullanarak BigDecimal'e ayrıştıramazsınız.
bezmax

24
Cevabınızı tamamen değiştirecekseniz, cevapta bundan bahsetmenizi öneririm. Aksi takdirde, insanlar orijinal cevabınızın neden bir anlam ifade etmediğini belirttikleri zaman çok tuhaf görünüyor. :-)
TJ Crowder

1
Evet, o yöntemi fark etmedim. Teşekkürler, bunu yapmanın en iyi yolu bu gibi görünüyor. Şimdi test edeceğim ve düzgün çalışıyorsa - cevabı kabul et.
bezmax

15
Bir örnek verebilir misiniz?
J Woodchuck

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

Hiçbir NumberFormatExceptionşeyin atılmadığını kanıtlamak için tam kod :

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

Çıktı

1000000000,999999999999999


@Steve McLeod .... hayır, sadece test ettim .... 1000000000.999999999999999
değerim

10
ALMAN yerel ayarı ve 1.000.000.000,999999999999 ile deneyin
TofuBeer

@ # TofuBeer .... örnek 1.000.000.000.999999999999999 idi. Yerel ayarınızı yapmak zorunda kalsaydım, tüm noktaları boşluğa yerleştirmek zorunda
kalırdım

14
Yani güvenli değil. Tüm yerel ayarları bilmiyorsunuz.
ymajoros

3
Örneğin DecimalFormatSymbols.getInstance().getGroupingSeparator()virgül yerine sadece kullanırsanız sorun değil, değişen yerel ayarlar konusunda endişelenmenize gerek yok.
Jason C

22

Aşağıdaki örnek kod iyi çalışıyor (yerel ayarın dinamik olarak elde edilmesi gerekir)

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

Kod daha temiz olabilir, ancak bu, farklı yerel ayarlar için hile yapıyor gibi görünüyor.

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

İşte bunu nasıl yapacağım:

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

Açıkçası, bu üretim koduna girecek olsaydı, o kadar basit olmazdı.

Dize'den virgül kaldırmada sorun görmüyorum.


Ve dize Amerikan formatında değilse? Almanya'da 1.000.000.000,999999999999999 olacaktır
Steve McLeod

1
Buradaki temel sorun, sayıların her biri kendi formatına sahip farklı kaynaklardan getirilebilmesidir. Bu durumda bunu yapmanın en iyi yolu, kaynakların her biri için bir "sayı biçimi" tanımlamaktır. Bir kaynak "123 456 789" ve diğer "123.456.789" biçimine sahip olabileceğinden, bunu basit değiştirmelerle yapamam.
bezmax

Sessizce "tek satırlık yöntem" terimini yeniden tanımladığınızı görüyorum ... ;-)
Péter Török

@Steve: Bu, "regexp herkese uyar" yaklaşımı değil, açık işlem gerektiren oldukça temel bir fark.
Michael Borgwardt

2
@Max: "Buradaki temel sorun, sayıların her biri kendi formatına sahip farklı kaynaklardan getirilebilmesidir." Hangi savunuyor için siz de kontrol etmiyoruz koda kapalı teslim önce girişi temizlik, daha doğrusu karşı daha.
TJ Crowder

3

Yerel ayarı bilmeden ve yerel ayardan bağımsız olmadan bir String'i BigDecimal'e dönüştürmek için bir çözüme ihtiyacım vardı. Bu soruna standart bir çözüm bulamadığım için kendi yardımcı yöntemimi yazdım. Başkalarına da yardımcı olabilir:

Güncelleme: Uyarı! Bu yardımcı yöntem yalnızca ondalık sayılar için çalışır, bu nedenle her zaman bir ondalık noktası olan sayılar! Aksi takdirde, yardımcı yöntem 1000 ile 999999 (artı / eksi) arasındaki sayılar için yanlış sonuç verebilir. Bezmax'a harika katkılarından dolayı teşekkürler!

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

Elbette yöntemi test ettim:

@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
Üzgünüm ama uç durumlar nedeniyle bunun nasıl yararlı olabileceğini anlamıyorum. Örneğin, neyin 7,333temsil ettiğini nasıl anlarsınız ? 7333 mü yoksa 7 ve 1/3 (7.333) mü? Farklı yerel ayarlarda bu sayı farklı şekilde ayrıştırılır. İşte konseptin kanıtı: ideone.com/Ue9rT8
bezmax

İyi girdi, pratikte bu problemi hiç yaşamadım. Yazımı güncelleyeceğim, çok teşekkür ederim! Ve bu Locale özel ürünlerinin testini, sadece nerede sorun yaşayacağını bilmek için iyileştireceğim.
user3227576

1
Çözümü farklı yerel ayarlar ve farklı sayılar için kontrol ettim ... ve hepimiz işe yarıyor, çünkü her zaman ondalık noktalı sayılara sahibiz (bir metin dosyasından Money değerlerini okuyoruz). Cevaba bir uyarı ekledim.
user3227576

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

dizenizden rakamlar ve nokta hariç tüm karakterleri kaldıracaktır.

Yerel ayara duyarlı hale getirmek için getDecimalSeparator()şuradan kullanmak isteyebilirsiniz java.text.DecimalFormatSymbols. Java bilmiyorum ama şöyle görünebilir:

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

Amerikan olmayan sayılar için beklenmedik sonuçlar verecek - Justin'in cevabına yapılan yorumlara bakın.
Péter Török

2

Eski konu ama belki de en kolayı createBigDecimal (String değeri) .... metoduna sahip Apache commons NumberUtils kullanmaktır.

Sanırım (umarım) yerel ayarları hesaba katar, yoksa oldukça faydasız olur.


createBigDecimal, NumberUtils Kaynak Kodunda belirtildiği gibi, Locale AFAIK
Mar Bar

1

Lütfen bunu dene benim için çalışıyor

BigDecimal bd ;
String value = "2000.00";

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

2
Bu yol, ondalık nokta ve thouthands grupları için özel sınırlayıcıların belirlenmesini desteklemez.
bezmax

Evet, ancak bu, HashMap gibi nesneden BigDecimal dizesini almak ve diğer hizmetleri çağırmak için Bigdecimal değeri depolamak içindir
Rajkumar Teckraft

1
Evet, ama soru bu değildi.
bezmax

Benim için de çalışıyor
Sathesh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.