Làm tròn đến một số chữ số có nghĩa tùy ý


83

Làm thế nào bạn có thể làm tròn bất kỳ số nào (không chỉ số nguyên> 0) thành N chữ số có nghĩa?

Ví dụ: nếu tôi muốn làm tròn thành ba chữ số có nghĩa, tôi đang tìm một công thức có thể lấy:

1.239.451 và trả lại 1.240.000

12.1257 và trả về 12.1

.0681 và trả về .0681

5 và trả về 5

Đương nhiên, thuật toán không nên được mã hóa cứng để chỉ xử lý N trong 3, mặc dù đó sẽ là một bước khởi đầu.


Có vẻ câu hỏi quá chung chung. Các ngôn ngữ lập trình khác nhau có chức năng tiêu chuẩn khác nhau để thực hiện việc này. Thực sự không thích hợp để phát minh lại bánh xe.
Johnny Wong

Câu trả lời:


106

Đây là mã tương tự trong Java mà không có lỗi 12.100000000000001 các câu trả lời khác có

Tôi cũng đã xóa mã lặp lại, thay đổi powerthành kiểu số nguyên để tránh các vấn đề nổi khi n - dhoàn tất và làm rõ ràng hơn trung gian dài

Lỗi là do nhân một số lớn với một số nhỏ. Thay vào đó tôi chia hai số có kích thước tương tự nhau.

EDIT
Sửa nhiều lỗi hơn. Đã thêm kiểm tra cho 0 vì nó sẽ dẫn đến NaN. Đã làm cho hàm thực sự hoạt động với các số âm (Mã ban đầu không xử lý các số âm vì nhật ký của một số âm là một số phức)

public static double roundToSignificantFigures(double num, int n) {
    if(num == 0) {
        return 0;
    }

    final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
    final int power = n - (int) d;

    final double magnitude = Math.pow(10, power);
    final long shifted = Math.round(num*magnitude);
    return shifted/magnitude;
}

2
Cảm ơn vì đã chấp nhận câu trả lời của tôi. Tôi chỉ nhận ra câu trả lời của tôi là hơn một năm sau câu hỏi. Đây là một trong những lý do tại sao stackoverflow lại rất tuyệt. Bạn có thể tìm thấy thông tin hữu ích!
Pyrolistical

2
Lưu ý rằng điều này có thể không thành công một chút đối với các giá trị gần với giới hạn vòng. Ví dụ: làm tròn 1,255 thành 3 chữ số có nghĩa sẽ trả về 1,26 nhưng trả về 1,25. Đó là bởi vì 1,255 * 100,0 là 125,499999 ... Nhưng đây là để được mong đợi khi làm việc với đôi
cquezel

Chà, tôi biết cái này cũ rồi, nhưng tôi đang cố sử dụng nó. Tôi có một phao mà tôi muốn hiển thị đến 3 số liệu quan trọng. Nếu giá trị float là 1.0, tôi gọi phương thức của bạn nhưng nó vẫn trả về là 1.0, ngay cả khi tôi ép float thành giá trị kép. Tôi muốn nó trở lại như 1. Có ý kiến ​​gì không?
Steve W,

3
Đoạn mã java này kết thúc trong ví dụ android chính thức android.googlesource.com/platform/development/+/fcf4286/samples/…
Curious Sam

1
Không hoàn hảo. Đối với num = -7999999.999999992 và n = 2 trả về -7999999.999999999 nhưng phải là -8000000.
Duncan Calvert

16

Đây là một triển khai JavaScript ngắn gọn và thú vị:

function sigFigs(n, sig) {
    var mult = Math.pow(10, sig - Math.floor(Math.log(n) / Math.LN10) - 1);
    return Math.round(n * mult) / mult;
}

alert(sigFigs(1234567, 3)); // Gives 1230000
alert(sigFigs(0.06805, 3)); // Gives 0.0681
alert(sigFigs(5, 3)); // Gives 5

nhưng 12,1257 cho 12,126
Pyrolistical

1
Câu trả lời hay đấy Ates. Có lẽ thêm một kích hoạt để trở về 0 nếu n==0:)
sscirrus

có lý do gì để làm Math.log(n) / Math.LN10hơn là Math.log10(n)?
Lee

1
@Lee developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… "Đây là một công nghệ mới, một phần của tiêu chuẩn ECMAScript 2015 (ES6)." Vì vậy, về cơ bản, các vấn đề tương thích.
Ates Goral

