2015-02-16 16 views
12

Tôi có một bảng tính .xlsx với một số duy nhất trong ô trên cùng bên trái của tờ 1.Matching điểm nổi của Excel trong Java

Các Excel UI hiển thị:

-130.98999999999 

này hiển thị trong các thanh công thức, tức là không bị ảnh hưởng bởi số vị trí thập phân mà ô chứa được đặt để hiển thị. Đó là số chính xác nhất Excel sẽ hiển thị cho ô này.

Trong XML cơ bản, chúng ta có:

<v>-130.98999999999069</v> 

Khi cố gắng để đọc các bảng tính với Apache POI, nó feeds số từ XML thông qua Double.valueOf và đi kèm với:

-130.9899999999907 

Thật không may, đây không phải là số mà người dùng có thể thấy trong Excel. Bất cứ ai có thể chỉ cho tôi một thuật toán để có được cùng một số người dùng nhìn thấy trong Excel?

Nghiên cứu của tôi cho đến nay cho thấy định dạng tệp Excel 2007 sử dụng phiên bản tiêu chuẩn không phải là điểm nổi IEE754, nơi không gian giá trị khác nhau. Tôi tin vào điểm nổi của Excel, con số này nằm ở phía bên kia của ranh giới để làm tròn và do đó đi ra như thể được làm tròn thay vì lên.

Trả lời

8

Tôi đồng ý với jmcnamara's prior answer. Câu trả lời này mở rộng trên đó.

Đối với mỗi số dấu phẩy động nhị phân IEEE 754 64 bit, có một phạm vi các phân số thập phân sẽ làm tròn với nó trên đầu vào. Bắt đầu từ -130.98999999999069, giá trị thể hiện gần nhất là -130.98999999999068677425384521484375. Theo vòng gần nhất với quy tắc nửa vòng, bất kỳ thứ gì trong phạm vi [-130.9899999999907009851085604168474674224853515625, -130.9899999999906725633991300128400325775146484375] làm tròn với giá trị đó. (Phạm vi được đóng bởi vì biểu diễn nhị phân của số trung tâm là ngay cả. Nếu nó là lẻ, phạm vi sẽ được mở). Cả hai -130.98999999999069 và -130.9899999999907 đều nằm trong phạm vi phủ sóng.

Bạn có cùng số dấu phẩy động như Excel. Bạn có cùng số dấu phẩy động như đã nhập vào Excel. Thật không may, các thử nghiệm khác cho thấy Excel 2007 chỉ chuyển đổi 15 chữ số quan trọng nhất của dữ liệu đầu vào của bạn. Tôi đã dán -130.98999999999069 vào ô Excel. Không chỉ được hiển thị dưới dạng -130.98999999999, số học sử dụng nó phù hợp với giá trị gần nhất với giá trị đó, -130.989999999990004653227515518665313720703125, thay vì đầu vào ban đầu.

Để có được hiệu ứng giống như Excel bạn có thể cần sử dụng, ví dụ: BigDecimal để cắt ngắn thành 15 chữ số thập phân, sau đó chuyển đổi thành gấp đôi.

Chuyển đổi chuỗi mặc định của Java cho các giá trị dấu phẩy động cơ bản chọn phần thập phân với các chữ số thập phân ít nhất sẽ chuyển về giá trị ban đầu. -130.9899999999907 có ít chữ số thập phân hơn -130.98999999999069. Rõ ràng, Excel hiển thị ít chữ số hơn, nhưng Apache POI đang nhận được một trong những biểu diễn của cùng một số như bạn có trong Java.

Đây là chương trình tôi đã sử dụng để lấy số trong câu trả lời này. Lưu ý rằng tôi đang sử dụng BigDecimal chỉ để có được bản in chính xác của đôi, và để tính điểm giữa giữa hai đôi liên tiếp.

import java.math.BigDecimal; 

class Test { 
    public static void main(String[] args) { 
    double d = -130.98999999999069; 
    BigDecimal dDec = new BigDecimal(d); 
    System.out.println("Printed as double: "+d); 
    BigDecimal down = new BigDecimal(Math.nextAfter(d, Double.NEGATIVE_INFINITY)); 
    System.out.println("Next down: " + down); 
    System.out.println("Half down: " + down.add(dDec).divide(BigDecimal.valueOf(2))); 
    System.out.println("Original: " + dDec); 
    BigDecimal up = new BigDecimal(Math.nextAfter(d, Double.POSITIVE_INFINITY)); 
    System.out.println("Half up: " + up.add(dDec).divide(BigDecimal.valueOf(2))); 
    System.out.println("Next up: " + up); 
    System.out.println("Original in hex: "+Long.toHexString(Double.doubleToLongBits(d))); 
    } 
} 

