Cách làm tròn số đến n vị trí thập phân trong Java


1259

Những gì tôi muốn là một phương pháp để chuyển đổi một chuỗi thành một chuỗi làm tròn bằng phương pháp nửa lên - tức là nếu số thập phân được làm tròn là 5, thì nó luôn làm tròn đến số tiếp theo. Đây là phương pháp làm tròn tiêu chuẩn mà hầu hết mọi người mong đợi trong hầu hết các tình huống.

Tôi cũng chỉ muốn hiển thị các chữ số có nghĩa - tức là không nên có bất kỳ số 0 nào.

Tôi biết một phương pháp để làm điều này là sử dụng String.formatphương pháp:

String.format("%.5g%n", 0.912385);

trả về:

0.91239

thật tuyệt vời, tuy nhiên nó luôn hiển thị các số có 5 chữ số thập phân ngay cả khi chúng không đáng kể:

String.format("%.5g%n", 0.912300);

trả về:

0.91230

Một phương pháp khác là sử dụng DecimalFormatter:

DecimalFormat df = new DecimalFormat("#.#####");
df.format(0.912385);

trả về:

0.91238

Tuy nhiên như bạn có thể thấy điều này sử dụng làm tròn nửa chẵn. Đó là nó sẽ làm tròn xuống nếu chữ số trước là chẵn. Điều tôi muốn là đây:

0.912385 -> 0.91239
0.912300 -> 0.9123

Cách tốt nhất để đạt được điều này trong Java là gì?

Câu trả lời:


751

Sử dụng setRoundingMode, đặt RoundingModerõ ràng để xử lý vấn đề của bạn với vòng nửa chẵn, sau đó sử dụng mẫu định dạng cho đầu ra yêu cầu của bạn.

Thí dụ:

DecimalFormat df = new DecimalFormat("#.####");
df.setRoundingMode(RoundingMode.CEILING);
for (Number n : Arrays.asList(12, 123.12345, 0.23, 0.1, 2341234.212431324)) {
    Double d = n.doubleValue();
    System.out.println(df.format(d));
}

đưa ra đầu ra:

12
123.1235
0.23
0.1
2341234.2125

EDIT : Câu trả lời ban đầu không đề cập đến độ chính xác của các giá trị kép. Điều đó cũng tốt nếu bạn không quan tâm nhiều đến việc nó làm tròn lên hay xuống. Nhưng nếu bạn muốn làm tròn chính xác, thì bạn cần tính đến độ chính xác dự kiến ​​của các giá trị. Các giá trị dấu phẩy động có biểu diễn nhị phân trong nội bộ. Điều đó có nghĩa là một giá trị như 2.7735 không thực sự có giá trị chính xác đó trong nội bộ. Nó có thể lớn hơn một chút hoặc nhỏ hơn một chút. Nếu giá trị bên trong nhỏ hơn một chút, thì nó sẽ không làm tròn đến 2,7740. Để khắc phục tình trạng đó, bạn cần lưu ý về tính chính xác của các giá trị mà bạn đang làm việc và thêm hoặc bớt giá trị đó trước khi làm tròn. Ví dụ: khi bạn biết rằng các giá trị của bạn chính xác tới 6 chữ số, sau đó làm tròn các giá trị nửa đường lên, hãy thêm độ chính xác đó vào giá trị:

Double d = n.doubleValue() + 1e-6;

Để làm tròn xuống, trừ đi độ chính xác.


9
Đây có lẽ là giải pháp tốt nhất được trình bày cho đến nay. Lý do tôi không phát hiện ra cơ sở này khi lần đầu tiên tôi nhìn vào lớp DecimalFormat là vì nó chỉ được giới thiệu trong Java 1.6. Thật không may, tôi bị hạn chế sử dụng 1.5 nhưng sẽ hữu ích khi biết trong tương lai.
Alex Spurling

1
Tôi đã thử điều này với : "#.##", làm tròn HALF_UP. 256.335f-> "256.33"... (ví dụ xuất phát từ nhận xét cho câu trả lời của @ asterite).
bigstones

6
Xin hãy cẩn thận vì DecimalFormat phụ thuộc vào cấu hình Cục bộ hiện tại của bạn, bạn có thể không nhận được dấu chấm làm dấu phân cách. Cá nhân tôi thích câu trả lời của Asterite bên dưới
Gomino

1
Ngoài ra, hãy lưu ý rằng bạn không nên mong đợi DecimalFormat an toàn cho chuỗi. Theo tài liệu Java : Các định dạng thập phân thường không được đồng bộ hóa. Nên tạo các thể hiện định dạng riêng cho từng luồng. Nếu nhiều luồng truy cập một định dạng đồng thời, nó phải được đồng bộ hóa bên ngoài.
CGK

1
Làm thế nào để tôi thực hiện nó để làm tròn đúng cách để nó không làm tròn 0,0004 đến 0,001

471

Giả sử valuelà một double, bạn có thể làm:

(double)Math.round(value * 100000d) / 100000d

Đó là độ chính xác 5 chữ số. Số lượng không cho biết số thập phân.


71
CẬP NHẬT: Tôi vừa xác nhận rằng làm điều này là CÁCH nhanh hơn so với sử dụng DecimalFormat. Tôi đã lặp lại bằng cách sử dụng DecimalFormat 200 lần và phương pháp này. DecimalFormat mất 14ms để hoàn thành 200 vòng, phương pháp này mất chưa đến 1ms. Như tôi nghi ngờ, điều này nhanh hơn. Nếu bạn được trả tiền theo chu kỳ đồng hồ, đây là những gì bạn nên làm. Tôi ngạc nhiên Chris Cudmore thậm chí sẽ nói những gì anh ấy nói là trung thực. phân bổ các đối tượng luôn đắt hơn so với việc tạo các nguyên hàm và sử dụng các phương thức tĩnh (Math.round () trái ngược với binaryFormat.format ()).
Andi Jay