Tôi có thiếu cái gì đó không, hay câu trả lời này giả định điều đó Math.floor(x) == Math.ceil(x) - 1? Bởi vì nó không phải xlà một số nguyên. Tôi nghĩ đối số thứ hai của powhàm nên là sig - Math.ceil(Math.log(n) / Math.LN10)(hoặc chỉ sử dụng Math.log10)
Paul

15

TÓM LƯỢC:

double roundit(double num, double N)
{
    double d = log10(num);
    double power;
    if (num > 0)
    {
        d = ceil(d);
        power = -(d-N);
    }
    else
    {
        d = floor(d); 
        power = -(d-N);
    }

    return (int)(num * pow(10.0, power) + 0.5) * pow(10.0, -power);
}

Vì vậy, bạn cần tìm vị trí thập phân của chữ số khác 0 đầu tiên, sau đó lưu N-1 chữ số tiếp theo, sau đó làm tròn chữ số thứ N dựa trên phần còn lại.

Chúng ta có thể sử dụng nhật ký để làm việc đầu tiên.

log 1239451 = 6.09
log 12.1257 = 1.08
log 0.0681  = -1.16

Vì vậy, đối với các số> 0, hãy lấy điểm cuối của nhật ký. Đối với các số <0, lấy tầng của nhật ký.

Bây giờ chúng ta có chữ số d: 7 trong trường hợp đầu tiên, 2 trong trường hợp thứ 2, -2 trong trường hợp thứ 3.

Chúng ta phải làm tròn (d-N)chữ số thứ. Cái gì đó như:

double roundedrest = num * pow(10, -(d-N));

pow(1239451, -4) = 123.9451
pow(12.1257, 1)  = 121.257
pow(0.0681, 4)   = 681

Sau đó, thực hiện điều làm tròn tiêu chuẩn:

roundedrest = (int)(roundedrest + 0.5);

Và hoàn tác các pow.

roundednum = pow(roundedrest, -(power))

Công suất là công suất được tính ở trên.


Về độ chính xác: Câu trả lời của Pyrolistical thực sự gần với kết quả thực hơn. Nhưng lưu ý rằng bạn không thể đại diện chính xác 12.1 trong mọi trường hợp. Nếu bạn in câu trả lời như sau:

System.out.println(new BigDecimal(n));

Câu trả lời là:

Pyro's: 12.0999999999999996447286321199499070644378662109375
Mine: 12.10000000000000142108547152020037174224853515625
Printing 12.1 directly: 12.0999999999999996447286321199499070644378662109375

Vì vậy, hãy sử dụng câu trả lời của Pyro!


1
Thuật toán này có vẻ dễ bị lỗi dấu phẩy động. Khi được triển khai với JavaScript, tôi nhận được: 0,06805 -> 0,06810000000000001 và 12,1 -> 12.100000000000001
Ates Goral

Bản thân 12.1 không thể được biểu diễn chính xác bằng cách sử dụng dấu phẩy động - nó không phải là kết quả của thuật toán này.
Claudiu 15/10/08

1
Mã này trong Java tạo ra 12.100000000000001 và mã này đang sử dụng nhân đôi 64-bit có thể hiển thị chính xác 12.1.
Pyrolistical

4
Không quan trọng nếu đó là 64 bit hay 128 bit. Bạn không thể đại diện cho phần 1/10 sử dụng một khoản tiền hữu hạn các quyền hạn của 2, và đó là cách số dấu chấm động được biểu diễn
Claudiu

2
đối với những người làm phiền, về cơ bản câu trả lời của Pyrolistical chính xác hơn của tôi để thuật toán in số dấu phẩy động in '12 .1 'thay vì '12 .100000000000001'. câu trả lời của anh ấy tốt hơn, ngay cả khi tôi đã đúng về mặt kỹ thuật rằng bạn không thể đại diện chính xác '12 .1 '.
Claudiu

10

Không phải là triển khai JavaScript "ngắn và hấp dẫn"

Number(n).toPrecision(sig)

ví dụ