Đây là sản lượng của nó:

Printed as double: -130.9899999999907 
Next down: -130.989999999990715195963275618851184844970703125 
Half down: -130.9899999999907009851085604168474674224853515625 
Original: -130.98999999999068677425384521484375 
Half up: -130.9899999999906725633991300128400325775146484375 
Next up: -130.989999999990658352544414810836315155029296875 
Original in hex: c0605fae147ae000 
2

Bạn cần sử dụng BigDecimal cho điều này (vì không làm mất bất kỳ độ chính xác nào).
Ví dụ: đọc giá trị dưới dạng String, sau đó tạo một BigDecimal từ nó.

Dưới đây là ví dụ mà bạn không mất bất kỳ độ chính xác nào, tức là
là cách lấy chính xác cùng một số mà người dùng nhìn thấy trong Excel.

import java.math.BigDecimal; 

public class Test020 { 

    public static void main(String[] args) { 
     BigDecimal d1 = new BigDecimal("-130.98999999999069"); 
     System.out.println(d1.toString()); 

     BigDecimal d2 = new BigDecimal("10.0"); 

     System.out.println(d1.add(d2).toString()); 
     System.out.println(d1.multiply(d2).toString()); 
    } 

} 
+2

Vấn đề của tôi không chính xác, đó là cách mô phỏng giải thích số của Excel. Sử dụng BigDecimal giữ nguyên số lượng như được báo cáo ở định dạng tệp, nhưng nó không giúp tôi gần gũi hơn với hành vi của Excel. –

+1

@DavidNorth Vâng, một khi bạn có số là một 'BigDecimal' bạn có thể làm tròn số bất kỳ số thập phân nào (đây là những gì Excel trình bày trực quan cho người dùng, có vẻ như, 11 vị trí thập phân trong ví dụ cụ thể của bạn). Nói chung, cố gắng "mô phỏng hành vi của Excel" không phải là cách để đi, tôi nghĩ;) –

+2

Tôi sẽ phải thêm một số ví dụ nữa, nhưng khó khăn chúng tôi đang gặp là không có thuật toán làm tròn đơn nào mà chúng ta có thể tìm thấy sử dụng BigDecimal khớp với Excel trong mọi trường hợp. Đối với không cố gắng để mô phỏng hành vi của Excel: khó khăn là người dùng của tôi có thể nhìn thấy một số trong Excel và muốn tôi cho họ thấy cùng một số khi tôi tiêu thụ bảng tính. Không phải là không hợp lý của họ, ngay cả khi Microsoft đang cố hết sức để làm cho nó không thể ... –

2

Được đề xuất bởi peter.petrov Tôi sẽ sử dụng BigDecimal cho việc này. Như đã đề cập, bạn hãy nhập dữ liệu mà không bị mất và luôn đặt tỷ lệ thành 15 bạn có same behaviour as in Excel

3

Thật không may, đây không phải là số mà người dùng có thể thấy trong Excel. Bất cứ ai có thể chỉ cho tôi một thuật toán để có được cùng một số người dùng nhìn thấy trong Excel?

Tôi không nghĩ rằng nó đang sử dụng thuật toán ở đây. Excel sử dụng IEEE754 đôi nội bộ và tôi đoán rằng nó chỉ được sử dụng một định dạng printf phong cách khi hiển thị số:

$ python -c 'print "%.14g" % -130.98999999999069' 
-130.98999999999 

$ python -c 'print "%.14g" % -130.9899999999907' 
-130.98999999999 
+2

Giá trị nội bộ có thể là -130.98999999999068677425384521484375, giá trị dấu phẩy động nhị phân IEEE 754 64 bit gần nhất với -130.9899999999907. –

1

Tôi sử dụng này để tính cùng một giá trị hiển thị 15 chữ số.

private static final int EXCEL_MAX_DIGITS = 15; 

/** 
* Fix floating-point rounding errors. 
* 
* https://en.wikipedia.org/wiki/Numeric_precision_in_Microsoft_Excel 
* https://support.microsoft.com/en-us/kb/214118 
* https://support.microsoft.com/en-us/kb/269370 
*/ 
private static double fixFloatingPointPrecision(double value) { 
    BigDecimal original = new BigDecimal(value); 
    BigDecimal fixed = new BigDecimal(original.unscaledValue(), original.precision()) 
      .setScale(EXCEL_MAX_DIGITS, RoundingMode.HALF_UP); 
    int newScale = original.scale() - original.precision() + EXCEL_MAX_DIGITS; 
    return new BigDecimal(fixed.unscaledValue(), newScale).doubleValue(); 
} 
Các vấn đề liên quan