안전한 문자열을 BigDecimal로 변환


120

문자열에서 일부 BigDecimal 값을 읽으려고합니다. 이 문자열이 "1,000,000,000.999999999999999"라고 가정하고이 문자열에서 BigDecimal을 가져 오려고합니다. 그것을하는 방법은 무엇입니까?

우선, 문자열 대체 (쉼표 대체 등)를 사용하는 솔루션이 마음에 들지 않습니다. 저를 위해 그 일을하기 위해서는 깔끔한 포맷터가 있어야한다고 생각합니다.

DecimalFormatter 클래스를 찾았지만 두 배로 작동하므로 엄청난 양의 정밀도가 손실됩니다.

그래서 어떻게 할 수 있습니까?


사용자 지정 형식이 주어 졌기 때문에 형식을 BigDecimal 호환 형식으로 변환하는 것은 고통 스럽습니다.
bezmax

2
"어떤 사용자 지정 형식이 주어지면 고통 스럽습니다 ..."잘 모르겠습니다. 문제 영역을 분리합니다. 먼저 문자열에서 사람이 읽을 수있는 내용을 정리 한 다음 결과를 정확하고 효율적으로 변환하는 방법을 아는 무언가에게 전달합니다 BigDecimal.
TJ Crowder

답변:


90

setParseBigDecimalDecimalFormat에서 확인하십시오 . 이 setter를 사용하면 parseBigDecimal이 반환됩니다.


1
BigDecimal 클래스의 생성자는 사용자 정의 형식을 지원하지 않습니다. 질문에서 내가 준 예제는 사용자 정의 형식 (쉼표)을 사용합니다. 생성자를 사용하여 BigDecimal로 구문 분석 할 수 없습니다.
bezmax

24
답변 을 완전히 변경하려면 답변에 언급하는 것이 좋습니다. 그렇지 않으면 사람들이 왜 당신의 원래 대답이 의미가 없는지 지적했을 때 매우 이상하게 보입니다. :-)
TJ Crowder

1
네, 그 방법을 알아 차리지 못했습니다. 감사합니다. 그게 최선의 방법 인 것 같습니다. 지금 테스트하고 제대로 작동하면 대답을 수락하십시오.
bezmax

15
예를 들어 줄 수 있습니까?
J Woodchuck 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


@ 스티브 맥그로가 .... 아니, 난 그냥 그것을 테스트 .... 내 값은1000000000.999999999999999
Buhake Sindi에

10
독일어 로케일 시도하고 1.000.000.000,999999999999
TofuBeer

@ # TofuBeer .... 예 : 1,000,000,000.999999999999999. 로케일을해야한다면 모든 점을 공백으로 바꿔야합니다 ....
Buhake Sindi

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
여기서 주된 문제는 각각 고유 한 형식을 가진 서로 다른 소스에서 숫자를 가져올 수 있다는 것입니다. 따라서이 상황에서이를 수행하는 가장 좋은 방법은 각 소스에 대해 "숫자 형식"을 정의하는 것입니다. 하나의 소스는 "123 456 789"형식을, 다른 소스는 "123.456.789"형식을 가질 수 있으므로 간단한 대체로는 그렇게 할 수 없습니다.
bezmax

나는 당신이 "한 줄 방법"이라는 용어를 조용히 재정의하고있는 것을 봅니다 ... ;-)
Péter Török

@Steve : "regexp가 모두에 적용되는"접근 방식이 아니라 명시 적 처리를 요구하는 매우 근본적인 차이점입니다.
Michael Borgwardt

2
@Max : "여기서 주된 문제는 숫자가 각각 고유 한 형식을 가진 다른 소스에서 가져올 수 있다는 것입니다." 어떤 주장 에 대한 당신이 제어 할 수없는 코드에 떨어져 나눠 전에 입력 청소, 오히려에 대한보다.
TJ Crowder

3

로케일을 모르고 로케일 독립적이지 않고 String을 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)입니까? 다른 로케일에서는 해당 번호가 다르게 구문 분석됩니다. 개념 증명 : ideone.com/Ue9rT8
bezmax

좋은 입력, 실제로는이 문제가 없었습니다. 나는 내 게시물을 업데이트 할 것입니다, 대단히 감사합니다! 그리고 어디에서 문제가 발생하는지 알기 위해 이러한 로케일 스페셜에 대한 테스트를 개선 할 것입니다.
user3227576

1
나는 다른 로케일과 다른 숫자에 대한 솔루션을 확인했습니다. 대답에 경고를 추가했습니다.
user3227576

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

문자열에서 숫자와 점을 제외한 모든 문자를 제거합니다.

로케일을 인식하려면 getDecimalSeparator()from 을 사용할 수 있습니다 java.text.DecimalFormatSymbols. Java를 모르지만 다음과 같이 보일 수 있습니다.

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

미국 이외의 숫자에 대해 예기치 않은 결과를 제공합니다. Justin의 답변에 대한 의견을 참조하십시오.
Péter Török

2

오래된 주제이지만 아마도 가장 쉬운 방법은 createBigDecimal (문자열 값) 메소드가있는 Apache commons NumberUtils를 사용하는 것입니다 ....

나는 (희망) 로케일을 고려하지 않으면 오히려 쓸모가 없을 것입니다.


createBigDecimal은 로케일 AFAIK를 고려하지 않는 NumberUtils 소스 코드에 명시된대로 new BigDecimal (string)에 대한 래퍼 일뿐입니다
Mar Bar

1

나를 위해 일하는 이것을 시도하십시오

BigDecimal bd ;
String value = "2000.00";

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

2
이 방법은 소수점 및 thouthands 그룹에 대한 사용자 지정 구분 기호 지정을 지원하지 않습니다.
bezmax

네,하지만이의 HashMap 같은 개체에서 문자열의 BigDecimal 값을 받고 다른 서비스 호출하기 BigDecimal의 값으로 저장하는 것입니다
라지 쿠마르 Teckraft을

1
예,하지만 그것은 질문이 아닙니다.
bezmax 2015 년

뿐만 아니라 나를 위해 작동
Sathesh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.