alert(Number(12345).toPrecision(3)

?

Xin lỗi, tôi không quá lo lắng ở đây, chỉ là việc sử dụng hàm "roundit" từ Claudiu và .toPre precision trong JavaScript mang lại cho tôi các kết quả khác nhau nhưng chỉ làm tròn chữ số cuối cùng.

JavaScript:

Number(8.14301).toPrecision(4) == 8.143

.MẠNG LƯỚI

roundit(8.14301,4) == 8.144

1
Number(814301).toPrecision(4) == "8.143e+5". Nói chung không phải những gì bạn muốn nếu bạn đang hiển thị điều này cho người dùng.
Zaz

Rất đúng Josh vâng, tôi thường khuyên bạn chỉ nên sử dụng .toPre precision () cho các số thập phân và câu trả lời được chấp nhận (có chỉnh sửa) nên được sử dụng / xem xét theo yêu cầu cá nhân của bạn.
Justin Wignall

8

Giải pháp (rất hay!) Của Pyrolistical vẫn có vấn đề. Giá trị nhân đôi lớn nhất trong Java có thứ tự là 10 ^ 308, trong khi giá trị nhỏ nhất có thứ tự là 10 ^ -324. Do đó, bạn có thể gặp rắc rối khi áp dụng hàm roundToSignificantFigurescho một thứ gì đó nằm trong phạm vi một vài quyền hạn của mười Double.MIN_VALUE. Ví dụ, khi bạn gọi

roundToSignificantFigures(1.234E-310, 3);

thì biến powersẽ có giá trị 3 - (-309) = 312. Do đó, biến magnitudesẽ trở thành Infinity, và tất cả đều là rác từ đó trở đi. May mắn thay, đây không phải là một vấn đề không thể vượt qua: nó chỉ là yếu tố magnitude tràn ra. Điều thực sự quan trọng là sản phẩm num * magnitude và điều đó không bị tràn. Một cách để giải quyết vấn đề này là chia phép nhân với thừa số magintudethành hai bước:


 public static double roundToNumberOfSignificantDigits(double num, int n) {

    final double maxPowerOfTen = Math.floor(Math.log10(Double.MAX_VALUE));

    if(num == 0) {
        return 0;
    }

    final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
    final int power = n - (int) d;

    double firstMagnitudeFactor = 1.0;
    double secondMagnitudeFactor = 1.0;
    if (power > maxPowerOfTen) {
        firstMagnitudeFactor = Math.pow(10.0, maxPowerOfTen);
        secondMagnitudeFactor = Math.pow(10.0, (double) power - maxPowerOfTen);
    } else {
        firstMagnitudeFactor = Math.pow(10.0, (double) power);
    }

    double toBeRounded = num * firstMagnitudeFactor;
    toBeRounded *= secondMagnitudeFactor;

    final long shifted = Math.round(toBeRounded);
    double rounded = ((double) shifted) / firstMagnitudeFactor;
    rounded /= secondMagnitudeFactor;
    return rounded;
}


6

Làm thế nào về giải pháp java này:

double roundToSignificantFigure (double num, int precision) {
 trả về BigDecimal mới (num)
            .round (MathContext mới (precision, RoundingMode.HALF_EVEN))
            .doubleValue (); 
}

3

Đây là phiên bản JavaScript được sửa đổi của Ates để xử lý các số âm.

function sigFigs(n, sig) {
    if ( n === 0 )
        return 0
    var mult = Math.pow(10,
        sig - Math.floor(Math.log(n < 0 ? -n: n) / Math.LN10) - 1);
    return Math.round(n * mult) / mult;
 }

2

Điều này đến muộn 5 năm, nhưng mặc dù tôi sẽ chia sẻ cho những người khác vẫn gặp vấn đề tương tự. Tôi thích nó vì nó đơn giản và không có tính toán về mặt mã. Xem Các phương pháp tích hợp để hiển thị các số liệu quan trọng để biết thêm thông tin.

Đây là nếu bạn chỉ muốn in nó ra.

public String toSignificantFiguresString(BigDecimal bd, int significantFigures){
    return String.format("%."+significantFigures+"G", bd);
}

Đây là nếu bạn muốn chuyển đổi nó:

public BigDecimal toSignificantFigures(BigDecimal bd, int significantFigures){
    String s = String.format("%."+significantFigures+"G", bd);
    BigDecimal result = new BigDecimal(s);
    return result;
}

Đây là một ví dụ về nó trong hoạt động:

BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2);

Điều này sẽ hiển thị các số "lớn" trong ký hiệu khoa học, ví dụ: 15k là 1,5e04.
matt

2

JavaScript:

Number( my_number.toPrecision(3) );

Các Numberchức năng sẽ thay đổi sản lượng của mẫu "8.143e+5"để "814300".


1

Bạn đã thử mã hóa nó theo cách bạn làm bằng tay chưa?

  1. Chuyển số thành chuỗi
  2. Bắt đầu từ đầu chuỗi, các chữ số đếm - các số 0 đứng đầu không có nghĩa, mọi thứ khác đều như vậy.
  3. Khi bạn đến chữ số "thứ n", hãy nhìn trước chữ số tiếp theo và nếu nó là 5 hoặc cao hơn, hãy làm tròn.
  4. Thay thế tất cả các chữ số ở cuối bằng số 0.

1

[Đã sửa, 2009-10-26]

Về cơ bản, đối với N chữ số phân số có nghĩa :

