在Java中匹配Excel的浮点数

我在工作表1的左上方单元格中有一个单一数字的.xlsx电子表格。

Excel用户界面显示:

-130.98999999999 

这是在公式栏中可见的,即不受包含单元格设置显示的小数位数的影响。 这是Excel将为这个单元格显示的最准确的数字。

在底层的XML中,我们有:

 <v>-130.98999999999069</v> 

当试图用Apache POI读取工作簿时,它通过Double.valueOf从XML中提供数字,并提供:

 -130.9899999999907 

不幸的是,这与用户在Excel中可以看到的数字不同。 任何人都可以指向我的algorithm,以获得用户在Excel中看到相同的数字?

迄今为止,我的研究表明,Excel 2007文件格式使用IEE754浮点的稍微非标准版本,其中值空间不同。 我相信Excel的浮点数,这个数字是四舍五入的边界的另一边,因此出来,而不是向上舍入。

我同意jmcnamara先前的回答 。 这个答案扩展了它。

对于每个IEEE 754 64位二进制浮点数,在input时会有一个十进制小数的范围。 从-130.98999999999069开始,最接近的可表示值是-130.98999999999068677425384521484375。 根据舍入到最接近的半舍弃规则,范围[-130.9899999999907009851085604168474674224853515625,-130.9899999999906725633991300128400325775146484375]中的任何内容都舍入到该值。 (由于中间数字的二进制表示是偶数,因此范围是closures的,如果是奇数,则范围是开放的)。 -130.98999999999069和-130.9899999999907都在范围内。

您具有与Excel相同的浮点数。 您具有与input到Excel中相同的浮点数。 不幸的是,进一步的实验表明,Excel 2007只是转换input中最重要的15位数字。 我将-130.98999999999069粘贴到Excel单元格中。 它不仅显示为-130.98999999999,使用它的算术与最接近该值的两倍一致,-130.989999999990004653227515518665313720703125,而不是原始input。

要获得与Excel相同的效果,您可能需要使用BigDecimal来截断15个十进制数字,然后转换为double。

Java的默认string转换为浮点值基本上select具有最小小数位,将转换回原始值的小数部分。 小数位数-130.9899999999907小于-130.98999999999069。 显然,Excel显示的数字较less,但是Apache POI正在获取与Java中相同数量的表示之一。

这是我用来获得这个答案中的数字的程序。 请注意,我只使用BigDecimal来获得双精度打印输出,并计算两个连续双打之间的中点。

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

这是它的输出:

 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 

不幸的是,这与用户在Excel中可以看到的数字不同。 任何人都可以指向我的algorithm,以获得用户在Excel中看到相同的数字?

我不认为它在这里使用algorithm。 Excel在内部使用IEEE754 double,我猜测它只是在显示数字时使用printf样式格式:

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

您需要使用BigDecimal (不失去任何精度)。
例如,读取值作为一个String ,然后从它构造一个BigDecimal

这里是一个例子,你不会失去任何精度,即这个
是获取用户在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()); } } 

正如peter.petrovbuild议我会使用BigDecimal。 如前所述,让我们无损地导入数据,并始终将比例设置为15,您的行为与Excel中的一样

我用这个来计算相同的15位显示值。

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