Safe String เพื่อการแปลง BigDecimal


120

ฉันกำลังพยายามอ่านค่า BigDecimal จากสตริง สมมติว่าฉันมี String นี้: "1,000,000,000.999999999999999" และฉันต้องการเอา BigDecimal ออกมา วิธีทำคืออะไร?

ก่อนอื่นฉันไม่ชอบวิธีแก้ปัญหาโดยใช้การแทนที่สตริง (การแทนที่ลูกน้ำ ฯลฯ ) ฉันคิดว่าควรมีการจัดรูปแบบที่เรียบร้อยเพื่อทำงานนั้นให้ฉัน

ฉันพบคลาส DecimalFormatter แล้วอย่างไรก็ตามเมื่อทำงานผ่านสองเท่า - ความแม่นยำจำนวนมากจะหายไป

ฉันจะทำมันได้อย่างไร?


เนื่องจากรูปแบบที่กำหนดเองบางรูปแบบจึงเป็นการยากที่จะแปลงรูปแบบของคุณเป็นรูปแบบที่เข้ากันได้กับ BigDecimal
bezmax

2
"เนื่องจากรูปแบบที่กำหนดเองบางอย่างมันเป็นความเจ็บปวด ... "ฉันไม่รู้ว่ามันเป็นการแยกโดเมนปัญหา ก่อนอื่นให้คุณทำความสะอาดสิ่งที่มนุษย์อ่านได้จากสตริงจากนั้นส่งต่อสิ่งที่รู้วิธีเปลี่ยนผลลัพธ์ให้เป็นไฟล์BigDecimal.
TJ Crowder

คำตอบ:


90

ตรวจสอบsetParseBigDecimalใน DecimalFormat ด้วยตัวตั้งค่านี้parseจะส่งคืน BigDecimal ให้คุณ


1
ตัวสร้างในคลาส BigDecimal ไม่รองรับรูปแบบที่กำหนดเอง ตัวอย่างที่ฉันให้ในคำถามใช้รูปแบบที่กำหนดเอง (ลูกน้ำ) คุณไม่สามารถแยกวิเคราะห์เป็น BigDecimal โดยใช้ตัวสร้าง
bezmax

24
หากคุณกำลังจะสมบูรณ์เปลี่ยนคำตอบของคุณผมขอแนะนำการกล่าวขวัญในคำตอบ อย่างอื่นมันดูแปลกมากเมื่อมีคนชี้ให้เห็นว่าทำไมคำตอบเดิมของคุณไม่สมเหตุสมผล :-)
TJ Crowder

1
ใช่ไม่ได้สังเกตวิธีนั้น ขอบคุณดูเหมือนว่าจะเป็นวิธีที่ดีที่สุด จะทดสอบทันทีและถ้ามันทำงานได้อย่างถูกต้อง - ยอมรับคำตอบ
bezmax

15
คุณช่วยยกตัวอย่างได้ไหม
J Woodchuck

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
ลองใช้ภาษา GERMAN และ 1.000.000.000,999999999999
TofuBeer

@ # TofuBeer .... ตัวอย่างคือ 1,000,000,000.999999999999999 ถ้าฉันจะทำโลแคลของคุณฉันจะต้องแทนที่จุดทั้งหมดเป็นช่องว่าง ....
Buhake Sindi

14
ดังนั้นจึงไม่ปลอดภัย คุณไม่ทราบภาษาทั้งหมด
ymajoros

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(".", "");
}

เห็นได้ชัดว่าหากสิ่งนี้เกิดขึ้นในรหัสการผลิตมันคงไม่ง่ายอย่างนั้น

ฉันไม่เห็นปัญหาในการลบเครื่องหมายจุลภาคออกจาก String


และถ้าสตริงไม่อยู่ในรูปแบบอเมริกัน? ในเยอรมนีจะเป็น 1.000.000.000,999999999999999
Steve McLeod

1
ปัญหาหลักที่นี่คือตัวเลขอาจถูกดึงมาจากแหล่งที่มาที่แตกต่างกันซึ่งแต่ละแห่งมีรูปแบบของตัวเอง ดังนั้นวิธีที่ดีที่สุดในสถานการณ์นี้คือการกำหนด "รูปแบบตัวเลข" สำหรับแต่ละแหล่งที่มา ฉันไม่สามารถทำได้ด้วยการแทนที่อย่างง่ายเนื่องจากแหล่งที่มาหนึ่งสามารถมีรูปแบบ "123 456 789" และอีกรูปแบบ "123.456.789"
bezmax

ฉันเห็นว่าคุณกำลังนิยามคำว่า "one line method" ใหม่อย่างเงียบ ๆ ... ;-)
PéterTörök

@Steve: นั่นคือความแตกต่างพื้นฐานที่เรียกร้องให้มีการจัดการอย่างชัดเจนไม่ใช่แนวทาง "regexp ที่เหมาะกับทุกคน"
Michael Borgwardt

