Chuyển đổi Chuỗi an toàn sang BigDecimal


120

Tôi đang cố đọc một số giá trị BigDecimal từ chuỗi. Giả sử tôi có Chuỗi này: "1.000.000.000.999999999999999" và tôi muốn lấy BigDecimal từ nó. Cách làm là gì?

Trước hết, tôi không thích các giải pháp sử dụng chuỗi thay thế (thay thế dấu phẩy, v.v.). Tôi nghĩ rằng nên có một số định dạng gọn gàng để làm công việc đó cho tôi.

Tôi đã tìm thấy một lớp DecimalFormatter, tuy nhiên khi nó hoạt động thông qua gấp đôi - lượng lớn độ chính xác bị mất.

Vì vậy, làm thế nào tôi có thể làm điều đó?


Bởi vì được cung cấp một số định dạng tùy chỉnh, thật khó để làm cho nó chuyển đổi định dạng của bạn thành định dạng tương thích với BigDecimal.
bezmax

2
"Bởi vì được đưa ra một số định dạng tùy chỉnh, đó là một nỗi đau ..." Tôi không hiểu, nó giống như phân tách các miền vấn đề. Đầu tiên, bạn xóa những thứ mà con người có thể đọc được ra khỏi chuỗi, sau đó chuyển giao cho thứ biết cách biến kết quả thành a một cách chính xác và hiệu quả BigDecimal.
TJ Crowder

Câu trả lời:


90

Kiểm tra setParseBigDecimaltrong DecimalFormat. Với setter này, parsesẽ trả về BigDecimal cho bạn.


1
Hàm tạo trong lớp BigDecimal không hỗ trợ các định dạng tùy chỉnh. Ví dụ tôi đưa ra trong câu hỏi sử dụng định dạng tùy chỉnh (dấu phẩy). Bạn không thể phân tích cú pháp đó thành BigDecimal bằng cách sử dụng hàm tạo của nó.
bezmax

24
Nếu bạn định thay đổi hoàn toàn câu trả lời của mình, tôi khuyên bạn nên đề cập đến nó trong câu trả lời. Mặt khác, có vẻ rất kỳ lạ khi mọi người đã chỉ ra lý do tại sao câu trả lời ban đầu của bạn không có ý nghĩa gì. :-)
TJ Crowder

1
Vâng, không nhận thấy phương pháp đó. Cảm ơn, đó có vẻ là cách tốt nhất để làm điều đó. Sẽ kiểm tra nó ngay bây giờ và nếu nó hoạt động đúng - chấp nhận câu trả lời.
bezmax

15
bạn có thể cung cấp một ví dụ?
J Woodchuck

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

Mã đầy đủ để chứng minh rằng không NumberFormatExceptionđược ném:

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

Đầu ra

1000000000.999999999999999


@Steve McLeod .... nope, tôi chỉ thử nghiệm nó .... giá trị của tôi là1000000000.999999999999999
Buhake Sindi

10
Hãy thử với ngôn ngữ ĐỨC, và 1.000.000.000.999999999999
TofuBeer 20/09/10

@ # TofuBeer .... ví dụ là 1.000.000.000.999999999999999. Nếu tôi phải làm miền địa phương của bạn, tôi sẽ phải thay thế tất cả dấu chấm để không gian ....
Buhake Sindi

14
Vì vậy, nó không an toàn. Bạn không biết tất cả các ngôn ngữ.
ymajoros

3
Sẽ ổn nếu bạn chỉ sử dụng ví dụ DecimalFormatSymbols.getInstance().getGroupingSeparator()thay vì dấu phẩy, không cần phải lo lắng về các ngôn ngữ khác nhau.
Jason C

22

Mã mẫu sau hoạt động tốt (ngôn ngữ cần được lấy động)

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

Mã có thể rõ ràng hơn, nhưng điều này dường như không phù hợp với các ngôn ngữ khác nhau.

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

Đây là cách tôi sẽ làm điều đó:

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

Rõ ràng, nếu điều này diễn ra trong mã sản xuất, nó sẽ không đơn giản như vậy.

Tôi không thấy vấn đề gì với việc chỉ cần xóa dấu phẩy khỏi Chuỗi.


Và nếu chuỗi không có định dạng kiểu Mỹ? Ở Đức, nó sẽ là 1.000.000.000.999999999999999
Steve McLeod

1
Vấn đề chính ở đây là các con số có thể được lấy từ các nguồn khác nhau, mỗi nguồn có định dạng riêng. Vì vậy, cách tốt nhất để làm điều đó trong tình huống này là xác định một số "định dạng số" cho mỗi nguồn. Tôi không thể làm điều đó với các thay thế đơn giản vì một nguồn có thể có định dạng "123 456 789" và định dạng còn lại "123.456.789".
bezmax

