123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- /* ====================================================================
- 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;
-
-
- @SuppressWarnings("java:S128")
- 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 #composeFrac()} 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() {
- return BigInteger.valueOf(_wholePart).shiftLeft(24).or(BigInteger.valueOf(_fractionalPart & 0x00FFFFFF));
- }
-
- 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();
- }
- }
|