| /* ==================================================================== |
| 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(); |
| } |
| } |