public void TestSubnormal() { ExpandedDouble hd = new ExpandedDouble(0x0000000000000001L); if (hd.GetBinaryExponent() == -1023) { throw new AssertionException("identified bug - subnormal numbers not decoded properly"); } Assert.AreEqual(-1086, hd.GetBinaryExponent()); BigInteger frac = hd.GetSignificand(); Assert.AreEqual(64, frac.BitLength()); Assert.AreEqual(1, frac.BitCount()); }
public void TestNegative() { ExpandedDouble hd = new ExpandedDouble(unchecked((long)0xC010000000000000L)); if (hd.GetBinaryExponent() == -2046) { throw new AssertionException("identified bug - sign bit not masked out of exponent"); } Assert.AreEqual(2, hd.GetBinaryExponent()); BigInteger frac = hd.GetSignificand(); Assert.AreEqual(64, frac.BitLength()); Assert.AreEqual(1, frac.BitCount()); }
/* 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()); }
public static bool ConfirmRoundTrip(int i, long rawBitsA) { double a = BitConverter.Int64BitsToDouble(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 (Exception e) { Console.WriteLine("example[" + i + "] (" + FormatDoubleAsHex(a) + ") exception: " + e.Message); return false; } if (ed3.GetBinaryExponent() != ed1.GetBinaryExponent()) { Console.WriteLine("example[" + i + "] (" + FormatDoubleAsHex(a) + ") bin exp mismatch"); return false; } BigInteger diff = ed3.GetSignificand() - (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.BitCount() < 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 Console.WriteLine("example[" + i + "] (" + FormatDoubleAsHex(a) + ") frac mismatch: " + diff.ToString()); for (int j = -2; j < 3; j++) { Console.WriteLine((j < 0 ? "" : "+") + j + ": " + GetNearby(ed1, j)); } for (int j = -2; j < 3; j++) { Console.WriteLine((j < 0 ? "" : "+") + j + ": " + GetNearby(nd2, j)); } return false; }
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(ExpandedDouble hd, int offset) { return GetNearby(hd.GetSignificand(), hd.GetBinaryExponent(), offset); }
public static String GetBaseDecimal(ExpandedDouble hd) { /*int gg = 64 - hd.GetBinaryExponent() - 1; BigDecimal bd = new BigDecimal(hd.GetSignificand()).divide(new BigDecimal(BigInteger.ONE<<gg)); int excessPrecision = bd.precision() - 23; if (excessPrecision > 0) { bd = bd.SetScale(bd.scale() - excessPrecision, BigDecimal.ROUND_HALF_UP); } return bd.unscaledValue().ToString();*/ throw new NotImplementedException("This Method need BigDecimal class"); }
/** * 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); }
/* 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(); }