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 { decimalDigits = rnd.GetSignificantDecimalDigits(); } int countSigDigits = CountSignifantDigits(decimalDigits); if (decExponent < 0) { FormatLessThanOne(sb, decimalDigits, decExponent, countSigDigits); } else { FormatGreaterThanOne(sb, decimalDigits, decExponent, countSigDigits); } }
/* namespace */ public static String RawDoubleBitsToText(long pRawBits) { long rawBits = pRawBits; bool isNegative = rawBits < 0; // sign bit is in the same place for long and double if (isNegative) { rawBits &= 0x7FFFFFFFFFFFFFFFL; } 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"); } 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 // the standard rules below. isNegative = false; // except that the sign bit is ignored } NormalisedDecimal nd = ed.NormaliseBaseTen(); StringBuilder sb = new StringBuilder(MAX_TEXT_LEN + 1); if (isNegative) { sb.Append('-'); } ConvertToText(sb, nd); return(sb.ToString()); }
/** * 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); }
/** * 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; }
private static void CheckNormaliseBaseTenResult(ExpandedDouble orig, NormalisedDecimal result) { String sigDigs = result.GetSignificantDecimalDigits(); BigInteger frac = orig.GetSignificand(); while (frac.BitLength() + orig.GetBinaryExponent() < 200) { frac = frac * (BIG_POW_10); } int binaryExp = orig.GetBinaryExponent() - orig.GetSignificand().BitLength(); String origDigs = (frac << (binaryExp + 1)).ToString(10); if (!origDigs.StartsWith(sigDigs)) { throw new AssertionException("Expected '" + origDigs + "' but got '" + sigDigs + "'."); } double dO = Double.Parse("0." + origDigs.Substring(sigDigs.Length)); double d1 = Double.Parse(result.GetFractionalPart().ToString()); BigInteger subDigsO = new BigInteger((int)(dO * 32768 + 0.5)); BigInteger subDigsB = new BigInteger((int)(d1 * 32768 + 0.5)); if (subDigsO.Equals(subDigsB)) { return; } BigInteger diff = (subDigsB - subDigsO).Abs(); if (diff.IntValue() > 100) { // 100/32768 ~= 0.003 throw new AssertionException("minor mistake"); } }
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 >> (sc), be, offset); }
/** * 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 <c>false</c>. 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 <c>a</c> and <c>b</c>. */ public static int Compare(double a, double b) { long rawBitsA = BitConverter.DoubleToInt64Bits(a); long rawBitsB = BitConverter.DoubleToInt64Bits(b); int biasedExponentA = IEEEDouble.GetBiasedExponent(rawBitsA); int biasedExponentB = IEEEDouble.GetBiasedExponent(rawBitsB); if (biasedExponentA == IEEEDouble.BIASED_EXPONENT_SPECIAL_VALUE) { throw new ArgumentException("Special double values are not allowed: " + ToHex(a)); } if (biasedExponentB == IEEEDouble.BIASED_EXPONENT_SPECIAL_VALUE) { throw new ArgumentException("Special double values are not allowed: " + ToHex(a)); } int cmp; // sign bit is in the same place for long and double: bool aIsNegative = rawBitsA < 0; bool 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 & IEEEDouble.FRAC_MASK, rawBitsB & IEEEDouble.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 - IEEEDouble.EXPONENT_BIAS); ExpandedDouble edB = ExpandedDouble.FromRawBitsAndExponent(rawBitsB, biasedExponentB - IEEEDouble.EXPONENT_BIAS); NormalisedDecimal ndA = edA.NormaliseBaseTen().RoundUnits(); NormalisedDecimal ndB = edB.NormaliseBaseTen().RoundUnits(); cmp = ndA.CompareNormalised(ndB); if (aIsNegative) { return(-cmp); } return(cmp); }
/** * 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)); }