99
Kỹ thuật này thất bại trong hơn 90% trường hợp. -1.
Hầu tước Lorne

25
Thật vậy, điều này thất bại : Math.round(0.1 * Math.pow(10,20))/Math.pow(10,20) == 0.09223372036854775.
Robert Tupelo-Schneck

54
Hãy thật cẩn thận khi sử dụng phương pháp này (hoặc bất kỳ làm tròn các điểm nổi). Nó thất bại cho một cái gì đó đơn giản như 265.335. Kết quả trung gian của 265.335 * 100 (độ chính xác của 2 chữ số) là 26533.499999999996. Điều này có nghĩa là nó được làm tròn xuống 265,33. Đơn giản là có những vấn đề cố hữu khi chuyển đổi từ số dấu phẩy động sang số thập phân thực. Xem câu trả lời của EJP tại đây tại stackoverflow.com/a/12684082/144578
Sebastiaan van den Broek

6
@SebastiaanvandenBroek: Wow Tôi không bao giờ biết rằng thật dễ dàng để có được một câu trả lời sai. Tuy nhiên, nếu một người đang làm việc với các số không chính xác, người ta phải nhận ra rằng bất kỳ giá trị nào là không chính xác . 265.335thực sự có nghĩa là 265.335 += tolerance, nơi dung sai phụ thuộc vào các hoạt động trước đó và phạm vi của các giá trị đầu vào. Chúng tôi không biết giá trị chính xác, chính xác. Ở các giá trị cạnh, một trong hai câu trả lời là chính xác. Nếu chúng ta cần chính xác, chúng ta không nên làm việc gấp đôi. Ở failđây không phải là chuyển đổi trở lại gấp đôi. Theo suy nghĩ của OP, anh ta có thể dựa vào sự xuất 265.335hiện chính xác như vậy.
ToolmakerSteve

191
new BigDecimal(String.valueOf(double)).setScale(yourScale, BigDecimal.ROUND_HALF_UP);

sẽ giúp bạn có một BigDecimal. Để có được chuỗi ra khỏi nó, chỉ cần gọi rằng BigDecimal's toStringphương pháp, hoặc các toPlainStringphương pháp cho Java 5 + cho một chuỗi định dạng đơn giản.

Chương trình mẫu:

package trials;
import java.math.BigDecimal;

public class Trials {

    public static void main(String[] args) {
        int yourScale = 10;
        System.out.println(BigDecimal.valueOf(0.42344534534553453453-0.42324534524553453453).setScale(yourScale, BigDecimal.ROUND_HALF_UP));
    }

38
Đó là giải pháp ưa thích của tôi. Thậm chí ngắn hơn: BigDecimal.valueOf (doubleVar) .setScale (yourScaleHere, BigDecimal.ROUND_HALF_UP); BigDecimal.valueOf (double val) thực sự gọi Double.toString () dưới mui xe;)
Etienne Neveu

4
Đẹp. Đừng cắt góc và sử dụng new BigDecimal(doubleVar)khi bạn có thể gặp vấn đề với làm tròn các điểm nổi
Edd

8
@Edd, thật thú vị, vấn đề làm tròn xảy ra trong trường hợp SebastiaanvandenBroek đề cập đến bình luận cho câu trả lời của asterite. double val = 265.335;, BigDecimal.valueOf(val).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.34, nhưng (new BigDecimal(val)).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.33.
ToolmakerSteve

7
@ToolmakerSteve Đó là vì sử dụng BigDecimal mới với double sẽ lấy giá trị gấp đôi trực tiếp và cố gắng sử dụng giá trị đó để tạo BigDecimal, trong khi đó, khi sử dụng BigDecimal.valueOf hoặc dạng chuỗi sẽ phân tích nó thành chuỗi trước (đại diện chính xác hơn) trước khi chuyển đổi .
MetroidFan2002

116

Bạn cũng có thể sử dụng

DecimalFormat df = new DecimalFormat("#.00000");
df.format(0.912385);

để chắc chắn rằng bạn có dấu 0.


25
Tôi tin rằng một trong những mục tiêu của câu hỏi là "có nên không có bất kỳ zero trailing".
Hộp cơm trưa

7
Đối với câu hỏi này, op không muốn số không, nhưng đây chính xác là những gì tôi muốn. Nếu bạn có một danh sách các số có 3 chữ số thập phân, bạn muốn tất cả chúng có cùng một chữ số ngay cả khi đó là 0.
Tom Kincaid

Bạn đã quên chỉ địnhRoundingMode.
IgorGanapolsky

1
@IgorGanapolsky theo mặc định Decimal modesử dụngRoundingMode.HALF_EVEN.
EndermanAPM

87

Như một số người khác đã lưu ý, câu trả lời đúng là sử dụng DecimalFormathoặc BigDecimal. Điểm nổi không vị trí thập phân, do đó bạn không thể làm tròn / cắt ngắn đến một số lượng cụ thể của chúng ở vị trí đầu tiên. Bạn phải làm việc trong một cơ số thập phân, và đó là những gì hai lớp đó làm.

Tôi đang đăng đoạn mã sau đây làm ví dụ ngược lại cho tất cả các câu trả lời trong chuỗi này và thực sự trên StackOverflow (và các nơi khác) đề xuất phép nhân theo sau là cắt ngắn theo sau là chia. Những người ủng hộ kỹ thuật này không thể giải thích được tại sao đoạn mã sau tạo ra đầu ra sai trong hơn 92% trường hợp.

public class RoundingCounterExample
{

    static float roundOff(float x, int position)
    {
        float a = x;
        double temp = Math.pow(10.0, position);
        a *= temp;
        a = Math.round(a);
        return (a / (float)temp);
    }

