| /* ==================================================================== |
| 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; |
| import org.junit.jupiter.params.ParameterizedTest; |
| import org.junit.jupiter.params.provider.ValueSource; |
| |
| /** |
| * Tests for {@link ExpandedDouble} |
| */ |
| final class TestExpandedDouble { |
| private static final BigInteger BIG_POW_10 = BigInteger.valueOf(1000000000); |
| |
| @Test |
| 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 |
| 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 |
| */ |
| @ParameterizedTest |
| @ValueSource(longs = { |
| 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, |
| }) |
| void confirmRoundTrip(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"); |
| } |
| } |