2008-11-05 51 views
7

Hiện nay tôi có phương pháp này:Làm thế nào để kiểm tra xem một đôi có nhiều nhất n chữ số thập phân?

static boolean checkDecimalPlaces(double d, int decimalPlaces){ 
    if (d==0) return true; 

    double multiplier = Math.pow(10, decimalPlaces); 
    double check = d * multiplier; 
    check = Math.round(check);  
    check = check/multiplier; 
    return (d==check);  
} 

Nhưng phương pháp này không cho checkDecmialPlaces(649632196443.4279, 4) lẽ bởi vì tôi làm cơ sở 10 toán trên cơ sở số 2.

Vậy cách kiểm tra này có thể được thực hiện đúng cách?

Tôi đã nghĩ đến việc biểu diễn chuỗi giá trị kép và sau đó kiểm tra bằng regexp - nhưng điều đó thật lạ.

EDIT: Cảm ơn mọi câu trả lời. Có những trường hợp mà tôi thực sự có được một đôi và đối với những trường hợp tôi thực hiện như sau:

private static boolean checkDecimalPlaces(double d, int decimalPlaces) { 
    if (d == 0) return true; 

    final double epsilon = Math.pow(10.0, ((decimalPlaces + 1) * -1)); 

    double multiplier = Math.pow(10, decimalPlaces); 
    double check = d * multiplier; 
    long checkLong = (long) Math.abs(check); 
    check = checkLong/multiplier; 

    double e = Math.abs(d - check); 
    return e < epsilon; 
} 

Tôi đã thay đổi round đến một cắt ngắn. Dường như việc tính toán được thực hiện trong round làm tăng sự không chính xác quá nhiều. Ít nhất trong testcase thất bại.
Như một số bạn chỉ ra nếu tôi có thể nhận được vào chuỗi đầu vào 'thực tế' tôi nên sử dụng BigDecimal để kiểm tra và vì vậy tôi đã thực hiện:

BigDecimal decimal = new BigDecimal(value); 
BigDecimal checkDecimal = decimal.movePointRight(decimalPlaces); 
return checkDecimal.scale() == 0; 

Giá trị double tôi nhận được xuất phát từ API Apache POI mà đọc các tệp excel. Tôi đã làm một vài xét nghiệm và phát hiện ra rằng mặc dù các API trả về double giá trị cho các tế bào số tôi có thể có được một đại diện chính xác khi tôi ngay lập tức định dạng mà double với DecimalFormat:

DecimalFormat decimalFormat = new DecimalFormat(); 
decimalFormat.setMaximumIntegerDigits(Integer.MAX_VALUE); 
// don't use grouping for numeric-type cells 
decimalFormat.setGroupingUsed(false); 
decimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US)); 
value = decimalFormat.format(numericValue); 

Điều này cũng làm cho các giá trị mà không thể được biểu diễn chính xác ở định dạng nhị phân.

+0

"đúng" không có nghĩa là nhiều đây. Số thập phân ngẫu nhiên của bạn không có biểu diễn nhị phân chính xác. Bạn đang tìm số thập phân "đủ gần" để mọi người thích phiên bản thập phân của giá trị nhị phân. Làm thế nào gần là đủ gần? –

Trả lời

6

Thử nghiệm thất bại, bởi vì bạn đã đạt đến độ chính xác của biểu diễn dấu phẩy động nhị phân, có khoảng 16 chữ số với IEEE754 double precision. Nhân với 649632196443.4279 bởi 10000 sẽ cắt ngắn biểu diễn nhị phân, dẫn đến lỗi khi làm tròn và chia sau đó, do đó làm mất hiệu lực kết quả của hàm của bạn hoàn toàn.

Để biết thêm chi tiết, xem http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

Một cách tốt hơn là nên kiểm tra xem n+1 chữ số thập phân dưới một ngưỡng nhất định. Nếu d - round(d) nhỏ hơn epsilon (xem limit), đại diện thập phân của d không có vị trí thập phân đáng kể. Tương tự, nếu (d - round(d)) * 10^n nhỏ hơn epsilon, d có thể có tối đa n địa điểm quan trọng.

Sử dụng Jon Skeet 's DoubleConverter để kiểm tra các trường hợp trong đó d không đủ chính xác để giữ vị trí thập phân mà bạn đang tìm kiếm.

+0

Tôi cũng nhìn vào DoubleConverter Jon đăng trong câu trả lời và nó có vẻ khá giống với BigDecimal. Tôi tự hỏi tại sao anh ta lại trả lời. – Turismo

3

Như với tất cả các số học dấu chấm động, bạn không nên kiểm tra sự bình đẳng, mà đúng hơn là lỗi (epsilon) là đủ nhỏ.

Nếu bạn thay thế:

return (d==check); 

với một cái gì đó giống như

return (Math.abs(d-check) <= 0.0000001); 

cần làm việc. Rõ ràng, epsilon nên được chọn là đủ nhỏ so với số thập phân bạn đang kiểm tra.

+0

Vấn đề lớn nhất là tràn khi bạn làm: kiểm tra = d * nhân; tính năng này không hoạt động với số lượng lớn; – tvanfosson

+0

