2015-02-16 12 views
12

Ho un foglio .xlsx con un singolo numero nella cella in alto a sinistra del foglio 1.abbinabili virgola mobile di Excel in Java

Excel utente viene visualizzato:

-130.98999999999 

Questo è visibile nella barra della formula, cioè non influenzata dal numero di cifre decimali che la cella contenente è impostata per mostrare. È il numero più preciso che Excel mostrerà per questa cella.

Nel XML sottostante, abbiamo:

<v>-130.98999999999069</v> 

Quando si cerca di leggere la cartella di lavoro con Apache POI, si nutre il numero dalla XML tramite Double.valueOf e si presenta con:

-130.9899999999907 

Sfortunatamente, questo non è lo stesso numero che l'utente può vedere in Excel. Qualcuno può indicarmi un algoritmo per ottenere lo stesso numero che l'utente vede in Excel?

La mia ricerca finora suggerisce che il formato di file Excel 2007 utilizza una versione leggermente non standard del punto mobile IEE754, in cui lo spazio dei valori è diverso. Credo nel punto fluttuante di Excel, questo numero cade dall'altra parte del confine per arrotondare e quindi viene fuori come se fosse arrotondato anziché alzato.

risposta

8

Sono d'accordo con jmcnamara's prior answer. Questa risposta si espande su di esso.

Per ogni numero in virgola mobile binario IEEE 754 a 64 bit, è presente un intervallo di frazioni decimali che potrebbero essere arrotondate all'input. A partire da -130.98999999999069, il valore rappresentativo più vicino è -130.98999999999068677425384521484375. Sotto arrotondato al più vicino con le regole della metà pari, qualsiasi cosa nell'intervallo [-130.9899999999907009851085604168474674224853515625, -130.98999999999067253333131312128400325775146484375] arrotonda a quel valore. (L'intervallo è chiuso perché la rappresentazione binaria del numero centrale è pari. Se fosse dispari, l'intervallo sarebbe aperto). Sono compresi nell'intervallo -130.98999999999069 e -130.9899999999907.

Si ha lo stesso numero in virgola mobile di Excel. Lo stesso numero in virgola mobile è stato inserito in Excel. Sfortunatamente, ulteriori esperimenti suggeriscono che Excel 2007 converte solo le 15 cifre più significative del tuo input. Ho incollato -130.98999999999069 in una cella di Excel. Non solo è stato visualizzato come -130.98999999999, l'utilizzo dell'aritmetica era coerente con il doppio più vicino a quel valore, -130.989999999990004653227515518665313720703125, piuttosto che l'input originale.

Per ottenere lo stesso effetto di Excel potrebbe essere necessario utilizzare per es. BigDecimal per troncare a 15 cifre decimali, quindi convertire in doppio.

La conversione di stringhe predefinita di Java per i valori in virgola mobile sceglie fondamentalmente la frazione decimale con il minor numero di posizioni decimali da convertire al valore originale. -130.9899999999907 ha meno cifre decimali di -130.98999999999069. Apparentemente, Excel visualizza meno cifre, ma il POI di Apache ottiene una delle rappresentazioni dello stesso numero di Java.

Ecco il programma che ho utilizzato per ottenere i numeri in questa risposta. Si noti che sto usando BigDecimal solo per ottenere stampe esatte dei doppi e per calcolare il punto medio tra due doppi consecutivi.

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

Qui è la sua uscita:

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

Per questo è necessario utilizzare BigDecimal (per non perdere alcuna precisione).
E.g. leggere il valore come String, quindi creare un BigDecimal da esso.

Ecco un esempio in cui non si perde la precisione, ovvero questo
è il modo per ottenere esattamente lo stesso numero visualizzato dall'utente in 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

Il mio problema non è la precisione, è come emulare l'interpretazione di Excel del numero. L'utilizzo di BigDecimal mantiene il numero riportato nel formato di file, ma non mi avvicina di più al comportamento di Excel. –

+1

@DavidNorth Bene, una volta ottenuto il numero come 'BigDecimal' è possibile arrotondarlo a un numero qualsiasi di posizioni decimali (che è ciò che Excel visualizza visivamente all'utente, a quanto pare: 11 posizioni decimali nell'esempio specifico). In generale, provare a "simulare il comportamento di Excel" non è la strada da percorrere, credo;) –

+2

Dovrò aggiungere qualche altro esempio, ma la difficoltà che stiamo riscontrando è che non esiste un singolo algoritmo di arrotondamento che possiamo trovare utilizzando BigDecimal che corrisponde a Excel in tutti i casi. Per quanto riguarda non tentare di simulare il comportamento di Excel: la difficoltà è che i miei utenti possono vedere un numero in Excel e voglio che mostri loro lo stesso numero quando consumo il foglio di calcolo. Non irragionevole da parte loro, anche se Microsoft sta facendo del suo meglio per renderlo impossibile ... –

2

Come suggerito da peter.petrov userei BigDecimal per questo. Come accennato ti permette di importare i dati senza perdita ed essere sempre impostando la scala a 15 avete la same behaviour as in Excel

3

Purtroppo, questo non è lo stesso numero l'utente può vedere in Excel. Qualcuno può indicarmi un algoritmo per ottenere lo stesso numero che l'utente vede in Excel?

Non penso che stia utilizzando un algoritmo qui. Excel utilizza IEEE754 doppia internamente e direi che è solo utilizzando un formato printf stile quando la visualizzazione del numero:

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

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

Il valore interno è probabilmente -130.98999999999068677425384521484375, il valore in virgola mobile binario IEEE 754 a 64 bit più vicino a -130.9899999999907. –

1

Uso questo per calcolare lo stesso valore di visualizzazione 15 cifre.

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