java.lang.HexStringParser Maven / Gradle / Ivy
/*
* 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 java.lang;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* Parses hex string to a single or double precision floating point number.
*
* TODO: rewrite this!
*
* @hide
*/
final class HexStringParser {
private static final int DOUBLE_EXPONENT_WIDTH = 11;
private static final int DOUBLE_MANTISSA_WIDTH = 52;
private static final int FLOAT_EXPONENT_WIDTH = 8;
private static final int FLOAT_MANTISSA_WIDTH = 23;
private static final int HEX_RADIX = 16;
private static final int MAX_SIGNIFICANT_LENGTH = 15;
private static final String HEX_SIGNIFICANT = "0[xX](\\p{XDigit}+\\.?|\\p{XDigit}*\\.\\p{XDigit}+)";
private static final String BINARY_EXPONENT = "[pP]([+-]?\\d+)";
private static final String FLOAT_TYPE_SUFFIX = "[fFdD]?";
private static final String HEX_PATTERN = "[\\x00-\\x20]*([+-]?)" + HEX_SIGNIFICANT
+ BINARY_EXPONENT + FLOAT_TYPE_SUFFIX + "[\\x00-\\x20]*";
private static final Pattern PATTERN = Pattern.compile(HEX_PATTERN);
private final int EXPONENT_WIDTH;
private final int MANTISSA_WIDTH;
private final long EXPONENT_BASE;
private final long MAX_EXPONENT;
private final long MIN_EXPONENT;
private final long MANTISSA_MASK;
private long sign;
private long exponent;
private long mantissa;
private String abandonedNumber="";
public HexStringParser(int exponentWidth, int mantissaWidth) {
this.EXPONENT_WIDTH = exponentWidth;
this.MANTISSA_WIDTH = mantissaWidth;
this.EXPONENT_BASE = ~(-1L << (exponentWidth - 1));
this.MAX_EXPONENT = ~(-1L << exponentWidth);
this.MIN_EXPONENT = -(MANTISSA_WIDTH + 1);
this.MANTISSA_MASK = ~(-1L << mantissaWidth);
}
/*
* Parses the hex string to a double number.
*/
public static double parseDouble(String hexString) {
HexStringParser parser = new HexStringParser(DOUBLE_EXPONENT_WIDTH, DOUBLE_MANTISSA_WIDTH);
long result = parser.parse(hexString, true);
return Double.longBitsToDouble(result);
}
/*
* Parses the hex string to a float number.
*/
public static float parseFloat(String hexString) {
HexStringParser parser = new HexStringParser(FLOAT_EXPONENT_WIDTH, FLOAT_MANTISSA_WIDTH);
int result = (int) parser.parse(hexString, false);
return Float.intBitsToFloat(result);
}
private long parse(String hexString, boolean isDouble) {
Matcher matcher = PATTERN.matcher(hexString);
if (!matcher.matches()) {
throw new NumberFormatException("Invalid hex " + (isDouble ? "double" : "float")+ ":" +
hexString);
}
String signStr = matcher.group(1);
String significantStr = matcher.group(2);
String exponentStr = matcher.group(3);
parseHexSign(signStr);
parseExponent(exponentStr);
parseMantissa(significantStr);
sign <<= (MANTISSA_WIDTH + EXPONENT_WIDTH);
exponent <<= MANTISSA_WIDTH;
return sign | exponent | mantissa;
}
/*
* Parses the sign field.
*/
private void parseHexSign(String signStr) {
this.sign = signStr.equals("-") ? 1 : 0;
}
/*
* Parses the exponent field.
*/
private void parseExponent(String exponentStr) {
char leadingChar = exponentStr.charAt(0);
int expSign = (leadingChar == '-' ? -1 : 1);
if (!Character.isDigit(leadingChar)) {
exponentStr = exponentStr.substring(1);
}
try {
exponent = expSign * Long.parseLong(exponentStr);
checkedAddExponent(EXPONENT_BASE);
} catch (NumberFormatException e) {
exponent = expSign * Long.MAX_VALUE;
}
}
/*
* Parses the mantissa field.
*/
private void parseMantissa(String significantStr) {
String[] strings = significantStr.split("\\.");
String strIntegerPart = strings[0];
String strDecimalPart = strings.length > 1 ? strings[1] : "";
String significand = getNormalizedSignificand(strIntegerPart,strDecimalPart);
if (significand.equals("0")) {
setZero();
return;
}
int offset = getOffset(strIntegerPart, strDecimalPart);
checkedAddExponent(offset);
if (exponent >= MAX_EXPONENT) {
setInfinite();
return;
}
if (exponent <= MIN_EXPONENT) {
setZero();
return;
}
if (significand.length() > MAX_SIGNIFICANT_LENGTH) {
abandonedNumber = significand.substring(MAX_SIGNIFICANT_LENGTH);
significand = significand.substring(0, MAX_SIGNIFICANT_LENGTH);
}
mantissa = Long.parseLong(significand, HEX_RADIX);
if (exponent >= 1) {
processNormalNumber();
} else{
processSubNormalNumber();
}
}
private void setInfinite() {
exponent = MAX_EXPONENT;
mantissa = 0;
}
private void setZero() {
exponent = 0;
mantissa = 0;
}
/*
* Sets the exponent variable to Long.MAX_VALUE or -Long.MAX_VALUE if
* overflow or underflow happens.
*/
private void checkedAddExponent(long offset) {
long result = exponent + offset;
int expSign = Long.signum(exponent);
if (expSign * Long.signum(offset) > 0 && expSign * Long.signum(result) < 0) {
exponent = expSign * Long.MAX_VALUE;
} else {
exponent = result;
}
}
private void processNormalNumber(){
int desiredWidth = MANTISSA_WIDTH + 2;
fitMantissaInDesiredWidth(desiredWidth);
round();
mantissa = mantissa & MANTISSA_MASK;
}
private void processSubNormalNumber(){
int desiredWidth = MANTISSA_WIDTH + 1;
desiredWidth += (int)exponent;//lends bit from mantissa to exponent
exponent = 0;
fitMantissaInDesiredWidth(desiredWidth);
round();
mantissa = mantissa & MANTISSA_MASK;
}
/*
* Adjusts the mantissa to desired width for further analysis.
*/
private void fitMantissaInDesiredWidth(int desiredWidth){
int bitLength = countBitsLength(mantissa);
if (bitLength > desiredWidth) {
discardTrailingBits(bitLength - desiredWidth);
} else {
mantissa <<= (desiredWidth - bitLength);
}
}
/*
* Stores the discarded bits to abandonedNumber.
*/
private void discardTrailingBits(long num) {
long mask = ~(-1L << num);
abandonedNumber += (mantissa & mask);
mantissa >>= num;
}
/*
* The value is rounded up or down to the nearest infinitely precise result.
* If the value is exactly halfway between two infinitely precise results,
* then it should be rounded up to the nearest infinitely precise even.
*/
private void round() {
String result = abandonedNumber.replaceAll("0+", "");
boolean moreThanZero = (result.length() > 0 ? true : false);
int lastDiscardedBit = (int) (mantissa & 1L);
mantissa >>= 1;
int tailBitInMantissa = (int) (mantissa & 1L);
if (lastDiscardedBit == 1 && (moreThanZero || tailBitInMantissa == 1)) {
int oldLength = countBitsLength(mantissa);
mantissa += 1L;
int newLength = countBitsLength(mantissa);
//Rounds up to exponent when whole bits of mantissa are one-bits.
if (oldLength >= MANTISSA_WIDTH && newLength > oldLength) {
checkedAddExponent(1);
}
}
}
/*
* Returns the normalized significand after removing the leading zeros.
*/
private String getNormalizedSignificand(String strIntegerPart, String strDecimalPart) {
String significand = strIntegerPart + strDecimalPart;
significand = significand.replaceFirst("^0+", "");
if (significand.length() == 0) {
significand = "0";
}
return significand;
}
/*
* Calculates the offset between the normalized number and unnormalized
* number. In a normalized representation, significand is represented by the
* characters "0x1." followed by a lowercase hexadecimal representation of
* the rest of the significand as a fraction.
*/
private int getOffset(String strIntegerPart, String strDecimalPart) {
strIntegerPart = strIntegerPart.replaceFirst("^0+", "");
//If the Integer part is a nonzero number.
if (strIntegerPart.length() != 0) {
String leadingNumber = strIntegerPart.substring(0, 1);
return (strIntegerPart.length() - 1) * 4 + countBitsLength(Long.parseLong(leadingNumber,HEX_RADIX)) - 1;
}
//If the Integer part is a zero number.
int i;
for (i = 0; i < strDecimalPart.length() && strDecimalPart.charAt(i) == '0'; i++);
if (i == strDecimalPart.length()) {
return 0;
}
String leadingNumber=strDecimalPart.substring(i,i + 1);
return (-i - 1) * 4 + countBitsLength(Long.parseLong(leadingNumber, HEX_RADIX)) - 1;
}
private int countBitsLength(long value) {
int leadingZeros = Long.numberOfLeadingZeros(value);
return Long.SIZE - leadingZeros;
}
}