• Nhân một số với 10 N
• Cộng 0,5
• Cắt ngắn các chữ số của phân số (tức là cắt bớt kết quả thành một số nguyên)
• Chia cho 10 N

Đối với N chữ số tích phân có nghĩa (không phải phân số):

• Chia số cho 10 N
• Cộng 0,5
• Cắt ngắn các chữ số của phân số (tức là cắt bớt kết quả thành một số nguyên)
• Nhân với 10 N

Bạn có thể thực hiện việc này trên bất kỳ máy tính nào, chẳng hạn, có toán tử "INT" (cắt bớt số nguyên).


Không. Đọc lại câu hỏi. 1239451 với 3 sig sung sử dụng thuật toán của bạn không đúng cách sẽ mang lại 123.951
Pyrolistical

Đúng, tôi đã sửa nó để phân biệt giữa việc làm tròn thành một số thập phân (ở bên phải của dấu thập phân) với một số tích phân (ở bên trái).
David R Tribble

1
/**
 * Set Significant Digits.
 * @param value value
 * @param digits digits
 * @return
 */
public static BigDecimal setSignificantDigits(BigDecimal value, int digits) {
    //# Start with the leftmost non-zero digit (e.g. the "1" in 1200, or the "2" in 0.0256).
    //# Keep n digits. Replace the rest with zeros.
    //# Round up by one if appropriate.
    int p = value.precision();
    int s = value.scale();
    if (p < digits) {
        value = value.setScale(s + digits - p); //, RoundingMode.HALF_UP
    }
    value = value.movePointRight(s).movePointLeft(p - digits).setScale(0, RoundingMode.HALF_UP)
        .movePointRight(p - digits).movePointLeft(s);
    s = (s > (p - digits)) ? (s - (p - digits)) : 0;
    return value.setScale(s);
}

1

Đây là mã của Pyrolistical (câu trả lời hàng đầu hiện nay) trong Visual Basic.NET, nếu bất kỳ ai cần nó:

Public Shared Function roundToSignificantDigits(ByVal num As Double, ByVal n As Integer) As Double
    If (num = 0) Then
        Return 0
    End If

    Dim d As Double = Math.Ceiling(Math.Log10(If(num < 0, -num, num)))
    Dim power As Integer = n - CInt(d)
    Dim magnitude As Double = Math.Pow(10, power)
    Dim shifted As Double = Math.Round(num * magnitude)
    Return shifted / magnitude
End Function

0

Đây là cái mà tôi đã nghĩ ra trong VB:

Function SF(n As Double, SigFigs As Integer)
    Dim l As Integer = n.ToString.Length
    n = n / 10 ^ (l - SigFigs)
    n = Math.Round(n)
    n = n * 10 ^ (l - SigFigs)
    Return n
End Function

0

return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();


0

Tôi cần điều này trong Go, điều này hơi phức tạp do thư viện tiêu chuẩn Go thiếu math.Round()(trước go1.10). Vì vậy, tôi cũng phải đánh nó lên. Đây là bản dịch của tôi về câu trả lời xuất sắc của Pyrolistical :

// TODO: replace in go1.10 with math.Round()
func round(x float64) float64 {
    return float64(int64(x + 0.5))
}

// SignificantDigits rounds a float64 to digits significant digits.
// Translated from Java at https://stackoverflow.com/a/1581007/1068283
func SignificantDigits(x float64, digits int) float64 {
    if x == 0 {
        return 0
    }

    power := digits - int(math.Ceil(math.Log10(math.Abs(x))))
    magnitude := math.Pow(10, float64(power))
    shifted := round(x * magnitude)
    return shifted / magnitude
}

Điều này có một phản ứng bí ẩn! Nhưng tôi không thể tìm thấy lỗi hoặc vấn đề trong đó. Những gì đang xảy ra ở đây?
Michael Hampton

0

Bạn có thể tránh thực hiện tất cả các phép tính này với lũy thừa 10, v.v. bằng cách sử dụng FloatToStrF.

FloatToStrF cho phép bạn (trong số những thứ khác) chọn độ chính xác (số lượng các số liệu quan trọng) trong giá trị đầu ra (sẽ là một chuỗi). Tất nhiên, sau đó bạn có thể áp dụng StrToFloat vào điều này để nhận giá trị làm tròn của bạn dưới dạng một float.

Xem tại đây:

http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/SysUtils_FloatToStrF@Extended@TFloatFormat@Integer@Integer.html


-1
public static double roundToSignificantDigits(double num, int n) {
    return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}

Mã này sử dụng hàm định dạng có sẵn được chuyển sang hàm làm tròn

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.