git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@798771 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_5-FINAL
@@ -33,6 +33,7 @@ | |||
<changes> | |||
<release version="3.5-beta7" date="2009-??-??"> | |||
<action dev="POI-DEVELOPERS" type="fix">47598 - Improved formula evaluator number comparison</action> | |||
<action dev="POI-DEVELOPERS" type="fix">47571 - Fixed XWPFWordExtractor to extract inserted/deleted text</action> | |||
<action dev="POI-DEVELOPERS" type="fix">47548 - Fixed RecordFactoryInputStream to properly read continued DrawingRecords</action> | |||
<action dev="POI-DEVELOPERS" type="fix">46419 - Fixed compatibility issue with OpenOffice 3.0</action> |
@@ -17,6 +17,8 @@ | |||
package org.apache.poi.hssf.record.formula.eval; | |||
import org.apache.poi.ss.util.NumberComparer; | |||
/** | |||
* Base class for all comparison operator evaluators | |||
* | |||
@@ -108,8 +110,7 @@ public abstract class RelationalOperationEval implements OperationEval { | |||
if (vb instanceof NumberEval) { | |||
NumberEval nA = (NumberEval) va; | |||
NumberEval nB = (NumberEval) vb; | |||
// Excel considers -0.0 < 0.0 which is the same as Double.compare() | |||
return Double.compare(nA.getNumberValue(), nB.getNumberValue()); | |||
return NumberComparer.compare(nA.getNumberValue(), nB.getNumberValue()); | |||
} | |||
} | |||
throw new IllegalArgumentException("Bad operand types (" + va.getClass().getName() + "), (" | |||
@@ -126,7 +127,7 @@ public abstract class RelationalOperationEval implements OperationEval { | |||
} | |||
if (v instanceof NumberEval) { | |||
NumberEval ne = (NumberEval) v; | |||
return Double.compare(0, ne.getNumberValue()); | |||
return NumberComparer.compare(0.0, ne.getNumberValue()); | |||
} | |||
if (v instanceof StringEval) { | |||
StringEval se = (StringEval) v; |
@@ -0,0 +1,98 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.util; | |||
import java.math.BigInteger; | |||
import static org.apache.poi.ss.util.IEEEDouble.*; | |||
/** | |||
* Represents a 64 bit IEEE double quantity expressed with both decimal and binary exponents | |||
* Does not handle negative numbers or zero | |||
* <p/> | |||
* The value of a {@link ExpandedDouble} is given by<br/> | |||
* <tt> a × 2<sup>b</sup></tt> | |||
* <br/> | |||
* where:<br/> | |||
* | |||
* <tt>a</tt> = <i>significand</i><br/> | |||
* <tt>b</tt> = <i>binaryExponent</i> - bitLength(significand) + 1<br/> | |||
* | |||
* @author Josh Micich | |||
*/ | |||
final class ExpandedDouble { | |||
private static final BigInteger BI_FRAC_MASK = BigInteger.valueOf(FRAC_MASK); | |||
private static final BigInteger BI_IMPLIED_FRAC_MSB = BigInteger.valueOf(FRAC_ASSUMED_HIGH_BIT); | |||
private static BigInteger getFrac(long rawBits) { | |||
return BigInteger.valueOf(rawBits).and(BI_FRAC_MASK).or(BI_IMPLIED_FRAC_MSB).shiftLeft(11); | |||
} | |||
public static ExpandedDouble fromRawBitsAndExponent(long rawBits, int exp) { | |||
return new ExpandedDouble(getFrac(rawBits), exp); | |||
} | |||
/** | |||
* Always 64 bits long (MSB, bit-63 is '1') | |||
*/ | |||
private final BigInteger _significand; | |||
private final int _binaryExponent; | |||
public ExpandedDouble(long rawBits) { | |||
int biasedExp = (int) (rawBits >> 52); | |||
if (biasedExp == 0) { | |||
// sub-normal numbers | |||
BigInteger frac = BigInteger.valueOf(rawBits).and(BI_FRAC_MASK); | |||
int expAdj = 64 - frac.bitLength(); | |||
_significand = frac.shiftLeft(expAdj); | |||
_binaryExponent = (biasedExp & 0x07FF) - 1023 - expAdj; | |||
} else { | |||
BigInteger frac = getFrac(rawBits); | |||
_significand = frac; | |||
_binaryExponent = (biasedExp & 0x07FF) - 1023; | |||
} | |||
} | |||
ExpandedDouble(BigInteger frac, int binaryExp) { | |||
if (frac.bitLength() != 64) { | |||
throw new IllegalArgumentException("bad bit length"); | |||
} | |||
_significand = frac; | |||
_binaryExponent = binaryExp; | |||
} | |||
/** | |||
* Convert to an equivalent {@link NormalisedDecimal} representation having 15 decimal digits of precision in the | |||
* non-fractional bits of the significand. | |||
*/ | |||
public NormalisedDecimal normaliseBaseTen() { | |||
return NormalisedDecimal.create(_significand, _binaryExponent); | |||
} | |||
/** | |||
* @return the number of non-fractional bits after the MSB of the significand | |||
*/ | |||
public int getBinaryExponent() { | |||
return _binaryExponent; | |||
} | |||
public BigInteger getSignificand() { | |||
return _significand; | |||
} | |||
} |
@@ -0,0 +1,44 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.util; | |||
/** | |||
* For working with the internals of IEEE 754-2008 'binary64' (double precision) floating point numbers | |||
* | |||
* @author Josh Micich | |||
*/ | |||
final class IEEEDouble { | |||
private static final long EXPONENT_MASK = 0x7FF0000000000000L; | |||
private static final int EXPONENT_SHIFT = 52; | |||
public static final long FRAC_MASK = 0x000FFFFFFFFFFFFFL; | |||
public static final int EXPONENT_BIAS = 1023; | |||
public static final long FRAC_ASSUMED_HIGH_BIT = ( 1L<<EXPONENT_SHIFT ); | |||
/** | |||
* The value the exponent field gets for all <i>NaN</i> and <i>Infinity</i> values | |||
*/ | |||
public static final int BIASED_EXPONENT_SPECIAL_VALUE = 0x07FF; | |||
/** | |||
* @param rawBits the 64 bit binary representation of the double value | |||
* @return the top 12 bits (sign and biased exponent value) | |||
*/ | |||
public static int getBiasedExponent(long rawBits) { | |||
return (int) ((rawBits & EXPONENT_MASK) >> EXPONENT_SHIFT); | |||
} | |||
} |
@@ -0,0 +1,209 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.util; | |||
import java.math.BigInteger; | |||
final class MutableFPNumber { | |||
// TODO - what about values between (10<sup>14</sup>-0.5) and (10<sup>14</sup>-0.05) ? | |||
/** | |||
* The minimum value in 'Base-10 normalised form'.<br/> | |||
* When {@link #_binaryExponent} == 46 this is the the minimum {@link #_frac} value | |||
* (10<sup>14</sup>-0.05) * 2^17 | |||
* <br/> | |||
* Values between (10<sup>14</sup>-0.05) and 10<sup>14</sup> will be represented as '1' | |||
* followed by 14 zeros. | |||
* Values less than (10<sup>14</sup>-0.05) will get shifted by one more power of 10 | |||
* | |||
* This frac value rounds to '1' followed by fourteen zeros with an incremented decimal exponent | |||
*/ | |||
private static final BigInteger BI_MIN_BASE = new BigInteger("0B5E620F47FFFE666", 16); | |||
/** | |||
* For 'Base-10 normalised form'<br/> | |||
* The maximum {@link #_frac} value when {@link #_binaryExponent} == 49 | |||
* (10^15-0.5) * 2^14 | |||
*/ | |||
private static final BigInteger BI_MAX_BASE = new BigInteger("0E35FA9319FFFE000", 16); | |||
/** | |||
* Width of a long | |||
*/ | |||
private static final int C_64 = 64; | |||
/** | |||
* Minimum precision after discarding whole 32-bit words from the significand | |||
*/ | |||
private static final int MIN_PRECISION = 72; | |||
private BigInteger _significand; | |||
private int _binaryExponent; | |||
public MutableFPNumber(BigInteger frac, int binaryExponent) { | |||
_significand = frac; | |||
_binaryExponent = binaryExponent; | |||
} | |||
public MutableFPNumber copy() { | |||
return new MutableFPNumber(_significand, _binaryExponent); | |||
} | |||
public void normalise64bit() { | |||
int oldBitLen = _significand.bitLength(); | |||
int sc = oldBitLen - C_64; | |||
if (sc == 0) { | |||
return; | |||
} | |||
if (sc < 0) { | |||
throw new IllegalStateException("Not enough precision"); | |||
} | |||
_binaryExponent += sc; | |||
if (sc > 32) { | |||
int highShift = (sc-1) & 0xFFFFE0; | |||
_significand = _significand.shiftRight(highShift); | |||
sc -= highShift; | |||
oldBitLen -= highShift; | |||
} | |||
if (sc < 1) { | |||
throw new IllegalStateException(); | |||
} | |||
_significand = Rounder.round(_significand, sc); | |||
if (_significand.bitLength() > oldBitLen) { | |||
sc++; | |||
_binaryExponent++; | |||
} | |||
_significand = _significand.shiftRight(sc); | |||
} | |||
public int get64BitNormalisedExponent() { | |||
return _binaryExponent + _significand.bitLength() - C_64; | |||
} | |||
@Override | |||
public boolean equals(Object obj) { | |||
MutableFPNumber other = (MutableFPNumber) obj; | |||
if (_binaryExponent != other._binaryExponent) { | |||
return false; | |||
} | |||
return _significand.equals(other._significand); | |||
} | |||
public boolean isBelowMaxRep() { | |||
int sc = _significand.bitLength() - C_64; | |||
return _significand.compareTo(BI_MAX_BASE.shiftLeft(sc)) < 0; | |||
} | |||
public boolean isAboveMinRep() { | |||
int sc = _significand.bitLength() - C_64; | |||
return _significand.compareTo(BI_MIN_BASE.shiftLeft(sc)) > 0; | |||
} | |||
public NormalisedDecimal createNormalisedDecimal(int pow10) { | |||
// missingUnderBits is (0..3) | |||
int missingUnderBits = _binaryExponent-39; | |||
int fracPart = (_significand.intValue() << missingUnderBits) & 0xFFFF80; | |||
long wholePart = _significand.shiftRight(C_64-_binaryExponent-1).longValue(); | |||
return new NormalisedDecimal(wholePart, fracPart, pow10); | |||
} | |||
public void multiplyByPowerOfTen(int pow10) { | |||
TenPower tp = TenPower.getInstance(Math.abs(pow10)); | |||
if (pow10 < 0) { | |||
mulShift(tp._divisor, tp._divisorShift); | |||
} else { | |||
mulShift(tp._multiplicand, tp._multiplierShift); | |||
} | |||
} | |||
private void mulShift(BigInteger multiplicand, int multiplierShift) { | |||
_significand = _significand.multiply(multiplicand); | |||
_binaryExponent += multiplierShift; | |||
// check for too much precision | |||
int sc = (_significand.bitLength() - MIN_PRECISION) & 0xFFFFFFE0; | |||
// mask makes multiples of 32 which optimises BigInteger.shiftRight | |||
if (sc > 0) { | |||
// no need to round because we have at least 8 bits of extra precision | |||
_significand = _significand.shiftRight(sc); | |||
_binaryExponent += sc; | |||
} | |||
} | |||
private static final class Rounder { | |||
private static final BigInteger[] HALF_BITS; | |||
static { | |||
BigInteger[] bis = new BigInteger[33]; | |||
long acc=1; | |||
for (int i = 1; i < bis.length; i++) { | |||
bis[i] = BigInteger.valueOf(acc); | |||
acc <<=1; | |||
} | |||
HALF_BITS = bis; | |||
} | |||
/** | |||
* @param nBits number of bits to shift right | |||
*/ | |||
public static BigInteger round(BigInteger bi, int nBits) { | |||
if (nBits < 1) { | |||
return bi; | |||
} | |||
return bi.add(HALF_BITS[nBits]); | |||
} | |||
} | |||
/** | |||
* Holds values for quick multiplication and division by 10 | |||
*/ | |||
private static final class TenPower { | |||
private static final BigInteger FIVE = new BigInteger("5"); | |||
private static final TenPower[] _cache = new TenPower[350]; | |||
public final BigInteger _multiplicand; | |||
public final BigInteger _divisor; | |||
public final int _divisorShift; | |||
public final int _multiplierShift; | |||
private TenPower(int index) { | |||
BigInteger fivePowIndex = FIVE.pow(index); | |||
int bitsDueToFiveFactors = fivePowIndex.bitLength(); | |||
int px = 80 + bitsDueToFiveFactors; | |||
BigInteger fx = BigInteger.ONE.shiftLeft(px).divide(fivePowIndex); | |||
int adj = fx.bitLength() - 80; | |||
_divisor = fx.shiftRight(adj); | |||
bitsDueToFiveFactors -= adj; | |||
_divisorShift = -(bitsDueToFiveFactors+index+80); | |||
int sc = fivePowIndex.bitLength() - 68; | |||
if (sc > 0) { | |||
_multiplierShift = index + sc; | |||
_multiplicand = fivePowIndex.shiftRight(sc); | |||
} else { | |||
_multiplierShift = index; | |||
_multiplicand = fivePowIndex; | |||
} | |||
} | |||
static TenPower getInstance(int index) { | |||
TenPower result = _cache[index]; | |||
if (result == null) { | |||
result = new TenPower(index); | |||
_cache[index] = result; | |||
} | |||
return result; | |||
} | |||
} | |||
public ExpandedDouble createExpandedDouble() { | |||
return new ExpandedDouble(_significand, _binaryExponent); | |||
} | |||
} |
@@ -0,0 +1,271 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.util; | |||
import java.math.BigDecimal; | |||
import java.math.BigInteger; | |||
/** | |||
* Represents a transformation of a 64 bit IEEE double quantity having a decimal exponent and a | |||
* fixed point (15 decimal digit) significand. Some quirks of Excel's calculation behaviour are | |||
* simpler to reproduce with numeric quantities in this format. This class is currently used to | |||
* help: | |||
* <ol> | |||
* <li>Comparison operations</li> | |||
* <li>Conversions to text</li> | |||
* </ol> | |||
* | |||
* <p/> | |||
* This class does not handle negative numbers or zero. | |||
* <p/> | |||
* The value of a {@link NormalisedDecimal} is given by<br/> | |||
* <tt> significand × 10<sup>decimalExponent</sup></tt> | |||
* <br/> | |||
* where:<br/> | |||
* | |||
* <tt>significand</tt> = wholePart + fractionalPart / 2<sup>24</sup><br/> | |||
* | |||
* @author Josh Micich | |||
*/ | |||
final class NormalisedDecimal { | |||
/** | |||
* Number of powers of ten contained in the significand | |||
*/ | |||
private static final int EXPONENT_OFFSET = 14; | |||
private static final BigDecimal BD_2_POW_24 = new BigDecimal(BigInteger.ONE.shiftLeft(24)); | |||
/** | |||
* log<sub>10</sub>(2)×2<sup>20</sup> | |||
*/ | |||
private static final int LOG_BASE_10_OF_2_TIMES_2_POW_20 = 315653; // 315652.8287 | |||
/** | |||
* 2<sup>19</sup> | |||
*/ | |||
private static final int C_2_POW_19 = 1 << 19; | |||
/** | |||
* the value of {@link #_fractionalPart} that represents 0.5 | |||
*/ | |||
private static final int FRAC_HALF = 0x800000; | |||
/** | |||
* 10<sup>15</sup> | |||
*/ | |||
private static final long MAX_REP_WHOLE_PART = 0x38D7EA4C68000L; | |||
public static NormalisedDecimal create(BigInteger frac, int binaryExponent) { | |||
// estimate pow2&pow10 first, perform optional mulShift, then normalize | |||
int pow10; | |||
if (binaryExponent > 49 || binaryExponent < 46) { | |||
// working with ints (left shifted 20) instead of doubles | |||
// x = 14.5 - binaryExponent * log10(2); | |||
int x = (29 << 19) - binaryExponent * LOG_BASE_10_OF_2_TIMES_2_POW_20; | |||
x += C_2_POW_19; // round | |||
pow10 = -(x >> 20); | |||
} else { | |||
pow10 = 0; | |||
} | |||
MutableFPNumber cc = new MutableFPNumber(frac, binaryExponent); | |||
if (pow10 != 0) { | |||
cc.multiplyByPowerOfTen(-pow10); | |||
} | |||
switch (cc.get64BitNormalisedExponent()) { | |||
case 46: | |||
if (cc.isAboveMinRep()) { | |||
break; | |||
} | |||
case 44: | |||
case 45: | |||
cc.multiplyByPowerOfTen(1); | |||
pow10--; | |||
break; | |||
case 47: | |||
case 48: | |||
break; | |||
case 49: | |||
if (cc.isBelowMaxRep()) { | |||
break; | |||
} | |||
case 50: | |||
cc.multiplyByPowerOfTen(-1); | |||
pow10++; | |||
break; | |||
default: | |||
throw new IllegalStateException("Bad binary exp " + cc.get64BitNormalisedExponent() + "."); | |||
} | |||
cc.normalise64bit(); | |||
return cc.createNormalisedDecimal(pow10); | |||
} | |||
/** | |||
* Rounds at the digit with value 10<sup>decimalExponent</sup> | |||
*/ | |||
public NormalisedDecimal roundUnits() { | |||
long wholePart = _wholePart; | |||
if (_fractionalPart >= FRAC_HALF) { | |||
wholePart++; | |||
} | |||
int de = _relativeDecimalExponent; | |||
if (wholePart < MAX_REP_WHOLE_PART) { | |||
return new NormalisedDecimal(wholePart, 0, de); | |||
} | |||
return new NormalisedDecimal(wholePart/10, 0, de+1); | |||
} | |||
/** | |||
* The decimal exponent increased by one less than the digit count of {@link #_wholePart} | |||
*/ | |||
private final int _relativeDecimalExponent; | |||
/** | |||
* The whole part of the significand (typically 15 digits). | |||
* | |||
* 47-50 bits long (MSB may be anywhere from bit 46 to 49) | |||
* LSB is units bit. | |||
*/ | |||
private final long _wholePart; | |||
/** | |||
* The fractional part of the significand. | |||
* 24 bits (only top 14-17 bits significant): a value between 0x000000 and 0xFFFF80 | |||
*/ | |||
private final int _fractionalPart; | |||
NormalisedDecimal(long wholePart, int fracPart, int decimalExponent) { | |||
_wholePart = wholePart; | |||
_fractionalPart = fracPart; | |||
_relativeDecimalExponent = decimalExponent; | |||
} | |||
/** | |||
* Convert to an equivalent {@link ExpandedDouble} representation (binary frac and exponent). | |||
* The resulting transformed object is easily converted to a 64 bit IEEE double: | |||
* <ul> | |||
* <li>bits 2-53 of the {@link #getSignificand()} become the 52 bit 'fraction'.</li> | |||
* <li>{@link #getBinaryExponent()} is biased by 1023 to give the 'exponent'.</li> | |||
* </ul> | |||
* The sign bit must be obtained from somewhere else. | |||
* @return a new {@link NormalisedDecimal} normalised to base 2 representation. | |||
*/ | |||
public ExpandedDouble normaliseBaseTwo() { | |||
MutableFPNumber cc = new MutableFPNumber(composeFrac(), 39); | |||
cc.multiplyByPowerOfTen(_relativeDecimalExponent); | |||
cc.normalise64bit(); | |||
return cc.createExpandedDouble(); | |||
} | |||
/** | |||
* @return the significand as a fixed point number (with 24 fraction bits and 47-50 whole bits) | |||
*/ | |||
BigInteger composeFrac() { | |||
long wp = _wholePart; | |||
int fp = _fractionalPart; | |||
return new BigInteger(new byte[] { | |||
(byte) (wp >> 56), // N.B. assuming sign bit is zero | |||
(byte) (wp >> 48), | |||
(byte) (wp >> 40), | |||
(byte) (wp >> 32), | |||
(byte) (wp >> 24), | |||
(byte) (wp >> 16), | |||
(byte) (wp >> 8), | |||
(byte) (wp >> 0), | |||
(byte) (fp >> 16), | |||
(byte) (fp >> 8), | |||
(byte) (fp >> 0), | |||
}); | |||
} | |||
public String getSignificantDecimalDigits() { | |||
return Long.toString(_wholePart); | |||
} | |||
/** | |||
* Rounds the first whole digit position (considers only units digit, not frational part). | |||
* Caller should check total digit count of result to see whether the rounding operation caused | |||
* a carry out of the most significant digit | |||
*/ | |||
public String getSignificantDecimalDigitsLastDigitRounded() { | |||
long wp = _wholePart + 5; // rounds last digit | |||
StringBuilder sb = new StringBuilder(24); | |||
sb.append(wp); | |||
sb.setCharAt(sb.length()-1, '0'); | |||
return sb.toString(); | |||
} | |||
/** | |||
* @return the number of powers of 10 which have been extracted from the significand and binary exponent. | |||
*/ | |||
public int getDecimalExponent() { | |||
return _relativeDecimalExponent+EXPONENT_OFFSET; | |||
} | |||
/** | |||
* assumes both this and other are normalised | |||
*/ | |||
public int compareNormalised(NormalisedDecimal other) { | |||
int cmp = _relativeDecimalExponent - other._relativeDecimalExponent; | |||
if (cmp != 0) { | |||
return cmp; | |||
} | |||
if (_wholePart > other._wholePart) { | |||
return 1; | |||
} | |||
if (_wholePart < other._wholePart) { | |||
return -1; | |||
} | |||
return _fractionalPart - other._fractionalPart; | |||
} | |||
public BigDecimal getFractionalPart() { | |||
return new BigDecimal(_fractionalPart).divide(BD_2_POW_24); | |||
} | |||
private String getFractionalDigits() { | |||
if (_fractionalPart == 0) { | |||
return "0"; | |||
} | |||
return getFractionalPart().toString().substring(2); | |||
} | |||
@Override | |||
public String toString() { | |||
StringBuilder sb = new StringBuilder(); | |||
sb.append(getClass().getName()); | |||
sb.append(" ["); | |||
String ws = String.valueOf(_wholePart); | |||
sb.append(ws.charAt(0)); | |||
sb.append('.'); | |||
sb.append(ws.substring(1)); | |||
sb.append(' '); | |||
sb.append(getFractionalDigits()); | |||
sb.append("E"); | |||
sb.append(getDecimalExponent()); | |||
sb.append("]"); | |||
return sb.toString(); | |||
} | |||
} |
@@ -0,0 +1,173 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.util; | |||
import static org.apache.poi.ss.util.IEEEDouble.*; | |||
/** | |||
* Excel compares numbers using different rules to those of java, so | |||
* {@link Double#compare(double, double)} won't do. | |||
* | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class NumberComparer { | |||
/** | |||
* This class attempts to reproduce Excel's behaviour for comparing numbers. Results are | |||
* mostly the same as those from {@link Double#compare(double, double)} but with some | |||
* rounding. For numbers that are very close, this code converts to a format having 15 | |||
* decimal digits of precision and a decimal exponent, before completing the comparison. | |||
* <p/> | |||
* In Excel formula evaluation, expressions like "(0.06-0.01)=0.05" evaluate to "TRUE" even | |||
* though the equivalent java expression is <code>false</code>. In examples like this, | |||
* Excel achieves the effect by having additional logic for comparison operations. | |||
* <p/> | |||
* <p/> | |||
* Note - Excel also gives special treatment to expressions like "0.06-0.01-0.05" which | |||
* evaluates to "0" (in java, rounding anomalies give a result of 6.9E-18). The special | |||
* behaviour here is for different reasons to the example above: If the last operator in a | |||
* cell formula is '+' or '-' and the result is less than 2<sup>50</sup> times smaller than | |||
* first operand, the result is rounded to zero. | |||
* Needless to say, the two rules are not consistent and it is relatively easy to find | |||
* examples that satisfy<br/> | |||
* "A=B" is "TRUE" but "A-B" is not "0"<br/> | |||
* and<br/> | |||
* "A=B" is "FALSE" but "A-B" is "0"<br/> | |||
* <br/> | |||
* This rule (for rounding the result of a final addition or subtraction), has not been | |||
* implemented in POI (as of Jul-2009). | |||
* | |||
* @return <code>negative, 0, or positive</code> according to the standard Excel comparison | |||
* of values <tt>a</tt> and <tt>b</tt>. | |||
*/ | |||
public static int compare(double a, double b) { | |||
long rawBitsA = Double.doubleToLongBits(a); | |||
long rawBitsB = Double.doubleToLongBits(b); | |||
int biasedExponentA = getBiasedExponent(rawBitsA); | |||
int biasedExponentB = getBiasedExponent(rawBitsB); | |||
if (biasedExponentA == BIASED_EXPONENT_SPECIAL_VALUE) { | |||
throw new IllegalArgumentException("Special double values are not allowed: " + toHex(a)); | |||
} | |||
if (biasedExponentB == BIASED_EXPONENT_SPECIAL_VALUE) { | |||
throw new IllegalArgumentException("Special double values are not allowed: " + toHex(a)); | |||
} | |||
int cmp; | |||
// sign bit is in the same place for long and double: | |||
boolean aIsNegative = rawBitsA < 0; | |||
boolean bIsNegative = rawBitsB < 0; | |||
// compare signs | |||
if (aIsNegative != bIsNegative) { | |||
// Excel seems to have 'normal' comparison behaviour around zero (no rounding) | |||
// even -0.0 < +0.0 (which is not quite the initial conclusion of bug 47198) | |||
return aIsNegative ? -1 : +1; | |||
} | |||
// then compare magnitudes (IEEE 754 has exponent bias specifically to allow this) | |||
cmp = biasedExponentA - biasedExponentB; | |||
int absExpDiff = Math.abs(cmp); | |||
if (absExpDiff > 1) { | |||
return aIsNegative ? -cmp : cmp; | |||
} | |||
if (absExpDiff == 1) { | |||
// special case exponent differs by 1. There is still a chance that with rounding the two quantities could end up the same | |||
} else { | |||
// else - sign and exponents equal | |||
if (rawBitsA == rawBitsB) { | |||
// fully equal - exit here | |||
return 0; | |||
} | |||
} | |||
if (biasedExponentA == 0) { | |||
if (biasedExponentB == 0) { | |||
return compareSubnormalNumbers(rawBitsA & FRAC_MASK, rawBitsB & FRAC_MASK, aIsNegative); | |||
} | |||
// else biasedExponentB is 1 | |||
return -compareAcrossSubnormalThreshold(rawBitsB, rawBitsA, aIsNegative); | |||
} | |||
if (biasedExponentB == 0) { | |||
// else biasedExponentA is 1 | |||
return +compareAcrossSubnormalThreshold(rawBitsA, rawBitsB, aIsNegative); | |||
} | |||
// sign and exponents same, but fractional bits are different | |||
ExpandedDouble edA = ExpandedDouble.fromRawBitsAndExponent(rawBitsA, biasedExponentA - EXPONENT_BIAS); | |||
ExpandedDouble edB = ExpandedDouble.fromRawBitsAndExponent(rawBitsB, biasedExponentB - EXPONENT_BIAS); | |||
NormalisedDecimal ndA = edA.normaliseBaseTen().roundUnits(); | |||
NormalisedDecimal ndB = edB.normaliseBaseTen().roundUnits(); | |||
cmp = ndA.compareNormalised(ndB); | |||
if (aIsNegative) { | |||
return -cmp; | |||
} | |||
return cmp; | |||
} | |||
/** | |||
* If both numbers are subnormal, Excel seems to use standard comparison rules | |||
*/ | |||
private static int compareSubnormalNumbers(long fracA, long fracB, boolean isNegative) { | |||
int cmp = fracA > fracB ? +1 : fracA < fracB ? -1 : 0; | |||
return isNegative ? -cmp : cmp; | |||
} | |||
/** | |||
* Usually any normal number is greater (in magnitude) than any subnormal number. | |||
* However there are some anomalous cases around the threshold where Excel produces screwy results | |||
* @param isNegative both values are either negative or positive. This parameter affects the sign of the comparison result | |||
* @return usually <code>isNegative ? -1 : +1</code> | |||
*/ | |||
private static int compareAcrossSubnormalThreshold(long normalRawBitsA, long subnormalRawBitsB, boolean isNegative) { | |||
long fracB = subnormalRawBitsB & FRAC_MASK; | |||
if (fracB == 0) { | |||
// B is zero, so A is definitely greater than B | |||
return isNegative ? -1 : +1; | |||
} | |||
long fracA = normalRawBitsA & FRAC_MASK; | |||
if (fracA <= 0x0000000000000007L && fracB >= 0x000FFFFFFFFFFFFAL) { | |||
// Both A and B close to threshold - weird results | |||
if (fracA == 0x0000000000000007L && fracB == 0x000FFFFFFFFFFFFAL) { | |||
// special case | |||
return 0; | |||
} | |||
// exactly the opposite | |||
return isNegative ? +1 : -1; | |||
} | |||
// else - typical case A and B is not close to threshold | |||
return isNegative ? -1 : +1; | |||
} | |||
/** | |||
* for formatting double values in error messages | |||
*/ | |||
private static String toHex(double a) { | |||
return "0x" + Long.toHexString(Double.doubleToLongBits(a)).toUpperCase(); | |||
} | |||
} |
@@ -17,8 +17,6 @@ | |||
package org.apache.poi.ss.util; | |||
import java.math.BigDecimal; | |||
import java.math.BigInteger; | |||
/** | |||
* Excel converts numbers to text with different rules to those of java, so | |||
@@ -113,21 +111,9 @@ import java.math.BigInteger; | |||
*/ | |||
public final class NumberToTextConverter { | |||
private static final long expMask = 0x7FF0000000000000L; | |||
private static final long FRAC_MASK= 0x000FFFFFFFFFFFFFL; | |||
private static final int EXPONENT_SHIFT = 52; | |||
private static final int FRAC_BITS_WIDTH = EXPONENT_SHIFT; | |||
private static final int EXPONENT_BIAS = 1023; | |||
private static final long FRAC_ASSUMED_HIGH_BIT = ( 1L<<EXPONENT_SHIFT ); | |||
private static final long EXCEL_NAN_BITS = 0xFFFF0420003C0000L; | |||
private static final int MAX_TEXT_LEN = 20; | |||
private static final int DEFAULT_COUNT_SIGNIFICANT_DIGITS = 15; | |||
private static final int MAX_EXTRA_ZEROS = MAX_TEXT_LEN - DEFAULT_COUNT_SIGNIFICANT_DIGITS; | |||
private static final float LOG2_10 = 3.32F; | |||
private NumberToTextConverter() { | |||
// no instances of this class | |||
} | |||
@@ -149,186 +135,110 @@ public final class NumberToTextConverter { | |||
if (isNegative) { | |||
rawBits &= 0x7FFFFFFFFFFFFFFFL; | |||
} | |||
int biasedExponent = (int) ((rawBits & expMask) >> EXPONENT_SHIFT); | |||
if (biasedExponent == 0) { | |||
if (rawBits == 0) { | |||
return isNegative ? "-0" : "0"; | |||
} | |||
ExpandedDouble ed = new ExpandedDouble(rawBits); | |||
if (ed.getBinaryExponent() < -1022) { | |||
// value is 'denormalised' which means it is less than 2^-1022 | |||
// excel displays all these numbers as zero, even though calculations work OK | |||
return isNegative ? "-0" : "0"; | |||
} | |||
int exponent = biasedExponent - EXPONENT_BIAS; | |||
long fracBits = FRAC_ASSUMED_HIGH_BIT | rawBits & FRAC_MASK; | |||
// Start by converting double value to BigDecimal | |||
BigDecimal bd; | |||
if (biasedExponent == 0x07FF) { | |||
if (ed.getBinaryExponent() == 1024) { | |||
// Special number NaN /Infinity | |||
// Normally one would not create HybridDecimal objects from these values | |||
// except in these cases Excel really tries to render them as if they were normal numbers | |||
if(rawBits == EXCEL_NAN_BITS) { | |||
return "3.484840871308E+308"; | |||
} | |||
// This is where excel really gets it wrong | |||
// Special numbers like Infinity and Nan are interpreted according to | |||
// Special numbers like Infinity and NaN are interpreted according to | |||
// the standard rules below. | |||
isNegative = false; // except that the sign bit is ignored | |||
} | |||
bd = convertToBigDecimal(exponent, fracBits); | |||
return formatBigInteger(isNegative, bd.unscaledValue(), bd.scale()); | |||
} | |||
private static BigDecimal convertToBigDecimal(int exponent, long fracBits) { | |||
byte[] joob = { | |||
(byte) (fracBits >> 48), | |||
(byte) (fracBits >> 40), | |||
(byte) (fracBits >> 32), | |||
(byte) (fracBits >> 24), | |||
(byte) (fracBits >> 16), | |||
(byte) (fracBits >> 8), | |||
(byte) (fracBits >> 0), | |||
}; | |||
BigInteger bigInt = new BigInteger(joob); | |||
int lastSigBitIndex = exponent-FRAC_BITS_WIDTH; | |||
if(lastSigBitIndex < 0) { | |||
BigInteger shifto = new BigInteger("1").shiftLeft(-lastSigBitIndex); | |||
int scale = 1 -(int) (lastSigBitIndex/LOG2_10); | |||
BigDecimal bd1 = new BigDecimal(bigInt); | |||
BigDecimal bdShifto = new BigDecimal(shifto); | |||
return bd1.divide(bdShifto, scale, BigDecimal.ROUND_HALF_UP); | |||
NormalisedDecimal nd = ed.normaliseBaseTen(); | |||
StringBuilder sb = new StringBuilder(MAX_TEXT_LEN+1); | |||
if (isNegative) { | |||
sb.append('-'); | |||
} | |||
BigInteger sl = bigInt.shiftLeft(lastSigBitIndex); | |||
return new BigDecimal(sl); | |||
convertToText(sb, nd); | |||
return sb.toString(); | |||
} | |||
private static String formatBigInteger(boolean isNegative, BigInteger unscaledValue, int scale) { | |||
if (scale < 0) { | |||
throw new RuntimeException("negative scale"); | |||
} | |||
StringBuffer sb = new StringBuffer(unscaledValue.toString()); | |||
int numberOfLeadingZeros = -1; | |||
int unscaledLength = sb.length(); | |||
if (scale > 0 && scale >= unscaledLength) { | |||
// less than one | |||
numberOfLeadingZeros = scale-unscaledLength; | |||
formatLessThanOne(sb, numberOfLeadingZeros+1); | |||
private static void convertToText(StringBuilder sb, NormalisedDecimal pnd) { | |||
NormalisedDecimal rnd = pnd.roundUnits(); | |||
int decExponent = rnd.getDecimalExponent(); | |||
String decimalDigits; | |||
if (Math.abs(decExponent)>98) { | |||
decimalDigits = rnd.getSignificantDecimalDigitsLastDigitRounded(); | |||
if (decimalDigits.length() == 16) { | |||
// rounding caused carry | |||
decExponent++; | |||
} | |||
} else { | |||
int decimalPointIndex = unscaledLength - scale; | |||
formatGreaterThanOne(sb, decimalPointIndex); | |||
} | |||
if(isNegative) { | |||
sb.insert(0, '-'); | |||
decimalDigits = rnd.getSignificantDecimalDigits(); | |||
} | |||
return sb.toString(); | |||
} | |||
private static int getNumberOfSignificantFiguresDisplayed(int exponent) { | |||
int nLostDigits; // number of significand digits lost due big exponents | |||
if(exponent > 99) { | |||
// any exponent greater than 99 has 3 digits instead of 2 | |||
nLostDigits = 1; | |||
} else if (exponent < -98) { | |||
// For some weird reason on the negative side | |||
// step is occurs from -98 to -99 (not from -99 to -100) | |||
nLostDigits = 1; | |||
int countSigDigits = countSignifantDigits(decimalDigits); | |||
if (decExponent < 0) { | |||
formatLessThanOne(sb, decimalDigits, decExponent, countSigDigits); | |||
} else { | |||
nLostDigits = 0; | |||
formatGreaterThanOne(sb, decimalDigits, decExponent, countSigDigits); | |||
} | |||
return DEFAULT_COUNT_SIGNIFICANT_DIGITS - nLostDigits; | |||
} | |||
private static boolean needsScientificNotation(int nDigits) { | |||
return nDigits > MAX_TEXT_LEN; | |||
} | |||
private static void formatGreaterThanOne(StringBuffer sb, int nIntegerDigits) { | |||
private static void formatLessThanOne(StringBuilder sb, String decimalDigits, int decExponent, | |||
int countSigDigits) { | |||
int nLeadingZeros = -decExponent - 1; | |||
int normalLength = 2 + nLeadingZeros + countSigDigits; // 2 == "0.".length() | |||
int maxSigFigs = getNumberOfSignificantFiguresDisplayed(nIntegerDigits); | |||
int decimalPointIndex = nIntegerDigits; | |||
boolean roundCausedCarry = performRound(sb, 0, maxSigFigs); | |||
int endIx = Math.min(maxSigFigs, sb.length()-1); | |||
int nSigFigures; | |||
if(roundCausedCarry) { | |||
sb.insert(0, '1'); | |||
decimalPointIndex++; | |||
nSigFigures = 1; | |||
} else { | |||
nSigFigures = countSignifantDigits(sb, endIx); | |||
} | |||
if(needsScientificNotation(decimalPointIndex)) { | |||
sb.setLength(nSigFigures); | |||
if (nSigFigures > 1) { | |||
sb.insert(1, '.'); | |||
if (needsScientificNotation(normalLength)) { | |||
sb.append(decimalDigits.charAt(0)); | |||
if (countSigDigits > 1) { | |||
sb.append('.'); | |||
sb.append(decimalDigits.subSequence(1, countSigDigits)); | |||
} | |||
sb.append("E+"); | |||
appendExp(sb, decimalPointIndex-1); | |||
sb.append("E-"); | |||
appendExp(sb, -decExponent); | |||
return; | |||
} | |||
if(isAllZeros(sb, decimalPointIndex, maxSigFigs)) { | |||
sb.setLength(decimalPointIndex); | |||
return; | |||
sb.append("0."); | |||
for (int i=nLeadingZeros; i>0; i--) { | |||
sb.append('0'); | |||
} | |||
// else some sig-digits after the decimal point | |||
sb.setLength(nSigFigures); | |||
sb.insert(decimalPointIndex, '.'); | |||
sb.append(decimalDigits.subSequence(0, countSigDigits)); | |||
} | |||
/** | |||
* @param sb initially contains just the significant digits | |||
* @param pAbsExponent to be inserted (after "0.") at the start of the number | |||
*/ | |||
private static void formatLessThanOne(StringBuffer sb, int pAbsExponent) { | |||
if (sb.charAt(0) == 0) { | |||
throw new IllegalArgumentException("First digit of significand should be non-zero"); | |||
private static void formatGreaterThanOne(StringBuilder sb, String decimalDigits, int decExponent, int countSigDigits) { | |||
if (decExponent > 19) { | |||
// scientific notation | |||
sb.append(decimalDigits.charAt(0)); | |||
if (countSigDigits>1) { | |||
sb.append('.'); | |||
sb.append(decimalDigits.subSequence(1, countSigDigits)); | |||
} | |||
sb.append("E+"); | |||
appendExp(sb, decExponent); | |||
return; | |||
} | |||
if (pAbsExponent < 1) { | |||
throw new IllegalArgumentException("abs(exponent) must be positive"); | |||
int nFractionalDigits = countSigDigits - decExponent-1; | |||
if (nFractionalDigits > 0) { | |||
sb.append(decimalDigits.subSequence(0, decExponent+1)); | |||
sb.append('.'); | |||
sb.append(decimalDigits.subSequence(decExponent+1, countSigDigits)); | |||
return; | |||
} | |||
int numberOfLeadingZeros = pAbsExponent-1; | |||
int absExponent = pAbsExponent; | |||
int maxSigFigs = getNumberOfSignificantFiguresDisplayed(-absExponent); | |||
boolean roundCausedCarry = performRound(sb, 0, maxSigFigs); | |||
int nRemainingSigFigs; | |||
if(roundCausedCarry) { | |||
absExponent--; | |||
numberOfLeadingZeros--; | |||
nRemainingSigFigs = 1; | |||
sb.setLength(0); | |||
sb.append("1"); | |||
} else { | |||
nRemainingSigFigs = countSignifantDigits(sb, 0 + maxSigFigs); | |||
sb.setLength(nRemainingSigFigs); | |||
sb.append(decimalDigits.subSequence(0, countSigDigits)); | |||
for (int i=-nFractionalDigits; i>0; i--) { | |||
sb.append('0'); | |||
} | |||
} | |||
int normalLength = 2 + numberOfLeadingZeros + nRemainingSigFigs; // 2 == "0.".length() | |||
if (needsScientificNotation(normalLength)) { | |||
if (sb.length()>1) { | |||
sb.insert(1, '.'); | |||
} | |||
sb.append('E'); | |||
sb.append('-'); | |||
appendExp(sb, absExponent); | |||
} else { | |||
sb.insert(0, "0."); | |||
for(int i=numberOfLeadingZeros; i>0; i--) { | |||
sb.insert(2, '0'); | |||
} | |||
} | |||
private static boolean needsScientificNotation(int nDigits) { | |||
return nDigits > MAX_TEXT_LEN; | |||
} | |||
private static int countSignifantDigits(StringBuffer sb, int startIx) { | |||
int result=startIx; | |||
private static int countSignifantDigits(String sb) { | |||
int result=sb.length()-1; | |||
while(sb.charAt(result) == '0') { | |||
result--; | |||
if(result < 0) { | |||
@@ -338,68 +248,12 @@ public final class NumberToTextConverter { | |||
return result + 1; | |||
} | |||
private static void appendExp(StringBuffer sb, int val) { | |||
private static void appendExp(StringBuilder sb, int val) { | |||
if(val < 10) { | |||
sb.append('0'); | |||
sb.append((char)('0' + val)); | |||
return; | |||
} | |||
sb.append(val); | |||
} | |||
private static boolean isAllZeros(StringBuffer sb, int startIx, int endIx) { | |||
for(int i=startIx; i<=endIx && i<sb.length(); i++) { | |||
if(sb.charAt(i) != '0') { | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
/** | |||
* @return <code>true</code> if carry (out of the MS digit) occurred | |||
*/ | |||
private static boolean performRound(StringBuffer sb, int firstSigFigIx, int nSigFigs) { | |||
int nextDigitIx = firstSigFigIx + nSigFigs; | |||
if(nextDigitIx == sb.length()) { | |||
return false; // nothing to do - digit to be rounded is at the end of the buffer | |||
} | |||
if(nextDigitIx > sb.length()) { | |||
throw new RuntimeException("Buffer too small to fit all significant digits"); | |||
} | |||
boolean hadCarryOutOfFirstDigit; | |||
if(sb.charAt(nextDigitIx) < '5') { | |||
// change to digit | |||
hadCarryOutOfFirstDigit = false; | |||
} else { | |||
hadCarryOutOfFirstDigit = roundAndCarry(sb, nextDigitIx); | |||
} | |||
// clear out the rest of the digits after the rounded digit | |||
// (at least the nearby digits) | |||
int endIx = Math.min(nextDigitIx + MAX_EXTRA_ZEROS, sb.length()); | |||
for(int i = nextDigitIx; i<endIx; i++) { | |||
sb.setCharAt(i, '0'); | |||
} | |||
return hadCarryOutOfFirstDigit; | |||
} | |||
private static boolean roundAndCarry(StringBuffer sb, int nextDigitIx) { | |||
int changeDigitIx = nextDigitIx - 1; | |||
while(sb.charAt(changeDigitIx) == '9') { | |||
sb.setCharAt(changeDigitIx, '0'); | |||
changeDigitIx--; | |||
// All nines, rounded up. Notify caller | |||
if(changeDigitIx < 0) { | |||
return true; | |||
} | |||
} | |||
// no more '9's to round up. | |||
// Last digit to be changed is still inside sb | |||
char prevDigit = sb.charAt(changeDigitIx); | |||
sb.setCharAt(changeDigitIx, (char) (prevDigit + 1)); | |||
return false; | |||
} | |||
} |
@@ -94,10 +94,10 @@ public final class TestEqualEval extends TestCase { | |||
/** | |||
* Bug 47198 involved a formula "-A1=0" where cell A1 was 0.0. | |||
* Excel evaluates "-A1=0" to TRUE, not because it thinks -0.0==0.0 | |||
* Excel evaluates "-A1=0" to TRUE, not because it thinks -0.0==0.0 | |||
* but because "-A1" evaluated to +0.0 | |||
* <p/> | |||
* Note - the original diagnosis of bug 47198 was that | |||
* Note - the original diagnosis of bug 47198 was that | |||
* "Excel considers -0.0 to be equal to 0.0" which is NQR | |||
* See {@link TestMinusZeroResult} for more specific tests regarding -0.0. | |||
*/ | |||
@@ -114,4 +114,19 @@ public final class TestEqualEval extends TestCase { | |||
throw new AssertionFailedError("Identified bug 47198: -0.0 != 0.0"); | |||
} | |||
} | |||
public void testRounding_bug47598() { | |||
double x = 1+1.0028-0.9973; // should be 1.0055, but has IEEE rounding | |||
assertFalse(x == 1.0055); | |||
NumberEval a = new NumberEval(x); | |||
NumberEval b = new NumberEval(1.0055); | |||
assertEquals("1.0055", b.getStringValue()); | |||
Eval[] args = { a, b, }; | |||
BoolEval result = (BoolEval) EqualEval.instance.evaluate(args, 0, (short) 0); | |||
if (!result.getBooleanValue()) { | |||
throw new AssertionFailedError("Identified bug 47598: 1+1.0028-0.9973 != 1.0055"); | |||
} | |||
} | |||
} |
@@ -21,13 +21,15 @@ import junit.framework.Test; | |||
import junit.framework.TestSuite; | |||
/** | |||
* Test suite for <tt>org.apache.poi.ss.util</tt> | |||
* | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class AllSSUtilTests { | |||
public static Test suite() { | |||
public static Test suite() { | |||
TestSuite result = new TestSuite(AllSSUtilTests.class.getName()); | |||
result.addTestSuite(TestCellReference.class); | |||
result.addTestSuite(TestExpandedDouble.class); | |||
result.addTestSuite(TestNumberComparer.class); | |||
result.addTestSuite(TestNumberToTextConverter.class); | |||
result.addTestSuite(TestRegion.class); | |||
return result; |
@@ -0,0 +1,155 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.util; | |||
import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import org.apache.poi.hssf.usermodel.HSSFCell; | |||
import org.apache.poi.hssf.usermodel.HSSFCellStyle; | |||
import org.apache.poi.hssf.usermodel.HSSFFont; | |||
import org.apache.poi.hssf.usermodel.HSSFRichTextString; | |||
import org.apache.poi.hssf.usermodel.HSSFRow; | |||
import org.apache.poi.hssf.usermodel.HSSFSheet; | |||
import org.apache.poi.hssf.usermodel.HSSFWorkbook; | |||
import org.apache.poi.ss.util.NumberComparisonExamples.ComparisonExample; | |||
import org.apache.poi.util.HexDump; | |||
/** | |||
* Creates a spreadsheet that checks Excel's comparison of various IEEE double values. | |||
* The class {@link NumberComparisonExamples} contains specific comparison examples | |||
* (along with their expected results) that get encoded into rows of the spreadsheet. | |||
* Each example is checked with a formula (in column I) that displays either "OK" or | |||
* "ERROR" depending on whether actual results match those expected. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public class NumberComparingSpreadsheetGenerator { | |||
private static final class SheetWriter { | |||
private final HSSFSheet _sheet; | |||
private int _rowIndex; | |||
public SheetWriter(HSSFWorkbook wb) { | |||
HSSFSheet sheet = wb.createSheet("Sheet1"); | |||
writeHeaderRow(wb, sheet); | |||
_sheet = sheet; | |||
_rowIndex = 1; | |||
} | |||
public void addTestRow(double a, double b, int expResult) { | |||
writeDataRow(_sheet, _rowIndex++, a, b, expResult); | |||
} | |||
} | |||
private static void writeHeaderCell(HSSFRow row, int i, String text, HSSFCellStyle style) { | |||
HSSFCell cell = row.createCell(i); | |||
cell.setCellValue(new HSSFRichTextString(text)); | |||
cell.setCellStyle(style); | |||
} | |||
static void writeHeaderRow(HSSFWorkbook wb, HSSFSheet sheet) { | |||
sheet.setColumnWidth(0, 6000); | |||
sheet.setColumnWidth(1, 6000); | |||
sheet.setColumnWidth(2, 3600); | |||
sheet.setColumnWidth(3, 3600); | |||
sheet.setColumnWidth(4, 2400); | |||
sheet.setColumnWidth(5, 2400); | |||
sheet.setColumnWidth(6, 2400); | |||
sheet.setColumnWidth(7, 2400); | |||
sheet.setColumnWidth(8, 2400); | |||
HSSFRow row = sheet.createRow(0); | |||
HSSFCellStyle style = wb.createCellStyle(); | |||
HSSFFont font = wb.createFont(); | |||
font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); | |||
style.setFont(font); | |||
writeHeaderCell(row, 0, "Raw Long Bits A", style); | |||
writeHeaderCell(row, 1, "Raw Long Bits B", style); | |||
writeHeaderCell(row, 2, "Value A", style); | |||
writeHeaderCell(row, 3, "Value B", style); | |||
writeHeaderCell(row, 4, "Exp Cmp", style); | |||
writeHeaderCell(row, 5, "LT", style); | |||
writeHeaderCell(row, 6, "EQ", style); | |||
writeHeaderCell(row, 7, "GT", style); | |||
writeHeaderCell(row, 8, "Check", style); | |||
} | |||
/** | |||
* Fills a spreadsheet row with one comparison example. The two numeric values are written to | |||
* columns C and D. Columns (F, G and H) respectively get formulas ("v0<v1", "v0=v1", "v0>v1"), | |||
* which will be evaluated by Excel. Column D gets the expected comparison result. Column I | |||
* gets a formula to check that Excel's comparison results match that predicted in column D. | |||
* | |||
* @param v0 the first value to be compared | |||
* @param v1 the second value to be compared | |||
* @param expRes expected comparison result (-1, 0, or +1) | |||
*/ | |||
static void writeDataRow(HSSFSheet sheet, int rowIx, double v0, double v1, int expRes) { | |||
HSSFRow row = sheet.createRow(rowIx); | |||
int rowNum = rowIx + 1; | |||
row.createCell(0).setCellValue(formatDoubleAsHex(v0)); | |||
row.createCell(1).setCellValue(formatDoubleAsHex(v1)); | |||
row.createCell(2).setCellValue(v0); | |||
row.createCell(3).setCellValue(v1); | |||
row.createCell(4).setCellValue(expRes < 0 ? "LT" : expRes > 0 ? "GT" : "EQ"); | |||
row.createCell(5).setCellFormula("C" + rowNum + "<" + "D" + rowNum); | |||
row.createCell(6).setCellFormula("C" + rowNum + "=" + "D" + rowNum); | |||
row.createCell(7).setCellFormula("C" + rowNum + ">" + "D" + rowNum); | |||
// TODO - bug elsewhere in POI - something wrong with encoding of NOT() function | |||
String frm = "if(or(" + | |||
"and(E#='LT', F# , G#=FALSE, H#=FALSE)," + | |||
"and(E#='EQ', F#=FALSE, G# , H#=FALSE)," + | |||
"and(E#='GT', F#=FALSE, G#=FALSE, H# )" + | |||
"), 'OK', 'error')" ; | |||
row.createCell(8).setCellFormula(frm.replaceAll("#", String.valueOf(rowNum)).replace('\'', '"')); | |||
} | |||
private static String formatDoubleAsHex(double d) { | |||
long l = Double.doubleToLongBits(d); | |||
StringBuilder sb = new StringBuilder(20); | |||
sb.append(HexDump.longToHex(l)).append('L'); | |||
return sb.toString(); | |||
} | |||
public static void main(String[] args) { | |||
HSSFWorkbook wb = new HSSFWorkbook(); | |||
SheetWriter sw = new SheetWriter(wb); | |||
ComparisonExample[] ces = NumberComparisonExamples.getComparisonExamples(); | |||
for (int i = 0; i < ces.length; i++) { | |||
ComparisonExample ce = ces[i]; | |||
sw.addTestRow(ce.getA(), ce.getB(), ce.getExpectedResult()); | |||
} | |||
File outputFile = new File("ExcelNumberCompare.xls"); | |||
try { | |||
FileOutputStream os = new FileOutputStream(outputFile); | |||
wb.write(os); | |||
os.close(); | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
System.out.println("Finished writing '" + outputFile.getAbsolutePath() + "'"); | |||
} | |||
} |
@@ -0,0 +1,182 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.util; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
/** | |||
* Contains specific examples of <tt>double</tt> value pairs and their comparison result according to Excel. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
final class NumberComparisonExamples { | |||
private NumberComparisonExamples() { | |||
// no instances of this class | |||
} | |||
/** | |||
* represents one comparison test case | |||
*/ | |||
public static final class ComparisonExample { | |||
private final long _rawBitsA; | |||
private final long _rawBitsB; | |||
private final int _expectedResult; | |||
public ComparisonExample(long rawBitsA, long rawBitsB, int expectedResult) { | |||
_rawBitsA = rawBitsA; | |||
_rawBitsB = rawBitsB; | |||
_expectedResult = expectedResult; | |||
} | |||
public double getA() { | |||
return Double.longBitsToDouble(_rawBitsA); | |||
} | |||
public double getB() { | |||
return Double.longBitsToDouble(_rawBitsB); | |||
} | |||
public double getNegA() { | |||
return -Double.longBitsToDouble(_rawBitsA); | |||
} | |||
public double getNegB() { | |||
return -Double.longBitsToDouble(_rawBitsB); | |||
} | |||
public int getExpectedResult() { | |||
return _expectedResult; | |||
} | |||
} | |||
private static final ComparisonExample[] examples = initExamples(); | |||
private static ComparisonExample[] initExamples() { | |||
List<ComparisonExample> temp = new ArrayList<ComparisonExample>(); | |||
addStepTransition(temp, 0x4010000000000005L); | |||
addStepTransition(temp, 0x4010000000000010L); | |||
addStepTransition(temp, 0x401000000000001CL); | |||
addStepTransition(temp, 0x403CE0FFFFFFFFF1L); | |||
addStepTransition(temp, 0x5010000000000006L); | |||
addStepTransition(temp, 0x5010000000000010L); | |||
addStepTransition(temp, 0x501000000000001AL); | |||
addStepTransition(temp, 0x544CE6345CF32018L); | |||
addStepTransition(temp, 0x544CE6345CF3205AL); | |||
addStepTransition(temp, 0x544CE6345CF3209CL); | |||
addStepTransition(temp, 0x544CE6345CF320DEL); | |||
addStepTransition(temp, 0x54B250001000101DL); | |||
addStepTransition(temp, 0x54B2500010001050L); | |||
addStepTransition(temp, 0x54B2500010001083L); | |||
addStepTransition(temp, 0x6230100010001000L); | |||
addStepTransition(temp, 0x6230100010001005L); | |||
addStepTransition(temp, 0x623010001000100AL); | |||
addStepTransition(temp, 0x7F50300020001011L); | |||
addStepTransition(temp, 0x7F5030002000102BL); | |||
addStepTransition(temp, 0x7F50300020001044L); | |||
addStepTransition(temp, 0x2B2BFFFF1000102AL); | |||
addStepTransition(temp, 0x2B2BFFFF10001079L); | |||
addStepTransition(temp, 0x2B2BFFFF100010C8L); | |||
addStepTransition(temp, 0x2B2BFF001000102DL); | |||
addStepTransition(temp, 0x2B2BFF0010001035L); | |||
addStepTransition(temp, 0x2B2BFF001000103DL); | |||
addStepTransition(temp, 0x2B61800040002024L); | |||
addStepTransition(temp, 0x2B61800040002055L); | |||
addStepTransition(temp, 0x2B61800040002086L); | |||
addStepTransition(temp, 0x008000000000000BL); | |||
// just outside 'subnormal' range | |||
addStepTransition(temp, 0x0010000000000007L); | |||
addStepTransition(temp, 0x001000000000001BL); | |||
addStepTransition(temp, 0x001000000000002FL); | |||
for(ComparisonExample ce : new ComparisonExample[] { | |||
// negative, and exponents differ by more than 1 | |||
ce(0xBF30000000000000L, 0xBE60000000000000L, -1), | |||
// negative zero *is* less than positive zero, but not easy to get out of calculations | |||
ce(0x0000000000000000L, 0x8000000000000000L, +1), | |||
// subnormal numbers compare without rounding for some reason | |||
ce(0x0000000000000000L, 0x0000000000000001L, -1), | |||
ce(0x0008000000000000L, 0x0008000000000001L, -1), | |||
ce(0x000FFFFFFFFFFFFFL, 0x000FFFFFFFFFFFFEL, +1), | |||
ce(0x000FFFFFFFFFFFFBL, 0x000FFFFFFFFFFFFCL, -1), | |||
ce(0x000FFFFFFFFFFFFBL, 0x000FFFFFFFFFFFFEL, -1), | |||
// across subnormal threshold (some mistakes when close) | |||
ce(0x000FFFFFFFFFFFFFL, 0x0010000000000000L, +1), | |||
ce(0x000FFFFFFFFFFFFBL, 0x0010000000000007L, +1), | |||
ce(0x000FFFFFFFFFFFFAL, 0x0010000000000007L, 0), | |||
// when a bit further apart - normal results | |||
ce(0x000FFFFFFFFFFFF9L, 0x0010000000000007L, -1), | |||
ce(0x000FFFFFFFFFFFFAL, 0x0010000000000008L, -1), | |||
ce(0x000FFFFFFFFFFFFBL, 0x0010000000000008L, -1), | |||
}) { | |||
temp.add(ce); | |||
} | |||
ComparisonExample[] result = new ComparisonExample[temp.size()]; | |||
temp.toArray(result); | |||
return result; | |||
} | |||
private static ComparisonExample ce(long rawBitsA, long rawBitsB, int expectedResult) { | |||
return new ComparisonExample(rawBitsA, rawBitsB, expectedResult); | |||
} | |||
private static void addStepTransition(List<ComparisonExample> temp, long rawBits) { | |||
for(ComparisonExample ce : new ComparisonExample[] { | |||
ce(rawBits-1, rawBits+0, 0), | |||
ce(rawBits+0, rawBits+1, -1), | |||
ce(rawBits+1, rawBits+2, 0), | |||
}) { | |||
temp.add(ce); | |||
} | |||
} | |||
public static ComparisonExample[] getComparisonExamples() { | |||
return examples.clone(); | |||
} | |||
public static ComparisonExample[] getComparisonExamples2() { | |||
ComparisonExample[] result = examples.clone(); | |||
for (int i = 0; i < result.length; i++) { | |||
int ha = ("a"+i).hashCode(); | |||
double a = ha * Math.pow(0.75, ha % 100); | |||
int hb = ("b"+i).hashCode(); | |||
double b = hb * Math.pow(0.75, hb % 100); | |||
result[i] = new ComparisonExample(Double.doubleToLongBits(a), Double.doubleToLongBits(b), Double.compare(a, b)); | |||
} | |||
return result; | |||
} | |||
} |
@@ -95,17 +95,17 @@ final class NumberToTextConversionExamples { | |||
ec(0x4087A00000000000L, "756.0", "756"), | |||
ec(0x401E3D70A3D70A3DL, "7.56", "7.56"), | |||
// ec(0x405EDD3C07FB4C8CL, "123.4567890123455", "123.456789012345"), | |||
ec(0x405EDD3C07FB4C8CL, "123.4567890123455", "123.456789012345"), | |||
ec(0x405EDD3C07FB4C99L, "123.45678901234568", "123.456789012346"), | |||
ec(0x405EDD3C07FB4CAEL, "123.45678901234598", "123.456789012346"), | |||
ec(0x4132D687E3DF2180L, "1234567.8901234567", "1234567.89012346"), | |||
// ec(0x3F543A272D9E0E49L, "0.001234567890123455", "0.00123456789012345"), | |||
ec(0x3F543A272D9E0E49L, "0.001234567890123455", "0.00123456789012345"), | |||
ec(0x3F543A272D9E0E4AL, "0.0012345678901234552", "0.00123456789012346"), | |||
ec(0x3F543A272D9E0E55L, "0.0012345678901234576", "0.00123456789012346"), | |||
ec(0x3F543A272D9E0E72L, "0.0012345678901234639", "0.00123456789012346"), | |||
ec(0x3F543A272D9E0E76L, "0.0012345678901234647", "0.00123456789012346"), | |||
// ec(0x3F543A272D9E0E77L, "0.001234567890123465", "0.00123456789012346"), | |||
ec(0x3F543A272D9E0E77L, "0.001234567890123465", "0.00123456789012346"), | |||
ec(0x3F543A272D9E0E78L, "0.0012345678901234652", "0.00123456789012347"), | |||
@@ -121,11 +121,11 @@ final class NumberToTextConversionExamples { | |||
ec(0x544CE6345CF32121L, "1.2345678901234751E98", "1.23456789012348E+98"), | |||
// ec(0x54820FE0BA17F5E9L, "1.23456789012355E99", "1.2345678901236E+99"), | |||
ec(0x54820FE0BA17F5E9L, "1.23456789012355E99", "1.2345678901236E+99"), | |||
ec(0x54820FE0BA17F5EAL, "1.2345678901235502E99", "1.2345678901236E+99"), | |||
// ec(0x54820FE0BA17F784L, "1.2345678901236498E99", "1.2345678901237E+99"), | |||
ec(0x54820FE0BA17F784L, "1.2345678901236498E99", "1.2345678901237E+99"), | |||
ec(0x54820FE0BA17F785L, "1.23456789012365E99", "1.2345678901237E+99"), | |||
// ec(0x54820FE0BA17F920L, "1.2345678901237498E99", "1.2345678901238E+99"), | |||
ec(0x54820FE0BA17F920L, "1.2345678901237498E99", "1.2345678901238E+99"), | |||
ec(0x54820FE0BA17F921L, "1.23456789012375E99", "1.2345678901238E+99"), | |||
@@ -137,52 +137,52 @@ final class NumberToTextConversionExamples { | |||
ec(0x547D42AEA2879F2AL,"9.999999999999995E98", "9.99999999999999E+98"), | |||
ec(0x547D42AEA2879F2BL,"9.999999999999996E98", "1E+99"), | |||
ec(0x547D42AEA287A0A0L,"1.0000000000000449E99", "1E+99"), | |||
// ec(0x547D42AEA287A0A1L,"1.000000000000045E99", "1.0000000000001E+99"), | |||
ec(0x547D42AEA287A0A1L,"1.000000000000045E99", "1.0000000000001E+99"), | |||
ec(0x547D42AEA287A3D8L,"1.0000000000001449E99", "1.0000000000001E+99"), | |||
// ec(0x547D42AEA287A3D9L,"1.000000000000145E99", "1.0000000000002E+99"), | |||
ec(0x547D42AEA287A3D9L,"1.000000000000145E99", "1.0000000000002E+99"), | |||
ec(0x547D42AEA287A710L,"1.000000000000245E99", "1.0000000000002E+99"), | |||
// ec(0x547D42AEA287A711L,"1.0000000000002451E99", "1.0000000000003E+99"), | |||
ec(0x547D42AEA287A711L,"1.0000000000002451E99", "1.0000000000003E+99"), | |||
ec(0x54B249AD2594C2F9L,"9.999999999999744E99", "9.9999999999997E+99"), | |||
// ec(0x54B249AD2594C2FAL,"9.999999999999746E99", "9.9999999999998E+99"), | |||
ec(0x54B249AD2594C2FAL,"9.999999999999746E99", "9.9999999999998E+99"), | |||
ec(0x54B249AD2594C32DL,"9.999999999999845E99", "9.9999999999998E+99"), | |||
// ec(0x54B249AD2594C32EL,"9.999999999999847E99", "9.9999999999999E+99"), | |||
ec(0x54B249AD2594C32EL,"9.999999999999847E99", "9.9999999999999E+99"), | |||
ec(0x54B249AD2594C360L,"9.999999999999944E99", "9.9999999999999E+99"), | |||
// ec(0x54B249AD2594C361L,"9.999999999999946E99", "1E+100"), | |||
ec(0x54B249AD2594C361L,"9.999999999999946E99", "1E+100"), | |||
ec(0x54B249AD2594C464L,"1.0000000000000449E100","1E+100"), | |||
// ec(0x54B249AD2594C465L,"1.000000000000045E100", "1.0000000000001E+100"), | |||
ec(0x54B249AD2594C465L,"1.000000000000045E100", "1.0000000000001E+100"), | |||
ec(0x54B249AD2594C667L,"1.000000000000145E100", "1.0000000000001E+100"), | |||
// ec(0x54B249AD2594C668L,"1.0000000000001451E100","1.0000000000002E+100"), | |||
ec(0x54B249AD2594C668L,"1.0000000000001451E100","1.0000000000002E+100"), | |||
ec(0x54B249AD2594C86AL,"1.000000000000245E100", "1.0000000000002E+100"), | |||
// ec(0x54B249AD2594C86BL,"1.0000000000002452E100","1.0000000000003E+100"), | |||
ec(0x54B249AD2594C86BL,"1.0000000000002452E100","1.0000000000003E+100"), | |||
ec(0x2B95DF5CA28EF4A8L,"1.0000000000000251E-98","1.00000000000003E-98"), | |||
// ec(0x2B95DF5CA28EF4A7L,"1.000000000000025E-98", "1.00000000000002E-98"), | |||
ec(0x2B95DF5CA28EF4A7L,"1.000000000000025E-98", "1.00000000000002E-98"), | |||
ec(0x2B95DF5CA28EF46AL,"1.000000000000015E-98", "1.00000000000002E-98"), | |||
ec(0x2B95DF5CA28EF469L,"1.0000000000000149E-98","1.00000000000001E-98"), | |||
ec(0x2B95DF5CA28EF42DL,"1.0000000000000051E-98","1.00000000000001E-98"), | |||
// ec(0x2B95DF5CA28EF42CL,"1.000000000000005E-98", "1E-98"), | |||
// ec(0x2B95DF5CA28EF3ECL,"9.999999999999946E-99", "1E-98"), | |||
ec(0x2B95DF5CA28EF42CL,"1.000000000000005E-98", "1E-98"), | |||
ec(0x2B95DF5CA28EF3ECL,"9.999999999999946E-99", "1E-98"), | |||
ec(0x2B95DF5CA28EF3EBL,"9.999999999999944E-99", "9.9999999999999E-99"), | |||
// ec(0x2B95DF5CA28EF3AEL,"9.999999999999845E-99", "9.9999999999999E-99"), | |||
ec(0x2B95DF5CA28EF3AEL,"9.999999999999845E-99", "9.9999999999999E-99"), | |||
ec(0x2B95DF5CA28EF3ADL,"9.999999999999843E-99", "9.9999999999998E-99"), | |||
// ec(0x2B95DF5CA28EF371L,"9.999999999999746E-99", "9.9999999999998E-99"), | |||
ec(0x2B95DF5CA28EF371L,"9.999999999999746E-99", "9.9999999999998E-99"), | |||
ec(0x2B95DF5CA28EF370L,"9.999999999999744E-99", "9.9999999999997E-99"), | |||
// ec(0x2B617F7D4ED8C7F5L,"1.000000000000245E-99", "1.0000000000003E-99"), | |||
ec(0x2B617F7D4ED8C7F5L,"1.000000000000245E-99", "1.0000000000003E-99"), | |||
ec(0x2B617F7D4ED8C7F4L,"1.0000000000002449E-99","1.0000000000002E-99"), | |||
// ec(0x2B617F7D4ED8C609L,"1.0000000000001452E-99","1.0000000000002E-99"), | |||
ec(0x2B617F7D4ED8C609L,"1.0000000000001452E-99","1.0000000000002E-99"), | |||
ec(0x2B617F7D4ED8C608L,"1.000000000000145E-99", "1.0000000000001E-99"), | |||
// ec(0x2B617F7D4ED8C41CL,"1.000000000000045E-99", "1.0000000000001E-99"), | |||
ec(0x2B617F7D4ED8C41CL,"1.000000000000045E-99", "1.0000000000001E-99"), | |||
ec(0x2B617F7D4ED8C41BL,"1.0000000000000449E-99","1E-99"), | |||
// ec(0x2B617F7D4ED8C323L,"9.999999999999945E-100","1E-99"), | |||
ec(0x2B617F7D4ED8C323L,"9.999999999999945E-100","1E-99"), | |||
ec(0x2B617F7D4ED8C322L,"9.999999999999943E-100","9.9999999999999E-100"), | |||
// ec(0x2B617F7D4ED8C2F2L,"9.999999999999846E-100","9.9999999999999E-100"), | |||
ec(0x2B617F7D4ED8C2F2L,"9.999999999999846E-100","9.9999999999999E-100"), | |||
ec(0x2B617F7D4ED8C2F1L,"9.999999999999844E-100","9.9999999999998E-100"), | |||
// ec(0x2B617F7D4ED8C2C1L,"9.999999999999746E-100","9.9999999999998E-100"), | |||
ec(0x2B617F7D4ED8C2C1L,"9.999999999999746E-100","9.9999999999998E-100"), | |||
ec(0x2B617F7D4ED8C2C0L,"9.999999999999744E-100","9.9999999999997E-100"), | |||
@@ -0,0 +1,225 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.util; | |||
import java.math.BigDecimal; | |||
import java.math.BigInteger; | |||
import junit.framework.AssertionFailedError; | |||
import junit.framework.TestCase; | |||
import org.apache.poi.util.HexDump; | |||
/** | |||
* Tests for {@link ExpandedDouble} | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class TestExpandedDouble extends TestCase { | |||
private static final BigInteger BIG_POW_10 = BigInteger.valueOf(1000000000); | |||
public void testNegative() { | |||
ExpandedDouble hd = new ExpandedDouble(0xC010000000000000L); | |||
if (hd.getBinaryExponent() == -2046) { | |||
throw new AssertionFailedError("identified bug - sign bit not masked out of exponent"); | |||
} | |||
assertEquals(2, hd.getBinaryExponent()); | |||
BigInteger frac = hd.getSignificand(); | |||
assertEquals(64, frac.bitLength()); | |||
assertEquals(1, frac.bitCount()); | |||
} | |||
public void testSubnormal() { | |||
ExpandedDouble hd = new ExpandedDouble(0x0000000000000001L); | |||
if (hd.getBinaryExponent() == -1023) { | |||
throw new AssertionFailedError("identified bug - subnormal numbers not decoded properly"); | |||
} | |||
assertEquals(-1086, hd.getBinaryExponent()); | |||
BigInteger frac = hd.getSignificand(); | |||
assertEquals(64, frac.bitLength()); | |||
assertEquals(1, frac.bitCount()); | |||
} | |||
/** | |||
* Tests specific values for conversion from {@link ExpandedDouble} to {@link NormalisedDecimal} and back | |||
*/ | |||
public void testRoundTripShifting() { | |||
long[] rawValues = { | |||
0x4010000000000004L, | |||
0x7010000000000004L, | |||
0x1010000000000004L, | |||
0x0010000000000001L, // near lowest normal number | |||
0x0010000000000000L, // lowest normal number | |||
0x000FFFFFFFFFFFFFL, // highest subnormal number | |||
0x0008000000000000L, // subnormal number | |||
0xC010000000000004L, | |||
0xE230100010001004L, | |||
0x403CE0FFFFFFFFF2L, | |||
0x0000000000000001L, // smallest non-zero number (subnormal) | |||
0x6230100010000FFEL, | |||
0x6230100010000FFFL, | |||
0x6230100010001000L, | |||
0x403CE0FFFFFFFFF0L, // has single digit round trip error | |||
0x2B2BFFFF10001079L, | |||
}; | |||
boolean success = true; | |||
for (int i = 0; i < rawValues.length; i++) { | |||
success &= confirmRoundTrip(i, rawValues[i]); | |||
} | |||
if (!success) { | |||
throw new AssertionFailedError("One or more test examples failed. See stderr."); | |||
} | |||
} | |||
public static boolean confirmRoundTrip(int i, long rawBitsA) { | |||
double a = Double.longBitsToDouble(rawBitsA); | |||
if (a == 0.0) { | |||
// Can't represent 0.0 or -0.0 with NormalisedDecimal | |||
return true; | |||
} | |||
ExpandedDouble ed1; | |||
NormalisedDecimal nd2; | |||
ExpandedDouble ed3; | |||
try { | |||
ed1 = new ExpandedDouble(rawBitsA); | |||
nd2 = ed1.normaliseBaseTen(); | |||
checkNormaliseBaseTenResult(ed1, nd2); | |||
ed3 = nd2.normaliseBaseTwo(); | |||
} catch (RuntimeException e) { | |||
System.err.println("example[" + i + "] (" | |||
+ formatDoubleAsHex(a) + ") exception:"); | |||
e.printStackTrace(); | |||
return false; | |||
} | |||
if (ed3.getBinaryExponent() != ed1.getBinaryExponent()) { | |||
System.err.println("example[" + i + "] (" | |||
+ formatDoubleAsHex(a) + ") bin exp mismatch"); | |||
return false; | |||
} | |||
BigInteger diff = ed3.getSignificand().subtract(ed1.getSignificand()).abs(); | |||
if (diff.signum() == 0) { | |||
return true; | |||
} | |||
// original quantity only has 53 bits of precision | |||
// these quantities may have errors in the 64th bit, which hopefully don't make any difference | |||
if (diff.bitLength() < 2) { | |||
// errors in the 64th bit happen from time to time | |||
// this is well below the 53 bits of precision required | |||
return true; | |||
} | |||
// but bigger errors are a concern | |||
System.out.println("example[" + i + "] (" | |||
+ formatDoubleAsHex(a) + ") frac mismatch: " + diff.toString()); | |||
for (int j=-2; j<3; j++) { | |||
System.out.println((j<0?"":"+") + j + ": " + getNearby(ed1, j)); | |||
} | |||
for (int j=-2; j<3; j++) { | |||
System.out.println((j<0?"":"+") + j + ": " + getNearby(nd2, j)); | |||
} | |||
return false; | |||
} | |||
public static String getBaseDecimal(ExpandedDouble hd) { | |||
int gg = 64 - hd.getBinaryExponent() - 1; | |||
BigDecimal bd = new BigDecimal(hd.getSignificand()).divide(new BigDecimal(BigInteger.ONE.shiftLeft(gg))); | |||
int excessPrecision = bd.precision() - 23; | |||
if (excessPrecision > 0) { | |||
bd = bd.setScale(bd.scale() - excessPrecision, BigDecimal.ROUND_HALF_UP); | |||
} | |||
return bd.unscaledValue().toString(); | |||
} | |||
public static BigInteger getNearby(NormalisedDecimal md, int offset) { | |||
BigInteger frac = md.composeFrac(); | |||
int be = frac.bitLength() - 24 - 1; | |||
int sc = frac.bitLength() - 64; | |||
return getNearby(frac.shiftRight(sc), be, offset); | |||
} | |||
public static BigInteger getNearby(ExpandedDouble hd, int offset) { | |||
return getNearby(hd.getSignificand(), hd.getBinaryExponent(), offset); | |||
} | |||
private static BigInteger getNearby(BigInteger significand, int binExp, int offset) { | |||
int nExtraBits = 1; | |||
int nDec = (int) Math.round(3.0 + (64+nExtraBits) * Math.log10(2.0)); | |||
BigInteger newFrac = significand.shiftLeft(nExtraBits).add(BigInteger.valueOf(offset)); | |||
int gg = 64 + nExtraBits - binExp - 1; | |||
BigDecimal bd = new BigDecimal(newFrac); | |||
if (gg > 0) { | |||
bd = bd.divide(new BigDecimal(BigInteger.ONE.shiftLeft(gg))); | |||
} else { | |||
BigInteger frac = newFrac; | |||
while (frac.bitLength() + binExp < 180) { | |||
frac = frac.multiply(BigInteger.TEN); | |||
} | |||
int binaryExp = binExp - newFrac.bitLength() + frac.bitLength(); | |||
bd = new BigDecimal( frac.shiftRight(frac.bitLength()-binaryExp-1)); | |||
} | |||
int excessPrecision = bd.precision() - nDec; | |||
if (excessPrecision > 0) { | |||
bd = bd.setScale(bd.scale() - excessPrecision, BigDecimal.ROUND_HALF_UP); | |||
} | |||
return bd.unscaledValue(); | |||
} | |||
private static void checkNormaliseBaseTenResult(ExpandedDouble orig, NormalisedDecimal result) { | |||
String sigDigs = result.getSignificantDecimalDigits(); | |||
BigInteger frac = orig.getSignificand(); | |||
while (frac.bitLength() + orig.getBinaryExponent() < 200) { | |||
frac = frac.multiply(BIG_POW_10); | |||
} | |||
int binaryExp = orig.getBinaryExponent() - orig.getSignificand().bitLength(); | |||
String origDigs = frac.shiftLeft(binaryExp+1).toString(10); | |||
if (!origDigs.startsWith(sigDigs)) { | |||
throw new AssertionFailedError("Expected '" + origDigs + "' but got '" + sigDigs + "'."); | |||
} | |||
double dO = Double.parseDouble("0." + origDigs.substring(sigDigs.length())); | |||
double d1 = Double.parseDouble(result.getFractionalPart().toPlainString()); | |||
BigInteger subDigsO = BigInteger.valueOf((int) (dO * 32768 + 0.5)); | |||
BigInteger subDigsB = BigInteger.valueOf((int) (d1 * 32768 + 0.5)); | |||
if (subDigsO.equals(subDigsB)) { | |||
return; | |||
} | |||
BigInteger diff = subDigsB.subtract(subDigsO).abs(); | |||
if (diff.intValue() > 100) { | |||
// 100/32768 ~= 0.003 | |||
throw new AssertionFailedError("minor mistake"); | |||
} | |||
} | |||
private static String formatDoubleAsHex(double d) { | |||
long l = Double.doubleToLongBits(d); | |||
StringBuilder sb = new StringBuilder(20); | |||
sb.append(HexDump.longToHex(l)).append('L'); | |||
return sb.toString(); | |||
} | |||
} |
@@ -0,0 +1,106 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.util; | |||
import junit.framework.AssertionFailedError; | |||
import junit.framework.TestCase; | |||
import org.apache.poi.ss.util.NumberComparisonExamples.ComparisonExample; | |||
import org.apache.poi.util.HexDump; | |||
/** | |||
* Tests for {@link NumberComparer} | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class TestNumberComparer extends TestCase { | |||
public void testAllComparisonExamples() { | |||
ComparisonExample[] examples = NumberComparisonExamples.getComparisonExamples(); | |||
boolean success = true; | |||
for(int i=0;i<examples.length; i++) { | |||
ComparisonExample ce = examples[i]; | |||
success &= confirm(i, ce.getA(), ce.getB(), +ce.getExpectedResult()); | |||
success &= confirm(i, ce.getB(), ce.getA(), -ce.getExpectedResult()); | |||
success &= confirm(i, ce.getNegA(), ce.getNegB(), -ce.getExpectedResult()); | |||
success &= confirm(i, ce.getNegB(), ce.getNegA(), +ce.getExpectedResult()); | |||
} | |||
if (!success) { | |||
throw new AssertionFailedError("One or more cases failed. See stderr"); | |||
} | |||
} | |||
public void testRoundTripOnComparisonExamples() { | |||
ComparisonExample[] examples = NumberComparisonExamples.getComparisonExamples(); | |||
boolean success = true; | |||
for(int i=0;i<examples.length; i++) { | |||
ComparisonExample ce = examples[i]; | |||
success &= confirmRoundTrip(i, ce.getA()); | |||
success &= confirmRoundTrip(i, ce.getNegA()); | |||
success &= confirmRoundTrip(i, ce.getB()); | |||
success &= confirmRoundTrip(i, ce.getNegB()); | |||
} | |||
if (!success) { | |||
throw new AssertionFailedError("One or more cases failed. See stderr"); | |||
} | |||
} | |||
private boolean confirmRoundTrip(int i, double a) { | |||
return TestExpandedDouble.confirmRoundTrip(i, Double.doubleToLongBits(a)); | |||
} | |||
/** | |||
* The actual example from bug 47598 | |||
*/ | |||
public void testSpecificExampleA() { | |||
double a = 0.06-0.01; | |||
double b = 0.05; | |||
assertFalse(a == b); | |||
assertEquals(0, NumberComparer.compare(a, b)); | |||
} | |||
/** | |||
* The example from the nabble posting | |||
*/ | |||
public void testSpecificExampleB() { | |||
double a = 1+1.0028-0.9973; | |||
double b = 1.0055; | |||
assertFalse(a == b); | |||
assertEquals(0, NumberComparer.compare(a, b)); | |||
} | |||
private static boolean confirm(int i, double a, double b, int expRes) { | |||
int actRes = NumberComparer.compare(a, b); | |||
int sgnActRes = actRes < 0 ? -1 : actRes > 0 ? +1 : 0; | |||
if (sgnActRes != expRes) { | |||
System.err.println("Mismatch example[" + i + "] (" | |||
+ formatDoubleAsHex(a) + ", " + formatDoubleAsHex(b) + ") expected " | |||
+ expRes + " but got " + sgnActRes); | |||
return false; | |||
} | |||
return true; | |||
} | |||
private static String formatDoubleAsHex(double d) { | |||
long l = Double.doubleToLongBits(d); | |||
StringBuilder sb = new StringBuilder(20); | |||
sb.append(HexDump.longToHex(l)).append('L'); | |||
return sb.toString(); | |||
} | |||
} |