IEE 754 64-bit đôi chỉ có khoảng 18 chữ số chính xác như vậy, vì vậy, nếu bạn nhận được vào số nơi nhân với 1000 sẽ gây ra tràn (khoảng 10^304), bạn có thể chắc chắn rằng không có số thập phân trong đại diện. – paxdiablo

1

Loại double là số dấu phẩy động nhị phân. Luôn luôn có những điểm không chính xác rõ ràng trong việc xử lý chúng như thể chúng là số thập phân dấu phẩy động. Tôi không biết rằng bạn sẽ có thể viết chức năng của bạn để nó hoạt động theo cách bạn muốn.

Bạn có thể sẽ phải quay lại nguồn gốc của số (đầu vào chuỗi có thể) và giữ biểu diễn thập phân nếu nó quan trọng đối với bạn.

5

Nếu mục tiêu của bạn là đại diện cho một số có chính xác n số liệu quan trọng ở bên phải số thập phân, BigDecimal là lớp cần sử dụng.

Độ chính xác không thay đổi, tùy ý được ký số thập phân.Một BigDecimal bao gồm của một số nguyên chính xác tùy ý giá trị chưa được đánh giá và số nguyên 32 bit . Nếu không hoặc dương, tỷ lệ là số chữ số bên phải của dấu thập phân. Nếu âm, giá trị số chưa được đánh số của số là nhân với mười với công suất của số phủ định của tỷ lệ. Giá trị của số được đại diện bởi số BigDecimal do đó (unscaledValue × 10 tỷ lệ).

scale có thể được thiết lập thông qua setScale(int)

0

Tôi không chắc chắn rằng đây thực sự là doable nói chung. Ví dụ: có bao nhiêu vị trí thập phân không 1.0e-13 có? Điều gì xảy ra nếu nó phát sinh từ một số lỗi làm tròn trong khi làm số học và thực sự chỉ là 0 trong ngụy trang? Nếu trên, mặt khác bạn đang yêu cầu nếu có bất kỳ chữ số khác không trong n chữ số thập phân thứ nhất bạn có thể làm một cái gì đó như:

static boolean checkDecimalPlaces(double d, unsigned int decimalPlaces){ 
     // take advantage of truncation, may need to use BigInt here 
     // depending on your range 
     double d_abs = Math.abs(d); 
     unsigned long d_i = d_abs; 
     unsigned long e = (d_abs - d_i) * Math.pow(10, decimalPlaces); 
     return e > 0; 
    } 
+0

thực sự câu trả lời của bạn đã giúp tôi, nhưng vì nó là bây giờ nó là thiếu sót như bạn phải nhân 'd' trước khi bạn cắt nó để không mất chữ số đáng kể - xem chỉnh sửa của tôi về câu hỏi – Turismo

1

Nếu bạn có thể chuyển sang BigDecimal, như Ken G giải thích, đó là những gì bạn nên sử dụng.

Nếu không, bạn phải giải quyết một loạt vấn đề như được đề cập trong các câu trả lời khác. Với tôi, bạn đang xử lý một số nhị phân (double) và đặt câu hỏi về biểu diễn thập phân của số đó; tức là, bạn đang hỏi về một Chuỗi. Tôi nghĩ trực giác của bạn là chính xác.

0

Tôi nghĩ rằng đây là tốt hơn Chuyển đổi chuỗi và thẩm vấn những giá trị cho số mũ

public int calcBase10Exponet (Number increment) 
{ 
    //toSting of 0.0=0.0 
    //toSting of 1.0=1.0 
    //toSting of 10.0=10.0 
    //toSting of 100.0=100.0 
    //toSting of 1000.0=1000.0 
    //toSting of 10000.0=10000.0 
    //toSting of 100000.0=100000.0 
    //toSting of 1000000.0=1000000.0 
    //toSting of 1.0E7=1.0E7 
    //toSting of 1.0E8=1.0E8 
    //toSting of 1.0E9=1.0E9 
    //toSting of 1.0E10=1.0E10 
    //toSting of 1.0E11=1.0E11 
    //toSting of 0.1=0.1 
    //toSting of 0.01=0.01 
    //toSting of 0.0010=0.0010 <== need to trim off this extra zero 
    //toSting of 1.0E-4=1.0E-4 
    //toSting of 1.0E-5=1.0E-5 
    //toSting of 1.0E-6=1.0E-6 
    //toSting of 1.0E-7=1.0E-7 
    //toSting of 1.0E-8=1.0E-8 
    //toSting of 1.0E-9=1.0E-9 
    //toSting of 1.0E-10=1.0E-10 
    //toSting of 1.0E-11=1.0E-11 
    double dbl = increment.doubleValue(); 
    String str = Double.toString (dbl); 
// System.out.println ("NumberBoxDefaultPatternCalculator: toSting of " + dbl + "=" + str); 
    if (str.contains ("E")) 
    { 
    return Integer.parseInt (str.substring (str.indexOf ("E") + 1)); 
    } 
    if (str.endsWith (".0")) 
    { 
    return str.length() - 3; 
    } 
    while (str.endsWith ("0")) 
    { 
    str = str.substring (0, str.length() - 1); 
    } 
    return - (str.length() - str.indexOf (".") - 1); 
} 
Các vấn đề liên quan