Tôi thấy bạn đang âm thầm định nghĩa lại thuật ngữ "phương pháp một dòng" ... ;-)
Péter Török

@Steve: đó là một sự khác biệt khá cơ bản yêu cầu xử lý rõ ràng, không phải là cách tiếp cận "regexp phù hợp với tất cả".
Michael Borgwardt

2
@Max: "Vấn đề chính ở đây là các con số có thể được tìm nạp từ các nguồn khác nhau, mỗi nguồn có định dạng riêng." Điều này phản đối , thay vì phản đối, việc bạn làm sạch đầu vào trước khi chuyển giao cho mã mà bạn không kiểm soát.
TJ Crowder

3

Tôi cần một giải pháp để chuyển đổi một Chuỗi thành một BigDecimal mà không cần biết ngôn ngữ và không phụ thuộc vào ngôn ngữ. Tôi không thể tìm thấy bất kỳ giải pháp tiêu chuẩn nào cho vấn đề này vì vậy tôi đã viết phương pháp trợ giúp của riêng mình. Có thể nó cũng giúp ích cho bất kỳ ai khác:

Cập nhật: Cảnh báo! Phương thức trợ giúp này chỉ hoạt động với các số thập phân, vì vậy các số luôn có dấu thập phân! Nếu không, phương thức trợ giúp có thể cung cấp kết quả sai cho các số từ 1000 đến 999999 (cộng / trừ). Cảm ơn bezmax vì đầu vào tuyệt vời của anh ấy!

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

Tất nhiên tôi đã thử nghiệm phương pháp:

@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
Xin lỗi, nhưng tôi không thấy làm thế nào điều này có thể hữu ích vì các trường hợp cạnh. Ví dụ, làm thế nào để bạn biết những gì 7,333đại diện? Nó là 7333 hay 7 và 1/3 (7.333)? Ở các ngôn ngữ khác nhau, số đó sẽ được phân tích cú pháp khác nhau. Đây là bằng chứng của khái niệm: ideone.com/Ue9rT8
bezmax

Đầu vào tốt, chưa từng gặp vấn đề này trong thực tế. Tôi sẽ cập nhật bài viết của tôi, cảm ơn bạn rất nhiều! Và tôi sẽ cải thiện việc thử nghiệm cho những đặc biệt của Ngôn ngữ này chỉ để biết nơi nào sẽ nhận được những rắc rối.
user3227576

1
Tôi đã kiểm tra giải pháp cho các Miền khác nhau và các số khác nhau ... và đối với chúng tôi tất cả đều hoạt động, bởi vì chúng tôi luôn có các số có dấu thập phân (chúng tôi đang đọc các giá trị tiền từ tệp văn bản). Tôi đã thêm một cảnh báo vào câu trả lời.
user3227576

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

sẽ xóa tất cả các ký tự ngoại trừ các chữ số và dấu chấm khỏi chuỗi của bạn.

Để làm cho nó nhận biết ngôn ngữ, bạn có thể muốn sử dụng getDecimalSeparator()từ java.text.DecimalFormatSymbols. Tôi không biết Java, nhưng nó có thể trông như thế này:

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

Điều này sẽ cho kết quả bất ngờ cho những con số không phải người Mỹ - hãy xem các bình luận cho câu trả lời của Justin.
Péter Török

2

Chủ đề cũ nhưng có lẽ dễ nhất là sử dụng dấu phẩy Apache NumberUtils có phương thức createBigDecimal (Giá trị chuỗi) ....

Tôi đoán (hy vọng) nó có tính đến ngôn ngữ địa phương hoặc nếu không nó sẽ là vô ích.


createBigDecimal chỉ là trình bao bọc cho BigDecimal (chuỗi) mới, như đã nêu trong Mã nguồn NumberUtils coi không có ngôn ngữ địa phương AFAIK
Mar Bar

1

Hãy thử cái này nó làm việc cho tôi

BigDecimal bd ;
String value = "2000.00";

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

2
Cách này không hỗ trợ chỉ định dấu phân cách tùy chỉnh cho các nhóm dấu thập phân và thouthands.
bezmax

Có, nhưng điều này là để nhận được chuỗi giá trị BigDecimal từ đối tượng như HashMap và lưu trữ vào giá trị BigDecimal để gọi dịch vụ khác
Rajkumar Teckraft

1
Có, nhưng đó không phải là câu hỏi.
bezmax

Hoạt động tốt cho tôi
Sathesh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.