2015-02-16 16 views
12

Mam .xlsx arkusz z jednego numeru w lewym górnym komórce arkusza 1.Dopasowane zmiennoprzecinkowych programu Excel w Javie

Excel wyświetla UI:

-130.98999999999 

Jest to widoczne w pasek formuły, tzn. nie ma na nie wpływu liczba miejsc dziesiętnych, w której wyświetlana jest komórka zawierająca. Jest to najdokładniejszy numer, jaki Excel wyświetli dla tej komórki.

W podstawowej XML, mamy:

<v>-130.98999999999069</v> 

Kiedy próbuje odczytać skoroszytu z Apache POI, żywi numer z XML poprzez Double.valueOf i pochodzi z:

-130.9899999999907 

Niestety, nie jest to ten sam numer, który użytkownik może zobaczyć w programie Excel. Czy każdy może wskazać mi algorytm, aby uzyskać ten sam numer, który widzi użytkownik w programie Excel?

Moje dotychczasowe badania sugerują, że format pliku Excel 2007 używa nieco niestandardowej wersji zmiennoprzecinkowej IEE754, gdzie przestrzeń wartości jest inna. Wierzę w zmiennoprzecinkową wartość Excela, ta liczba spada na drugą stronę granicy zaokrąglania, a więc wychodzi zaokrąglona w dół zamiast w górę.

Odpowiedz

8

zgadzam się z jmcnamara's prior answer. Ta odpowiedź rozszerza się na nią.

Dla każdego 64-bitowego binarnego numeru zmiennoprzecinkowego IEEE 754 istnieje zakres ułamków dziesiętnych, który zaokrąglałby się do niego na wejściu. Począwszy od -130,98999999999069, najbliższa reprezentowalna wartość to -130,989999999068677425384521484375. Poniżej rundy do najbliższej z okrągłymi regułami równymi połowie, wszystko w zakresie [-130.9899999999907009851085604168474674224853515625, -130,9899999999906725633991300128400325775146484375] zaokrągla do tej wartości. (Zakres jest zamknięty, ponieważ binarna reprezentacja liczby centralnej jest parzysta, jeśli byłaby nieparzysta, zakres byłby otwarty). Zarówno -130.98999999999069, jak i -130,9899999999907 są w zasięgu.

Masz taką samą liczbę zmiennoprzecinkową jak Excel. Masz tę samą liczbę zmiennoprzecinkową, która została wprowadzona do programu Excel. Niestety, kolejne eksperymenty sugerują, że Excel 2007 konwertuje tylko najważniejsze 15 cyfr danych wejściowych. Wkleiłem -130.98999999999069 do komórki Excel. Nie tylko był wyświetlany jako -130.98999999999, arytmetyka używająca go była zgodna z najbliższą podwójną do tej wartości, -130.989999999990004653227515518665313720703125, a nie oryginalną wartością wejściową.

Aby uzyskać taki sam efekt jak w programie Excel, może być konieczne użycie np. BigDecimal do obcięcia do 15 cyfr dziesiętnych, a następnie konwersji do podwójnego.

Domyślna konwersja ciągów Java dla wartości zmiennoprzecinkowych w zasadzie wybiera ułamek dziesiętny z najmniejszą liczbą miejsc dziesiętnych, które zostałyby zamienione z powrotem na pierwotną wartość. -130,9899999999907 ma mniej miejsc po przecinku niż -130,98999999999069. Wygląda na to, że program Excel wyświetla mniej cyfr, ale Apache POI uzyskuje jedną z reprezentacji tego samego numeru, co w Javie.

Oto program, którego użyłem do uzyskania liczb w tej odpowiedzi. Zauważ, że używam BigDecimal tylko do uzyskania dokładnych wydruków podwójnych i do obliczenia punktu środkowego pomiędzy dwoma kolejnymi debelami.

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))); 
    } 
} 

Oto jej wyniki:

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

Do tego celu należy użyć BigDecimal (aby nie stracić precyzji).
E.g. odczytaj wartość jako String, a następnie utwórz z niej BigDecimal.

Oto przykład, w którym nie traci się precyzji, tj. Ten
jest sposobem uzyskania dokładnie tego samego numeru, który widzi użytkownik w programie 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

Mój problem to nie dokładność, to jak emulować interpretację liczby w Excelu. Używanie BigDecimal zachowuje numer podany w formacie pliku, ale nie zbliża mnie to do zachowania Excela. –

+1

@DavidNorth Cóż, gdy już masz numer "BigDecimal", możesz go zaokrąglić do dowolnej liczby miejsc po przecinku (co widać w Excelu dla użytkownika, wydaje się, 11 miejsc dziesiętnych w twoim konkretnym przykładzie). Ogólnie rzecz biorąc, próba "symulowania zachowania Excela" nie jest drogą, jak sądzę;) –

+2

Będę musiał dodać więcej przykładów, ale trudność, którą mamy, polega na tym, że nie ma jednego algorytmu do zaokrąglania, który możemy znaleźć przy użyciu BigDecimal, który pasuje do Excela we wszystkich przypadkach. Jeśli nie próbujesz symulować zachowania Excela: trudność polega na tym, że moi użytkownicy widzą liczbę w Excelu i chcą, żebym pokazał im ten sam numer podczas spożywania arkusza kalkulacyjnego. Nierozsądne z nich, nawet jeśli Microsoft robi wszystko, aby to uniemożliwić ... –

2

Zgodnie z sugestią Peter.petrov użyłbym do tego BigDecimal. Jak wspomniano to niech importowania danych bez utraty i być zawsze ustawienie skali do 15 masz same behaviour as in Excel

3

Niestety, to nie jest ten sam numer użytkownik może zobaczyć w programie Excel. Czy każdy może wskazać mi algorytm, aby uzyskać ten sam numer, który widzi użytkownik w programie Excel?

Nie sądzę, że używa tutaj algorytmu. Excel używa IEEE 754 podwójnie wewnętrznie, a ja myślę, że to jest po prostu przy użyciu formatu printf stylu podczas wyświetlania numeru:

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

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

Wewnętrzna wartość to prawdopodobnie -130.98999999999068677425384521484375, najbliższa 64-bitowa binarna wartość zmiennoprzecinkowa IEEE 754 do -130.9899999999907. –

1

Używam tego obliczania samą wartość 15-cyfrowy wyświetlacz.

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(); 
} 
Powiązane problemy