at.spardat.enterprise.util.NumberUtil Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* s IT Solutions AT Spardat GmbH - initial API and implementation
*******************************************************************************/
// @(#) $Id: NumberUtil.java 2582 2008-05-07 14:10:55Z webok $
package at.spardat.enterprise.util;
import java.math.BigDecimal;
/**
* Provides some utility methods pertaining to numbers.
*/
public class NumberUtil {
/**
* Returned from method {@link #getMetric} to describe a canonic number
*/
public final static class Metric {
// 0 is there is no sign, 1 otherwise
public byte lenSign_;
// number of digits before the decimal point
public short lenVorKomma_;
// number of leading zeros in the part before the decimal point (<= lenVorKomma_)
public byte lenZerosVK_;
// 0 if there is no decimal point, 1 otherwise
public byte lenKomma_;
// number of digits after the decimal point
public short lenNachKomma_;
}
/**
* Parses a provided number in the so called canonic number format and returns
* some information about it. We consider a number encoding to be canonic
* if it obeys the following syntax: An '-', followed by a sequence
* of digits, followed by '.', followed by a sequence of digits. All components
* are optional. At least one digit must be present.
*
* @param v the number. Must not be null.
* @return a Metric object describing the number or null, if the number is
* not a canonic one.
*/
public static Metric getMetric (String v) {
Metric m = new Metric(); // assume members are 0-initialized
int vLen = v.length();
int vIndex = 0;
if (vLen == 0) return null;
char c = v.charAt(vIndex);
// optionales Vorzeichen
if (c == '-') { m.lenSign_ = 1; vIndex++; }
// Vorkommateil
boolean leadingZeros = true;
while (vIndex < vLen) {
char ch = v.charAt(vIndex);
if (NumberUtil.isDigit(ch)) {
m.lenVorKomma_++; vIndex++;
if (leadingZeros) {
// falls leadingZeros true, werden leading zeros gez?hlt;
if (ch == '0') m.lenZerosVK_++;
else leadingZeros = false;
}
} else break;
}
// Dezimalpunkt
if (vIndex < vLen && v.charAt(vIndex) == '.') { m.lenKomma_ = 1; vIndex++; }
// Nachkommateil
while (vIndex < vLen) {
if (NumberUtil.isDigit(v.charAt(vIndex))) {
m.lenNachKomma_++; vIndex++;
} else break;
}
// Konsistent?
if (vIndex != vLen || m.lenVorKomma_ + m.lenNachKomma_ == 0) {
return null;
}
return m;
}
/**
* Provides a fast but not locale independent implementation of numeric check.
*/
public static boolean isDigit (char ch) {
return ch <= '9' && ch >= '0';
}
/**
* Converts a nonnegative integer to a string, left-pads with zeros up to
* a given length and appends the result to a StringBuffer.
*
* This method is performance optimized for small numbers, i.e., numbers
* less than 10000.
*
* @param i integer to be appended. Must not be negative.
* @param minLen minimum length of appended zero praefixed string
* @param toAppendTo StringBuffer where to append to
*/
public static void appendIntString (int i, int minLen, StringBuffer toAppendTo) {
if (i < 100) {
if (i < 10) {
StringUtil.appendN ('0', minLen-1, toAppendTo);
toAppendTo.append((char)(i+'0'));
} else {
char d1 = (char)((i%10)+'0'); i /= 10;
char d2 = (char)(i+'0');
StringUtil.appendN ('0', minLen-2, toAppendTo);
toAppendTo.append(d2); toAppendTo.append(d1);
}
} else if (i < 10000) {
if (i < 1000) {
char d1 = (char)((i%10)+'0'); i /= 10;
char d2 = (char)((i%10)+'0'); i /= 10;
char d3 = (char)(i+'0');
StringUtil.appendN ('0', minLen-3, toAppendTo);
toAppendTo.append(d3); toAppendTo.append(d2); toAppendTo.append(d1);
} else {
char d1 = (char)((i%10)+'0'); i /= 10;
char d2 = (char)((i%10)+'0'); i /= 10;
char d3 = (char)((i%10)+'0'); i /= 10;
char d4 = (char)(i+'0');
StringUtil.appendN ('0', minLen-4, toAppendTo);
toAppendTo.append(d4); toAppendTo.append(d3); toAppendTo.append(d2); toAppendTo.append(d1);
}
} else {
String iAsS = Integer.toString(i);
StringUtil.appendN ('0', minLen - iAsS.length(), toAppendTo);
toAppendTo.append(iAsS);
}
}
/**
* Converts a double to a fixed point number string using a point
* as decimal separation character (canonic format, see above).
* If the provided double is NaN or Infinity, the empty string is returned.
* Digits after the comma are reduced so that the total number of
* significant digits does not exceed 15 digits.
*
* @param d the input double
* @return string in canonic format
*/
public static String double2String (double d) {
if (Double.isInfinite(d) || Double.isNaN(d)) return "";
BigDecimal bd = new BigDecimal (d);
String bdS = BigDecimalHelper.toPlainString(bd);
Metric m = getMetric (bdS);
// if the number of significant digits in the produced string is not
// greater than 15, we accept it
int digits = m.lenVorKomma_ + m.lenNachKomma_;
if (digits <= 15) return bdS;
// otherwise: if the excess number of significant digits
// lies in the fractional part, which may be due to not
// being able to exactly represent the floating point value decimally,
// we round
int maxAfterKomma = 15 - m.lenVorKomma_;
if (maxAfterKomma >= 0 && maxAfterKomma < bd.scale()) {
bd = bd.setScale(maxAfterKomma, BigDecimal.ROUND_HALF_UP);
bdS = bd.toString();
if (bdS.indexOf('.') != -1) {
// strip trailing zeros
int i = bdS.length()-1;
for (; i>=0; i--) {
if (bdS.charAt(i) != '0') break;
}
// the character at index i is the last that must be retained
bdS = bdS.substring(0, i+1);
}
}
return bdS;
}
}