    public static void main(String[] args)
    {
        float a = roundOff(0.0009434f,3);
        System.out.println("a="+a+" (a % .001)="+(a % 0.001));
        int count = 0, errors = 0;
        for (double x = 0.0; x < 1; x += 0.0001)
        {
            count++;
            double d = x;
            int scale = 2;
            double factor = Math.pow(10, scale);
            d = Math.round(d * factor) / factor;
            if ((d % 0.01) != 0.0)
            {
                System.out.println(d + " " + (d % 0.01));
                errors++;
            }
        }
        System.out.println(count + " trials " + errors + " errors");
    }
}

Đầu ra của chương trình này:

10001 trials 9251 errors

EDIT: Để giải quyết một số ý kiến ​​dưới đây, tôi đã làm lại phần mô đun của vòng kiểm tra bằng cách sử dụng BigDecimalnew MathContext(16)cho hoạt động của mô đun như sau:

public static void main(String[] args)
{
    int count = 0, errors = 0;
    int scale = 2;
    double factor = Math.pow(10, scale);
    MathContext mc = new MathContext(16, RoundingMode.DOWN);
    for (double x = 0.0; x < 1; x += 0.0001)
    {
        count++;
        double d = x;
        d = Math.round(d * factor) / factor;
        BigDecimal bd = new BigDecimal(d, mc);
        bd = bd.remainder(new BigDecimal("0.01"), mc);
        if (bd.multiply(BigDecimal.valueOf(100)).remainder(BigDecimal.ONE, mc).compareTo(BigDecimal.ZERO) != 0)
        {
            System.out.println(d + " " + bd);
            errors++;
        }
    }
    System.out.println(count + " trials " + errors + " errors");
}

Kết quả:

10001 trials 4401 errors

8
Mẹo nhỏ là trong tất cả các lỗi 9251 của bạn, kết quả được in vẫn đúng.
Didier L

7
@DidierL Nó không làm tôi ngạc nhiên. Tôi đã có may mắn được làm 'Phương pháp số' là khóa học điện toán đầu tiên của mình và được giới thiệu ngay khi bắt đầu những gì dấu phẩy động có thể và không thể làm. Hầu hết các lập trình viên đều khá mơ hồ về nó.
Hầu tước Lorne

15
Tất cả những gì bạn đang làm là bác bỏ rằng nổi không thể hiện chính xác nhiều giá trị thập phân, điều mà tôi hy vọng tất cả chúng ta đều hiểu. Không làm tròn số đó gây ra một vấn đề. Khi bạn thừa nhận, những con số vẫn được in như mong đợi.
Peter Lawrey

8
Bài kiểm tra của bạn bị hỏng, lấy vòng () ra và bài kiểm tra thất bại 94% thời gian. ideone.com/1y62CY in 100 trials 94 errorsBạn nên bắt đầu với một bài kiểm tra vượt qua và cho thấy việc giới thiệu làm tròn phá vỡ bài kiểm tra.
Peter Lawrey

6
Từ chối, bác bỏ ở đây. Sử dụng Math.round cho phạm vi này doublelà không có lỗi ideone.com/BVCHh3
Peter Lawrey

83

Giả sử bạn có

double d = 9232.129394d;

bạn có thể dùng BigDecimal

BigDecimal bd = new BigDecimal(d).setScale(2, RoundingMode.HALF_EVEN);
d = bd.doubleValue();

hoặc không có BigDecimal

d = Math.round(d*100)/100.0d;

với cả hai giải pháp d == 9232.13


2
Tôi nghĩ rằng đây là giải pháp tốt nhất cho người dùng Java 1.5 (và bên dưới). Một nhận xét tho, không sử dụng chế độ làm tròn HALF_EVEN vì nó có hành vi khác nhau cho các số lẻ và chẵn (ví dụ 2,5 vòng đến 2 trong khi 5,5 vòng đến 6 chẳng hạn), trừ khi đây là điều bạn muốn.
IcedDante

4
Giải pháp đầu tiên là chính xác: giải pháp thứ hai không hoạt động. Xem ở đây để chứng minh.
Hầu tước Lorne

1
@EJP: Ngay cả giải pháp đầu tiên RoundingMode.HALF_UPcũng sai. Hãy thử nó với 1.505. Cách đúng là sử dụng BigDecimal.valueOf(d).
Matthias Braun

Matthias Braun, giải pháp rất ổn, do đó 31 lần .. 1.505 thập phân được lưu trữ trong dấu phẩy động gấp đôi là 1.50499998 nếu bạn muốn lấy 1,505 và chuyển đổi từ gấp đôi thành thập phân, trước tiên bạn phải chuyển đổi nó thành Double.toString (x) sau đó đặt nó vào BigDecimal (), nhưng điều đó cực kỳ chậm và đánh bại mục đích sử dụng gấp đôi tốc độ ở vị trí đầu tiên.
ham

1
Chạy một vòng 100k với cách BigDecimal (mất 225 ms) và Math.round (2 ms) và đây là thời gian ... Thời gian thực hiện: 225 mili giây để chuyển đổi bằng cách sử dụng: 9232.13 Thời gian thực hiện: 2 mili giây để chuyển đổi thành : 9232.13 techiesinfo.com
user1114134

59

Bạn có thể sử dụng lớp DecimalFormat.

double d = 3.76628729;

DecimalFormat newFormat = new DecimalFormat("#.##");
double twoDecimal =  Double.valueOf(newFormat.format(d));