2
@Max: "ปัญหาหลักที่นี่คือตัวเลขอาจถูกดึงมาจากแหล่งที่มาที่แตกต่างกันซึ่งแต่ละแหล่งมีรูปแบบของตัวเอง" ซึ่งระบุสำหรับมากกว่ากับการทำความสะอาดการป้อนข้อมูลก่อนที่จะส่งของคุณออกไปรหัสคุณทำไม่ได้ควบคุม
TJ Crowder

3

ฉันต้องการโซลูชันในการแปลง String เป็น BigDecimal โดยไม่ทราบตำแหน่งที่ตั้งและไม่ขึ้นกับโลแคล ฉันไม่พบวิธีแก้ปัญหามาตรฐานสำหรับปัญหานี้ดังนั้นฉันจึงเขียนวิธีการช่วยเหลือของฉันเอง อาจช่วยคนอื่นได้ด้วย:

Update: คำเตือน! วิธีการช่วยเหลือนี้ใช้ได้กับเลขฐานสิบเท่านั้นดังนั้นตัวเลขที่มีจุดทศนิยมเสมอ! มิฉะนั้นวิธีการช่วยเหลืออาจให้ผลลัพธ์ที่ไม่ถูกต้องสำหรับตัวเลขระหว่าง 1,000 ถึง 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
ขออภัยฉันไม่เห็นว่าสิ่งนี้จะมีประโยชน์อย่างไรเนื่องจากกรณี edge ตัวอย่างเช่นคุณรู้ได้อย่างไรว่าอะไร7,333เป็นตัวแทน? มันคือ 7333 หรือ 7 และ 1/3 (7.333)? ในภาษาที่ต่างกันจำนวนนั้นจะถูกแยกวิเคราะห์ต่างกัน นี่คือข้อพิสูจน์แนวคิด: ideone.com/Ue9rT8
bezmax

การป้อนข้อมูลที่ดีไม่เคยมีปัญหานี้ในทางปฏิบัติ ฉันจะอัปเดตโพสต์ของฉันขอบคุณมาก! และฉันจะปรับปรุงการทดสอบสำหรับสถานที่พิเศษเหล่านี้เพื่อให้ทราบว่าจะได้รับปัญหาที่ไหน
user3227576

1
ฉันได้ตรวจสอบวิธีการแก้ปัญหาสำหรับสถานที่ต่างๆและตัวเลขที่แตกต่างกัน ... และสำหรับเราทุกคนก็ใช้ได้เพราะเรามีตัวเลขที่มีจุดทศนิยมเสมอ (เรากำลังอ่าน Money-values ​​จาก textfile) ฉันได้เพิ่มคำเตือนในคำตอบแล้ว
user3227576

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

จะลบอักขระทั้งหมดยกเว้นตัวเลขและจุดออกจากสตริงของคุณ

เพื่อให้ทราบตำแหน่งที่ตั้งคุณอาจต้องการใช้getDecimalSeparator()จากjava.text.DecimalFormatSymbols. ฉันไม่รู้จัก Java แต่อาจมีลักษณะดังนี้:

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

ซึ่งจะให้ผลลัพธ์ที่ไม่คาดคิดสำหรับตัวเลขที่ไม่ใช่ชาวอเมริกัน - ดูความคิดเห็นต่อคำตอบของจัสติน
PéterTörök

2

หัวข้อเก่า แต่อาจจะง่ายที่สุดคือใช้ Apache commons NumberUtils ซึ่งมีเมธอด createBigDecimal (ค่า String) ....

ฉันเดาว่า (หวังว่า) จะต้องคำนึงถึงสถานที่ไม่เช่นนั้นก็จะค่อนข้างไร้ประโยชน์


createBigDecimal เป็นเพียงกระดาษห่อหุ้มสำหรับ BigDecimal (สตริง) ใหม่ตามที่ระบุไว้ในSource Code NumberUtils ซึ่งถือว่าไม่มี Locale AFAIK
Mar Bar

1

โปรดลองสิ่งนี้ได้ผลสำหรับฉัน

BigDecimal bd ;
String value = "2000.00";

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

2
วิธีนี้ไม่สนับสนุนการระบุตัวคั่นแบบกำหนดเองสำหรับจุดทศนิยมและกลุ่ม thouthands
bezmax

ใช่ แต่นี่คือการรับค่าสตริง BigDecimal จากวัตถุเช่น HashMap และจัดเก็บเป็นค่า Bigdecimal เพื่อเรียกใช้บริการอื่น ๆ
Rajkumar Teckraft

1
ใช่ แต่นั่นไม่ใช่คำถาม
bezmax

ใช้ได้ผลสำหรับฉันเช่นกัน
Sathesh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.