/*
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.math;
import java.util.Arrays;
public class FormattedFloatingDecimal{
public enum Form { SCIENTIFIC, COMPATIBLE, DECIMAL_FLOAT, GENERAL };
public static FormattedFloatingDecimal valueOf(double d, int precision, Form form){
FloatingDecimal.BinaryToASCIIConverter fdConverter =
FloatingDecimal.getBinaryToASCIIConverter(d, form == Form.COMPATIBLE);
return new FormattedFloatingDecimal(precision,form, fdConverter);
}
private int decExponentRounded;
private char[] mantissa;
private char[] exponent;
private static final ThreadLocal<Object> threadLocalCharBuffer =
new ThreadLocal<Object>() {
@Override
protected Object initialValue() {
return new char[20];
}
};
private static char[] getBuffer(){
return (char[]) threadLocalCharBuffer.get();
}
private FormattedFloatingDecimal(int precision, Form form, FloatingDecimal.BinaryToASCIIConverter fdConverter) {
if (fdConverter.isExceptional()) {
this.mantissa = fdConverter.toJavaFormatString().toCharArray();
this.exponent = null;
return;
}
char[] digits = getBuffer();
int nDigits = fdConverter.getDigits(digits);
int decExp = fdConverter.getDecimalExponent();
int exp;
boolean isNegative = fdConverter.isNegative();
switch (form) {
case COMPATIBLE:
exp = decExp;
this.decExponentRounded = exp;
fillCompatible(precision, digits, nDigits, exp, isNegative);
break;
case DECIMAL_FLOAT:
exp = applyPrecision(decExp, digits, nDigits, decExp + precision);
fillDecimal(precision, digits, nDigits, exp, isNegative);
this.decExponentRounded = exp;
break;
case SCIENTIFIC:
exp = applyPrecision(decExp, digits, nDigits, precision + 1);
fillScientific(precision, digits, nDigits, exp, isNegative);
this.decExponentRounded = exp;
break;
case GENERAL:
exp = applyPrecision(decExp, digits, nDigits, precision);
// adjust precision to be the number of digits to right of decimal
// the real exponent to be output is actually exp - 1, not exp
if (exp - 1 < -4 || exp - 1 >= precision) {
// form = Form.SCIENTIFIC;
precision--;
fillScientific(precision, digits, nDigits, exp, isNegative);
} else {
// form = Form.DECIMAL_FLOAT;
precision = precision - exp;
fillDecimal(precision, digits, nDigits, exp, isNegative);
}
this.decExponentRounded = exp;
break;
default:
assert false;
}
}
// returns the exponent after rounding has been done by applyPrecision
public int getExponentRounded() {
return decExponentRounded - 1;
}
Returns the mantissa as a char[]
. Note that the returned value is a reference to the internal char[]
containing the mantissa, therefore code invoking this method should not pass the return value to external code but should in that case make a copy. Returns: a reference to the internal char[]
representing the mantissa.
/**
* Returns the mantissa as a {@code char[]}. Note that the returned value
* is a reference to the internal {@code char[]} containing the mantissa,
* therefore code invoking this method should not pass the return value to
* external code but should in that case make a copy.
*
* @return a reference to the internal {@code char[]} representing the
* mantissa.
*/
public char[] getMantissa(){
return mantissa;
}
Returns the exponent as a char[]
. Note that the returned value is a reference to the internal char[]
containing the exponent, therefore code invoking this method should not pass the return value to external code but should in that case make a copy. Returns: a reference to the internal char[]
representing the exponent.
/**
* Returns the exponent as a {@code char[]}. Note that the returned value
* is a reference to the internal {@code char[]} containing the exponent,
* therefore code invoking this method should not pass the return value to
* external code but should in that case make a copy.
*
* @return a reference to the internal {@code char[]} representing the
* exponent.
*/
public char[] getExponent(){
return exponent;
}
Returns new decExp in case of overflow.
/**
* Returns new decExp in case of overflow.
*/
private static int applyPrecision(int decExp, char[] digits, int nDigits, int prec) {
if (prec >= nDigits || prec < 0) {
// no rounding necessary
return decExp;
}
if (prec == 0) {
// only one digit (0 or 1) is returned because the precision
// excludes all significant digits
if (digits[0] >= '5') {
digits[0] = '1';
Arrays.fill(digits, 1, nDigits, '0');
return decExp + 1;
} else {
Arrays.fill(digits, 0, nDigits, '0');
return decExp;
}
}
int q = digits[prec];
if (q >= '5') {
int i = prec;
q = digits[--i];
if ( q == '9' ) {
while ( q == '9' && i > 0 ){
q = digits[--i];
}
if ( q == '9' ){
// carryout! High-order 1, rest 0s, larger exp.
digits[0] = '1';
Arrays.fill(digits, 1, nDigits, '0');
return decExp+1;
}
}
digits[i] = (char)(q + 1);
Arrays.fill(digits, i+1, nDigits, '0');
} else {
Arrays.fill(digits, prec, nDigits, '0');
}
return decExp;
}
Fills mantissa and exponent char arrays for compatible format.
/**
* Fills mantissa and exponent char arrays for compatible format.
*/
private void fillCompatible(int precision, char[] digits, int nDigits, int exp, boolean isNegative) {
int startIndex = isNegative ? 1 : 0;
if (exp > 0 && exp < 8) {
// print digits.digits.
if (nDigits < exp) {
int extraZeros = exp - nDigits;
mantissa = create(isNegative, nDigits + extraZeros + 2);
System.arraycopy(digits, 0, mantissa, startIndex, nDigits);
Arrays.fill(mantissa, startIndex + nDigits, startIndex + nDigits + extraZeros, '0');
mantissa[startIndex + nDigits + extraZeros] = '.';
mantissa[startIndex + nDigits + extraZeros+1] = '0';
} else if (exp < nDigits) {
int t = Math.min(nDigits - exp, precision);
mantissa = create(isNegative, exp + 1 + t);
System.arraycopy(digits, 0, mantissa, startIndex, exp);
mantissa[startIndex + exp ] = '.';
System.arraycopy(digits, exp, mantissa, startIndex+exp+1, t);
} else { // exp == digits.length
mantissa = create(isNegative, nDigits + 2);
System.arraycopy(digits, 0, mantissa, startIndex, nDigits);
mantissa[startIndex + nDigits ] = '.';
mantissa[startIndex + nDigits +1] = '0';
}
} else if (exp <= 0 && exp > -3) {
int zeros = Math.max(0, Math.min(-exp, precision));
int t = Math.max(0, Math.min(nDigits, precision + exp));
// write '0' s before the significant digits
if (zeros > 0) {
mantissa = create(isNegative, zeros + 2 + t);
mantissa[startIndex] = '0';
mantissa[startIndex+1] = '.';
Arrays.fill(mantissa, startIndex + 2, startIndex + 2 + zeros, '0');
if (t > 0) {
// copy only when significant digits are within the precision
System.arraycopy(digits, 0, mantissa, startIndex + 2 + zeros, t);
}
} else if (t > 0) {
mantissa = create(isNegative, zeros + 2 + t);
mantissa[startIndex] = '0';
mantissa[startIndex + 1] = '.';
// copy only when significant digits are within the precision
System.arraycopy(digits, 0, mantissa, startIndex + 2, t);
} else {
this.mantissa = create(isNegative, 1);
this.mantissa[startIndex] = '0';
}
} else {
if (nDigits > 1) {
mantissa = create(isNegative, nDigits + 1);
mantissa[startIndex] = digits[0];
mantissa[startIndex + 1] = '.';
System.arraycopy(digits, 1, mantissa, startIndex + 2, nDigits - 1);
} else {
mantissa = create(isNegative, 3);
mantissa[startIndex] = digits[0];
mantissa[startIndex + 1] = '.';
mantissa[startIndex + 2] = '0';
}
int e, expStartIntex;
boolean isNegExp = (exp <= 0);
if (isNegExp) {
e = -exp + 1;
expStartIntex = 1;
} else {
e = exp - 1;
expStartIntex = 0;
}
// decExponent has 1, 2, or 3, digits
if (e <= 9) {
exponent = create(isNegExp,1);
exponent[expStartIntex] = (char) (e + '0');
} else if (e <= 99) {
exponent = create(isNegExp,2);
exponent[expStartIntex] = (char) (e / 10 + '0');
exponent[expStartIntex+1] = (char) (e % 10 + '0');
} else {
exponent = create(isNegExp,3);
exponent[expStartIntex] = (char) (e / 100 + '0');
e %= 100;
exponent[expStartIntex+1] = (char) (e / 10 + '0');
exponent[expStartIntex+2] = (char) (e % 10 + '0');
}
}
}
private static char[] create(boolean isNegative, int size) {
if(isNegative) {
char[] r = new char[size +1];
r[0] = '-';
return r;
} else {
return new char[size];
}
}
/*
* Fills mantissa char arrays for DECIMAL_FLOAT format.
* Exponent should be equal to null.
*/
private void fillDecimal(int precision, char[] digits, int nDigits, int exp, boolean isNegative) {
int startIndex = isNegative ? 1 : 0;
if (exp > 0) {
// print digits.digits.
if (nDigits < exp) {
mantissa = create(isNegative,exp);
System.arraycopy(digits, 0, mantissa, startIndex, nDigits);
Arrays.fill(mantissa, startIndex + nDigits, startIndex + exp, '0');
// Do not append ".0" for formatted floats since the user
// may request that it be omitted. It is added as necessary
// by the Formatter.
} else {
int t = Math.min(nDigits - exp, precision);
mantissa = create(isNegative, exp + (t > 0 ? (t + 1) : 0));
System.arraycopy(digits, 0, mantissa, startIndex, exp);
// Do not append ".0" for formatted floats since the user
// may request that it be omitted. It is added as necessary
// by the Formatter.
if (t > 0) {
mantissa[startIndex + exp] = '.';
System.arraycopy(digits, exp, mantissa, startIndex + exp + 1, t);
}
}
} else if (exp <= 0) {
int zeros = Math.max(0, Math.min(-exp, precision));
int t = Math.max(0, Math.min(nDigits, precision + exp));
// write '0' s before the significant digits
if (zeros > 0) {
mantissa = create(isNegative, zeros + 2 + t);
mantissa[startIndex] = '0';
mantissa[startIndex+1] = '.';
Arrays.fill(mantissa, startIndex + 2, startIndex + 2 + zeros, '0');
if (t > 0) {
// copy only when significant digits are within the precision
System.arraycopy(digits, 0, mantissa, startIndex + 2 + zeros, t);
}
} else if (t > 0) {
mantissa = create(isNegative, zeros + 2 + t);
mantissa[startIndex] = '0';
mantissa[startIndex + 1] = '.';
// copy only when significant digits are within the precision
System.arraycopy(digits, 0, mantissa, startIndex + 2, t);
} else {
this.mantissa = create(isNegative, 1);
this.mantissa[startIndex] = '0';
}
}
}
Fills mantissa and exponent char arrays for SCIENTIFIC format.
/**
* Fills mantissa and exponent char arrays for SCIENTIFIC format.
*/
private void fillScientific(int precision, char[] digits, int nDigits, int exp, boolean isNegative) {
int startIndex = isNegative ? 1 : 0;
int t = Math.max(0, Math.min(nDigits - 1, precision));
if (t > 0) {
mantissa = create(isNegative, t + 2);
mantissa[startIndex] = digits[0];
mantissa[startIndex + 1] = '.';
System.arraycopy(digits, 1, mantissa, startIndex + 2, t);
} else {
mantissa = create(isNegative, 1);
mantissa[startIndex] = digits[0];
}
char expSign;
int e;
if (exp <= 0) {
expSign = '-';
e = -exp + 1;
} else {
expSign = '+' ;
e = exp - 1;
}
// decExponent has 1, 2, or 3, digits
if (e <= 9) {
exponent = new char[] { expSign,
'0', (char) (e + '0') };
} else if (e <= 99) {
exponent = new char[] { expSign,
(char) (e / 10 + '0'), (char) (e % 10 + '0') };
} else {
char hiExpChar = (char) (e / 100 + '0');
e %= 100;
exponent = new char[] { expSign,
hiExpChar, (char) (e / 10 + '0'), (char) (e % 10 + '0') };
}
}
}