aboutsummaryrefslogtreecommitdiffstats
path: root/src/testcases/org/apache/poi/ss/util/TestExpandedDouble.java
blob: 7b285a60b4be999ca5f99ff91e9f5f923b3a91fc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/* ====================================================================
   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.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.math.BigInteger;

import org.junit.jupiter.api.Test;

/**
 * Tests for {@link ExpandedDouble}
 */
public final class TestExpandedDouble {
	private static final BigInteger BIG_POW_10 = BigInteger.valueOf(1000000000);

	@Test
	public void testNegative() {
		ExpandedDouble hd = new ExpandedDouble(0xC010000000000000L);
		assertNotEquals(-2046, hd.getBinaryExponent(), "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());
	}

	@Test
	public void testSubnormal() {
		ExpandedDouble hd = new ExpandedDouble(0x0000000000000001L);
		assertNotEquals(-1023, hd.getBinaryExponent(), "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
	 */
	@Test
	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,
		};
		for (int i = 0; i < rawValues.length; i++) {
			confirmRoundTrip(i, rawValues[i]);
		}
	}

	public static void 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;
		}
		ExpandedDouble ed1 = new ExpandedDouble(rawBitsA);
		NormalisedDecimal nd2 = ed1.normaliseBaseTen();
		checkNormaliseBaseTenResult(ed1, nd2);

		ExpandedDouble ed3 = nd2.normaliseBaseTwo();
		assertEquals(ed3.getBinaryExponent(), ed1.getBinaryExponent(), "bin exp mismatch");

		BigInteger diff = ed3.getSignificand().subtract(ed1.getSignificand()).abs();
		if (diff.signum() == 0) {
			return;
		}
		// original quantity only has 53 bits of precision
		// these quantities may have errors in the 64th bit, which hopefully don't make any difference

		// errors in the 64th bit happen from time to time
		// this is well below the 53 bits of precision required
		assertTrue(diff.bitLength() < 2);
	}


	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);
		assertTrue(origDigs.startsWith(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();
		// 100/32768 ~= 0.003
		assertTrue(diff.intValue() <= 100, "minor mistake");
	}
}