Bất kỳ lý do tại sao Double.valueOf()được chọn hơn Double.parseDouble()? Các valueOf()phương thức trả về một Doubleđối tượng, trong khi parseDouble()sẽ trả về một doublenguyên thủy. Với cách viết mã hiện tại, bạn cũng áp dụng tự động hủy hộp cho quay trở lại để chuyển nó sang nguyên hàm mà twoDoublebiến của bạn mong đợi, một hoạt động thêm mã byte. Tôi sẽ thay đổi câu trả lời để sử dụng parseDouble()thay thế.
ecbrodie

Double.parseDouble()cần Stringđầu vào.
Bến cảng

38

Cách thực hiện Java của Real gửi giải pháp này, cũng tương thích với các phiên bản trước Java 1.6.

BigDecimal bd = new BigDecimal(Double.toString(d));
bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
return bd.doubleValue();

33
double myNum = .912385;
int precision = 10000; //keep 4 digits
myNum= Math.floor(myNum * precision +.5)/precision;

2
vâng, đây chính xác là những gì math.round làm cho các số dương, nhưng bạn đã thử điều này với các số âm chưa? mọi người đang sử dụng math.round trong các giải pháp khác để bao gồm cả trường hợp số âm.
ham

Lưu ý: Math.floor(x + 0.5)Math.round(x)
Peter Lawrey

30

@Milhous: định dạng thập phân để làm tròn là tuyệt vời:

Bạn cũng có thể sử dụng

DecimalFormat df = new DecimalFormat("#.00000");
df.format(0.912385);

để chắc chắn rằng bạn có dấu 0.

Tôi sẽ nói thêm rằng phương pháp này rất tốt trong việc cung cấp một cơ chế làm tròn số, thực tế - không chỉ trực quan, mà cả khi xử lý.

Giả thuyết: bạn phải thực hiện cơ chế làm tròn thành chương trình GUI. Để thay đổi độ chính xác / độ chính xác của đầu ra kết quả, chỉ cần thay đổi định dạng dấu mũ (tức là trong dấu ngoặc). Vậy nên:

DecimalFormat df = new DecimalFormat("#0.######");
df.format(0.912385);

sẽ trở lại như đầu ra: 0.912385

DecimalFormat df = new DecimalFormat("#0.#####");
df.format(0.912385);

sẽ trở lại như đầu ra: 0.91239

DecimalFormat df = new DecimalFormat("#0.####");
df.format(0.912385);

sẽ trở lại như đầu ra: 0.9124

[EDIT: cũng vậy nếu định dạng dấu mũ giống như vậy ("# ​​0. ############") và bạn nhập số thập phân, ví dụ: 3.1415926, vì lý do của đối số, DecimalFormat không tạo ra bất kỳ rác nào ( ví dụ: các số 0 ở cuối) và sẽ trả về: 3.1415926.. nếu bạn đang nghiêng như vậy. Cứ cho là, nó hơi dài dòng vì thích một số nhà phát triển - nhưng này, nó có dung lượng bộ nhớ thấp trong quá trình xử lý và rất dễ thực hiện.]

Vì vậy, về cơ bản, vẻ đẹp của DecimalFormat là nó đồng thời xử lý sự xuất hiện của chuỗi - cũng như mức độ đặt chính xác làm tròn. Ergo: bạn nhận được hai lợi ích cho giá của việc thực thi một mã. ;)


2
Nếu bạn thực sự muốn số thập phân để tính toán (và không chỉ cho đầu ra), không sử dụng định dạng dấu phẩy động dựa trên nhị phân như thế nào double. Sử dụng BigDecimal hoặc bất kỳ định dạng dựa trên thập phân nào khác.
Paŭlo Ebermann

20

Dưới đây là tóm tắt về những gì bạn có thể sử dụng nếu bạn muốn kết quả dưới dạng Chuỗi:

  1. DecimalFormat # setRoundingMode () :

    DecimalFormat df = new DecimalFormat("#.#####");
    df.setRoundingMode(RoundingMode.HALF_UP);
    String str1 = df.format(0.912385)); // 0.91239
  2. BigDecimal # setScale ()

    String str2 = new BigDecimal(0.912385)
        .setScale(5, BigDecimal.ROUND_HALF_UP)
        .toString();

Dưới đây là một gợi ý về những thư viện bạn có thể sử dụng nếu bạn muốn double. Tuy nhiên, tôi không khuyên bạn nên chuyển đổi chuỗi vì đôi khi có thể không thể biểu thị chính xác những gì bạn muốn (xem ví dụ ở đây ):

  1. Chính xác từ Toán học Apache Commons

    double rounded = Precision.round(0.912385, 5, BigDecimal.ROUND_HALF_UP);
  2. Chức năng từ Colt

    double rounded = Functions.round(0.00001).apply(0.912385)
  3. Sử dụng từ Weka

    double rounded = Utils.roundDouble(0.912385, 5)

18

Bạn có thể sử dụng phương thức tiện ích sau-

public static double round(double valueToRound, int numberOfDecimalPlaces)
{
    double multipicationFactor = Math.pow(10, numberOfDecimalPlaces);
    double interestedInZeroDPs = valueToRound * multipicationFactor;
    return Math.round(interestedInZeroDPs) / multipicationFactor;
}

@mariolpantunes: Nó sẽ thất bại. Hãy thử điều này: round(1.005,2);hoặcround(0.50594724957626620092, 20);
Matthias Braun

Nó hoạt động. Nhưng nổi không chính xác và đôi là xấp xỉ. Hãy để chúng tôi xem xét ví dụ đầu tiên của bạn. Nếu bạn in đầu ra của InterestInZeroDP trước Math.round, nó sẽ in 100.49999999999999. Bạn đã mất độ chính xác như Math.round làm tròn số 100. Do tính chất hoặc số float và nhân đôi, có các trường hợp đường biên khi nó không hoạt động chính xác (thông tin thêm ở đây en.wikipedia.org/wiki/Floating_point#Accuracy_probols )
mariolpantunes

gấp đôi là nhanh! thập phân là chậm. máy tính không bận tâm xử lý suy nghĩ của họ trong ký hiệu thập phân. bạn phải từ bỏ một số độ chính xác thập phân để giữ cho dấu phẩy động nhanh gấp đôi.
hamish

@hamish Câu hỏi là về độ chính xác, không phải về tốc độ.
Hầu tước Lorne



7

Hãy thử điều này: org.apache.commons.math3.util.Precision.round (double x, int scale)

Xem: http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/util/Precision.html

Trang chủ Thư viện Toán học Apache Commons là: http://commons.apache.org/proper/commons-math/index.html

Ý nghĩa bên trong của phương pháp này là:

public static double round(double x, int scale) {
    return round(x, scale, BigDecimal.ROUND_HALF_UP);
}

public static double round(double x, int scale, int roundingMethod) {
    try {
        return (new BigDecimal
               (Double.toString(x))
               .setScale(scale, roundingMethod))
               .doubleValue();
    } catch (NumberFormatException ex) {
        if (Double.isInfinite(x)) {
            return x;
        } else {
            return Double.NaN;
        }
    }
}

7

Vì tôi không tìm thấy câu trả lời đầy đủ về chủ đề này, tôi đã kết hợp một lớp nên xử lý vấn đề này một cách hợp lý, với sự hỗ trợ cho:

  • Định dạng : Dễ dàng định dạng một chuỗi thành một chuỗi với một số vị trí thập phân nhất định
  • Phân tích cú pháp : Phân tích giá trị được định dạng trở lại gấp đôi
  • Bản địa : Định dạng và phân tích cú pháp bằng ngôn ngữ mặc định
  • Ký hiệu số mũ : Bắt đầu sử dụng ký hiệu số mũ sau một ngưỡng nhất định

Cách sử dụng khá đơn giản :

(Vì lợi ích của ví dụ này, tôi đang sử dụng ngôn ngữ tùy chỉnh)

public static final int DECIMAL_PLACES = 2;

NumberFormatter formatter = new NumberFormatter(DECIMAL_PLACES);

String value = formatter.format(9.319); // "9,32"
String value2 = formatter.format(0.0000005); // "5,00E-7"
String value3 = formatter.format(1324134123); // "1,32E9"

double parsedValue1 = formatter.parse("0,4E-2", 0); // 0.004
double parsedValue2 = formatter.parse("0,002", 0); // 0.002
double parsedValue3 = formatter.parse("3423,12345", 0); // 3423.12345

Đây là lớp học :

import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.Locale;

public class NumberFormatter {

    private static final String SYMBOL_INFINITE           = "\u221e";
    private static final char   SYMBOL_MINUS              = '-';
    private static final char   SYMBOL_ZERO               = '0';
    private static final int    DECIMAL_LEADING_GROUPS    = 10;
    private static final int    EXPONENTIAL_INT_THRESHOLD = 1000000000; // After this value switch to exponential notation
    private static final double EXPONENTIAL_DEC_THRESHOLD = 0.0001; // Below this value switch to exponential notation

    private DecimalFormat decimalFormat;
    private DecimalFormat decimalFormatLong;
    private DecimalFormat exponentialFormat;

    private char groupSeparator;

    public NumberFormatter(int decimalPlaces) {
        configureDecimalPlaces(decimalPlaces);
    }

    public void configureDecimalPlaces(int decimalPlaces) {
        if (decimalPlaces <= 0) {
            throw new IllegalArgumentException("Invalid decimal places");
        }

        DecimalFormatSymbols separators = new DecimalFormatSymbols(Locale.getDefault());
        separators.setMinusSign(SYMBOL_MINUS);
        separators.setZeroDigit(SYMBOL_ZERO);

        groupSeparator = separators.getGroupingSeparator();

        StringBuilder decimal = new StringBuilder();
        StringBuilder exponential = new StringBuilder("0.");

        for (int i = 0; i < DECIMAL_LEADING_GROUPS; i++) {
            decimal.append("###").append(i == DECIMAL_LEADING_GROUPS - 1 ? "." : ",");
        }

        for (int i = 0; i < decimalPlaces; i++) {
            decimal.append("#");
            exponential.append("0");
        }

        exponential.append("E0");

        decimalFormat = new DecimalFormat(decimal.toString(), separators);
        decimalFormatLong = new DecimalFormat(decimal.append("####").toString(), separators);
        exponentialFormat = new DecimalFormat(exponential.toString(), separators);

        decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
        decimalFormatLong.setRoundingMode(RoundingMode.HALF_UP);
        exponentialFormat.setRoundingMode(RoundingMode.HALF_UP);
    }

    public String format(double value) {
        String result;
        if (Double.isNaN(value)) {
            result = "";
        } else if (Double.isInfinite(value)) {
            result = String.valueOf(SYMBOL_INFINITE);
        } else {
            double absValue = Math.abs(value);
            if (absValue >= 1) {
                if (absValue >= EXPONENTIAL_INT_THRESHOLD) {
                    value = Math.floor(value);
                    result = exponentialFormat.format(value);
                } else {
                    result = decimalFormat.format(value);
                }
            } else if (absValue < 1 && absValue > 0) {
                if (absValue >= EXPONENTIAL_DEC_THRESHOLD) {
                    result = decimalFormat.format(value);
                    if (result.equalsIgnoreCase("0")) {
                        result = decimalFormatLong.format(value);
                    }
                } else {
                    result = exponentialFormat.format(value);
                }
            } else {
                result = "0";
            }
        }
        return result;
    }

    public String formatWithoutGroupSeparators(double value) {
        return removeGroupSeparators(format(value));
    }

    public double parse(String value, double defValue) {
        try {
            return decimalFormat.parse(value).doubleValue();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return defValue;
    }

    private String removeGroupSeparators(String number) {
        return number.replace(String.valueOf(groupSeparator), "");
    }

}

7

Nếu bạn thực sự muốn số thập phân để tính toán (và không chỉ cho đầu ra), không sử dụng định dạng dấu phẩy động dựa trên nhị phân như gấp đôi.

Use BigDecimal or any other decimal-based format.

Tôi sử dụng BigDecimal để tính toán, nhưng hãy nhớ rằng nó phụ thuộc vào kích thước của số bạn đang xử lý. Trong hầu hết các triển khai của tôi, tôi thấy phân tích cú pháp từ gấp đôi hoặc số nguyên thành Long là đủ để tính toán số lượng rất lớn.

Trên thực tế, gần đây tôi đã sử dụng phân tích cú pháp từ lâu để có được các biểu diễn chính xác (trái ngược với kết quả hex) trong GUI cho các số lớn như ################### ############### ký tự (làm ví dụ).


6

Để đạt được điều này, chúng ta có thể sử dụng trình định dạng này:

 DecimalFormat df = new DecimalFormat("#.00");
 String resultado = df.format(valor)

hoặc là:

DecimalFormat df = new DecimalFormat("0.00"); :

Sử dụng phương pháp này để luôn luôn có hai số thập phân:

   private static String getTwoDecimals(double value){
      DecimalFormat df = new DecimalFormat("0.00"); 
      return df.format(value);
    }

Xác định các giá trị này:

91.32
5.22
11.5
1.2
2.6

Sử dụng phương pháp chúng ta có thể nhận được kết quả này:

91.32
5.22
11.50
1.20
2.60

demo trực tuyến.


5

Chỉ trong trường hợp ai đó vẫn cần giúp đỡ với điều này. Giải pháp này hoạt động hoàn hảo cho tôi.

private String withNoTrailingZeros(final double value, final int nrOfDecimals) {
return new BigDecimal(String.valueOf(value)).setScale(nrOfDecimals,  BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString();

}

trả về a String với đầu ra mong muốn.


Vui lòng bao gồm lý do của bạn để hạ thấp trong nhận xét, nếu không đó là những gì chúng ta gọi là đe dọa.
Asher. M. O

4

Tôi đồng ý với câu trả lời được chọn để sử dụng DecimalFormat--- hoặc cách khác BigDecimal.

Xin vui lòng đọc Cập nhật dưới đây đầu tiên!

Tuy nhiên nếu bạn làm muốn làm tròn giá trị tăng gấp đôi và có được một doublekết quả giá trị, bạn có thể sử dụng org.apache.commons.math3.util.Precision.round(..)như đã đề cập ở trên. Việc thực hiện sử dụng BigDecimal, là chậm và tạo ra rác.

Một phương thức tương tự nhưng nhanh và không có rác được cung cấp bởi DoubleRoundertiện ích trong thư viện binary4j:

 double a = DoubleRounder.round(2.0/3.0, 3);
 double b = DoubleRounder.round(2.0/3.0, 3, RoundingMode.DOWN);
 double c = DoubleRounder.round(1000.0d, 17);
 double d = DoubleRounder.round(90080070060.1d, 9);
 System.out.println(a);
 System.out.println(b);
 System.out.println(c);
 System.out.println(d);

Sẽ xuất

 0.667
 0.666
 1000.0
 9.00800700601E10

Xem https://github.com/tools4j/decimal4j/wiki/DoubleRounder-Utility

Tuyên bố miễn trừ trách nhiệm: Tôi tham gia vào dự án binary4j.

Cập nhật: Như @iaFork đã chỉ ra DoubleRounder đôi khi trả về kết quả trái ngược. Lý do là nó thực hiện làm tròn chính xác về mặt toán học. Ví dụDoubleRounder.round(256.025d, 2) sẽ được làm tròn xuống 256.02 vì giá trị kép được biểu thị là 256.025d nhỏ hơn một chút so với giá trị hợp lý 256.025 và do đó sẽ được làm tròn xuống.

Ghi chú:

  • Hành vi này rất giống với hành vi của hàm BigDecimal(double)tạo (nhưng không valueOf(double)sử dụng hàm tạo chuỗi).
  • Vấn đề có thể được khắc phục bằng một bước làm tròn gấp đôi với độ chính xác cao hơn trước, nhưng nó phức tạp và tôi không đi sâu vào chi tiết ở đây

Vì những lý do đó và mọi thứ được đề cập ở trên trong bài đăng này, tôi không thể khuyên bạn nên sử dụng DoubleRounder .


Bạn có số liệu cho thấy giải pháp của bạn hiệu quả như thế nào so với các giải pháp khác không?
iaforek

Tôi chưa so sánh nó với các giải pháp khác nhưng có một điểm chuẩn jmh có sẵn trong mã nguồn: github.com/tools4j/decimal4j/blob/master/src/jmh/java/org/. Tôi đã chạy điểm chuẩn trên máy ảo , kết quả có sẵn dưới dạng tệp csv tại đây: github.com/tools4j/decimal4j/wiki/Performance
marco

1
DoubleRounder không thành công cho các trường hợp sau: DoubleRounder.round (256.025d, 2) - dự kiến: 256.03, thực tế: 256.02 hoặc cho DoubleRounder.round (260.775d, 2) - dự kiến: 260.78, thực tế: 260.77.
iaforek

@iaFork: điều này là chính xác, bởi vì DoubleRounder thực hiện làm tròn chính xác về mặt toán học. Tuy nhiên tôi thừa nhận rằng điều này hơi phản trực giác và do đó sẽ cập nhật câu trả lời của tôi cho phù hợp.
marco

3

Đoạn mã dưới đây cho thấy cách hiển thị n chữ số. Mẹo nhỏ là đặt pp biến thành 1 theo sau là n số không. Trong ví dụ dưới đây, giá trị pp biến có 5 số không, vì vậy 5 chữ số sẽ được hiển thị.

double pp = 10000;

double myVal = 22.268699999999967;
String needVal = "22.2687";

double i = (5.0/pp);

String format = "%10.4f";
String getVal = String.format(format,(Math.round((myVal +i)*pp)/pp)-i).trim();

3

Nếu bạn đang sử dụng DecimalFormatđể chuyển đổi doublesang String, điều đó rất đơn giản:

DecimalFormat formatter = new DecimalFormat("0.0##");
formatter.setRoundingMode(RoundingMode.HALF_UP);

double num = 1.234567;
return formatter.format(num);

Có một số RoundingModegiá trị enum để chọn, tùy thuộc vào hành vi bạn yêu cầu.


3

Tôi đến đây chỉ muốn một câu trả lời đơn giản về cách làm tròn số. Đây là một câu trả lời bổ sung để cung cấp điều đó.

Cách làm tròn số trong Java

Trường hợp phổ biến nhất là sử dụng Math.round().

Math.round(3.7) // 4

Các số được làm tròn đến số nguyên gần nhất. Một .5giá trị được làm tròn lên. Nếu bạn cần hành vi làm tròn khác nhau, bạn có thể sử dụng một trong các hàm Toán học khác . Xem so sánh dưới đây.

tròn

Như đã nêu ở trên, vòng này đến số nguyên gần nhất. .5số thập phân làm tròn lên. Phương thức này trả về một int.

Math.round(3.0); // 3
Math.round(3.1); // 3
Math.round(3.5); // 4
Math.round(3.9); // 4

Math.round(-3.0); // -3
Math.round(-3.1); // -3
Math.round(-3.5); // -3 *** careful here ***
Math.round(-3.9); // -4

trần nhà

Bất kỳ giá trị thập phân nào được làm tròn đến số nguyên tiếp theo. Nó đi đến trần nhà ing. Phương thức này trả về a double.

Math.ceil(3.0); // 3.0
Math.ceil(3.1); // 4.0
Math.ceil(3.5); // 4.0
Math.ceil(3.9); // 4.0

Math.ceil(-3.0); // -3.0
Math.ceil(-3.1); // -3.0
Math.ceil(-3.5); // -3.0
Math.ceil(-3.9); // -3.0

sàn nhà

Bất kỳ giá trị thập phân nào được làm tròn xuống số nguyên tiếp theo. Phương thức này trả về a double.

Math.floor(3.0); // 3.0
Math.floor(3.1); // 3.0
Math.floor(3.5); // 3.0
Math.floor(3.9); // 3.0

Math.floor(-3.0); // -3.0
Math.floor(-3.1); // -4.0
Math.floor(-3.5); // -4.0
Math.floor(-3.9); // -4.0

rint

Điều này tương tự như làm tròn trong giá trị thập phân đó làm tròn cho số nguyên gần nhất. Tuy nhiên, không giống như round, .5các giá trị làm tròn đến số nguyên chẵn. Phương thức này trả về a double.

Math.rint(3.0); // 3.0
Math.rint(3.1); // 3.0
Math.rint(3.5); // 4.0 ***
Math.rint(3.9); // 4.0
Math.rint(4.5); // 4.0 ***
Math.rint(5.5); // 6.0 ***

Math.rint(-3.0); // -3.0
Math.rint(-3.1); // -3.0
Math.rint(-3.5); // -4.0 ***
Math.rint(-3.9); // -4.0
Math.rint(-4.5); // -4.0 ***
Math.rint(-5.5); // -6.0 ***

1
bạn chỉ đang giải quyết trường hợp cụ thể làm tròn thành 0 số thập phân. Câu hỏi ban đầu là chung chung hơn.
lukas84

3

Nếu bạn đang sử dụng một công nghệ có JDK tối thiểu. Đây là một cách mà không có bất kỳ lib Java nào:

double scale = 100000;    
double myVal = 0.912385;
double rounded = (int)((myVal * scale) + 0.5d) / scale;

Điều này sẽ thất bại trong trường hợp myVal không nhỏ hơn 1 và với các số 0 sau số thập phân vượt quá giá trị tỷ lệ. Giả sử bạn có myVal = 9.00000000912385; Ở trên sẽ trả về 9.0. Tôi nghĩ rằng chúng ta nên cung cấp một giải pháp hoạt động trong mọi trường hợp của myVal. Không đặc biệt cho giá trị bạn đã nêu.
tavalendo

@ user102859 Trong ví dụ của bạn, 9.0 là kết quả chính xác. Tôi không hiểu làm thế nào điều này sẽ thất bại.
Craigo

2

DecimalFormat là cách tốt nhất để xuất ra, nhưng tôi không thích nó. Tôi luôn luôn làm điều này mọi lúc, vì nó trả về giá trị gấp đôi. Vì vậy, tôi có thể sử dụng nó nhiều hơn là chỉ đầu ra.

Math.round(selfEvaluate*100000d.0)/100000d.0;

HOẶC LÀ

Math.round(selfEvaluate*100000d.0)*0.00000d1;

Nếu bạn cần giá trị số thập phân lớn, bạn có thể sử dụng BigDecimal thay thế. Dù sao .0cũng quan trọng. Nếu không có nó, làm tròn 0,3333d5 trả về 0,3333 và chỉ cho phép 9 chữ số. Hàm thứ hai không .0có vấn đề với 0,30000 return 0,30000000000000004.


2

đây là câu trả lời của tôi

double num = 4.898979485566356;
DecimalFormat df = new DecimalFormat("#.##");      
time = Double.valueOf(df.format(num));

System.out.println(num); // 4.89

1

Vì vậy, sau khi đọc hầu hết các câu trả lời, tôi nhận ra hầu hết trong số chúng sẽ không chính xác, thực tế sử dụng BigDecimalcó vẻ như là sự lựa chọn tốt nhất, nhưng nếu bạn không hiểu cách thức RoundingModehoạt động, bạn sẽ không thể tránh khỏi sự chính xác. Tôi đã tìm ra điều này khi làm việc với những con số lớn trong một dự án và nghĩ rằng nó có thể giúp những người khác gặp khó khăn khi làm tròn số. Ví dụ.

BigDecimal bd = new BigDecimal("1363.2749");
bd = bd.setScale(2, RoundingMode.HALF_UP);
System.out.println(bd.doubleValue());

Bạn sẽ mong đợi nhận được 1363.28như một đầu ra, nhưng bạn sẽ kết thúc 1363.27, điều không được mong đợi, nếu bạn không biết những gì RoundingModeđang làm. Vì vậy, nhìn vào Tài liệu Oracle , bạn sẽ tìm thấy mô tả sau đây RoundingMode.HALF_UP.

Chế độ làm tròn để làm tròn về phía "hàng xóm gần nhất" trừ khi cả hai hàng xóm đều cách đều nhau, trong trường hợp này làm tròn số.

Vì vậy, biết điều này, chúng tôi nhận ra rằng chúng tôi sẽ không làm tròn chính xác, trừ khi chúng tôi muốn làm tròn với người hàng xóm gần nhất . Vì vậy, để hoàn thành một vòng thích hợp, chúng ta sẽ cần lặp từ n-1số thập phân sang các chữ số thập phân mong muốn. Ví dụ.

private double round(double value, int places) throws IllegalArgumentException {

    if (places < 0) throw new IllegalArgumentException();

    // Cast the number to a String and then separate the decimals.
    String stringValue = Double.toString(value);
    String decimals = stringValue.split("\\.")[1];

    // Round all the way to the desired number.
    BigDecimal bd = new BigDecimal(stringValue);
    for (int i = decimals.length()-1; i >= places; i--) {
        bd = bd.setScale(i, RoundingMode.HALF_UP);
    }

    return bd.doubleValue();
}

Điều này sẽ kết thúc cho chúng ta đầu ra dự kiến, sẽ được 1363.28.


0

Trong đó dp = vị trí thập phân bạn muốn và giá trị là gấp đôi.

    double p = Math.pow(10d, dp);

    double result = Math.round(value * p)/p;

1
Sản xuất 1.0cho value = 1.005dp = 2. Sử dụng cái này thay thế.
Matthias Braun

Không sao đâu Matt, ví dụ của bạn không hợp lệ. bởi vì 1.005 không thể được biểu diễn trong dấu phẩy động kép. nó phải được lưu trữ thực sự ở trên hoặc dưới 1.005 tức là nó được lưu trữ gấp đôi khi bạn biên dịch: 1.0049998 (nó không được lưu dưới dạng thập phân trong mã được biên dịch của bạn như bạn có thể tin tưởng) aim là chính xác, anh ta đang lưu trữ các giá trị dưới dạng dấu phẩy động gấp đôi, trong trường hợp rìa như của bạn dù sao cũng không đáng kể. nếu đúng như vậy, thì bạn sẽ sử dụng 3dp sau đó chuyển đổi nó thành số thập phân, sau đó thực hiện chức năng làm tròn số thập phân, giống như liên kết bạn đã đăng.
hamish

1
@hamish Tôi không thấy Matthias 'sẽ khiến độc giả tin vào điều gì' bất kỳ điều gì như giá trị được biên dịch dưới dạng thập phân. Đừng để từ ngữ vào miệng người khác.
Hầu tước Lorne

0

Hãy nhớ rằng String.format () và DecimalFormat tạo ra chuỗi sử dụng Locale mặc định. Vì vậy, họ có thể viết số được định dạng bằng dấu chấm hoặc dấu phẩy dưới dạng dấu phân cách giữa phần nguyên và phần thập phân. Để đảm bảo rằng Chuỗi tròn có định dạng bạn muốn sử dụng java.text.NumberFormat như vậy:

  Locale locale = Locale.ENGLISH;
  NumberFormat nf = NumberFormat.getNumberInstance(locale);
  // for trailing zeros:
  nf.setMinimumFractionDigits(2);
  // round to 2 digits:
  nf.setMaximumFractionDigits(2);

  System.out.println(nf.format(.99));
  System.out.println(nf.format(123.567));
  System.out.println(nf.format(123.0));

Sẽ in bằng ngôn ngữ tiếng Anh (bất kể ngôn ngữ của bạn là gì): 0,99 123,57 123,00

Ví dụ được lấy từ Farenda - cách chuyển đổi gấp đôi thành Chuỗi chính xác .


0

Nói chung, làm tròn được thực hiện bằng cách chia tỷ lệ: round(num / p) * p

/**
 * MidpointRounding away from zero ('arithmetic' rounding)
 * Uses a half-epsilon for correction. (This offsets IEEE-754
 * half-to-even rounding that was applied at the edge cases).
 */
double RoundCorrect(double num, int precision) {
    double c = 0.5 * EPSILON * num;
//  double p = Math.pow(10, precision); //slow
    double p = 1; while (precision--> 0) p *= 10;
    if (num < 0)
        p *= -1;
    return Math.round((num + c) * p) / p;
}

// testing edge cases
RoundCorrect(1.005, 2);   // 1.01 correct
RoundCorrect(2.175, 2);   // 2.18 correct
RoundCorrect(5.015, 2);   // 5.02 correct

RoundCorrect(-1.005, 2);  // -1.01 correct
RoundCorrect(-2.175, 2);  // -2.18 correct
RoundCorrect(-5.015, 2);  // -5.02 correct
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.