All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.darwinsys.util.ScaledNumberFormat Maven / Gradle / Ivy

package com.darwinsys.util;

import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParseException;
import java.text.ParsePosition;

/**
 * Format numbers scaled for human comprehension.
 *
 * "Human-readable" output uses43 digits max, and puts unit suffixes at the end.
 * Makes output compact and easy-to-read esp. on huge disks. Formatting code was
 * originally in OpenBSD "df", converted to library routine. Scanning code
 * written by Ian Darwin, originally for OpenBSD libutil.
 *
 * Rewritten in Java in January, 2001; re-synched with OpenBSD in 2004.
 *
 * @author Ian F. Darwin, http://www.darwinsys.com/
 */
public class ScaledNumberFormat extends Format {

	private static final long serialVersionUID = 1030043222640098114L;

	final static int NONE = 0; // to become an enum for 1.5

	final static int KILO = 1;

	final static int MEGA = 2;

	final static int GIGA = 3;

	final static int TERA = 4;

	final static int PETA = 5;

	final static int EXA = 6;

	DecimalFormat df;

	public ScaledNumberFormat() {
		df = new DecimalFormat("0");
	}

	/** The input scaling factors. All three arrays must be in same order. */
	static char scale_chars[] = { 'B', 'K', 'M', 'G', 'T', 'P', 'E', };

	/** The numeric scale values. All three arrays must be in same order. */
	int units[] = { NONE, KILO, MEGA, GIGA, TERA, PETA, EXA };

	/** The input scale sizes. All three arrays must be in same order. */
	static long scale_factors[] = { 1, 1024, 1048576L, 1073741824L,
			1099511627776L, 1125899906842624L, 1152921504606846976L };

	private final static long LLONG_MAX = scale_factors[6];

	/* To prevent numeric overflow (Java doesn't need a "long long") */
	static final int MAX_DIGITS = 10;

	/**
	 * Parse a String expected to contain a number in Human Scaled Form.
	 *
	 * @param s -
	 *            String to be parsed.
	 * @param where
	 *            Ignored - required by API
	 * @return a Long containing the value (value is always integral, even
	 *         though has a fractional part before scaling).
	 */
	public Object parseObject(String s, ParsePosition where) {
		int i, sign = 0, fract_digits = 0;
		long scale_fact = 1, whole = 0;
		long fpart = 0;

		if (s == null)
			return null;

		char[] b = s.trim().toCharArray();
		int p = 0; // the index into b; the # of chars we've scanned.

		/* Then at most one leading + or - */
		while (b[p] == '-' || b[p] == '+') {
			if (b[p] == '-') {
				if (sign != 0)
					throw new NumberFormatException("Number " + s
							+ " has more than one sign.");
				sign = -1;
				++p;
			} else if (b[p] == '+') {
				if (sign != 0)
					throw new NumberFormatException("Number " + s
							+ " has more than one sign.");
				sign = +1;
				++p;
			}
		}

		/*
		 * Main loop: Scan digits, find decimal point, if present. We don't
		 * allow exponentials, so no scientific notation (but note that E for
		 * Exa might look like e to some!). Advance 'p' to end, to get scale
		 * factor.
		 */
		for (; p < b.length && (Character.isDigit(b[p]) || b[p] == '.'); ++p) {
			int ndigits = 0;
			if (b[p] == '.') {
				if (fract_digits > 0) {
					throw new NumberFormatException("Number " + s
							+ " has more than one decimal point ");
				}
				fract_digits = 1;
				continue;
			}

			i = (b[p]) - '0'; // whew! finally a digit we can use
			if (fract_digits > 0) { // fractional digit
				if (fract_digits >= MAX_DIGITS)
					throw new NumberFormatException("Number too large");
				fpart *= 10;
				fpart += i;
				++fract_digits; // track for later scaling
			} else { // normal digit
				if (++ndigits >= MAX_DIGITS)
					throw new NumberFormatException("Number too large");
				whole *= 10;
				whole += i;
			}
		}
		/*
		 * printf("whole=%qd, fpart %ld, fract_digits %ld\n", whole, fpart,
		 * fract_digits);
		 */
		if (sign < 0) {
			whole *= sign;
			fpart *= sign;
		}

		/* If no scale factor given, we're done. fraction is discarded. */
		if (p >= b.length) {
			return Long.valueOf(whole);
		}

		/* Validate scale factor, and scale whole and fraction by it. */
		for (i = 0; i < scale_factors.length; i++) {
			if (b[p] == scale_chars[i]
					|| b[p] == Character.toLowerCase(scale_chars[i])) {
				// XXX if digits after this, throw exception
				scale_fact = scale_factors[i];
				// scale whole part: easy
				whole *= scale_fact;
				/*
				 * truncate fpart so it does't overflow. then scale fractional
				 * part.
				 */
				while (fpart >= LLONG_MAX / scale_fact) {
					fpart /= 10;
					fract_digits--;
				}
				fpart *= scale_fact;
				if (fract_digits > 0) {
					for (i = 0; i < fract_digits - 1; i++)
						fpart /= 10;
				}
				whole += fpart;
				return Long.valueOf(whole);
			}
		}
		throw new IllegalArgumentException("invalid scale factor " + b[p]);
	}

	/*
	 * Parse a String containing a Human Scaled Number.
	 *
	 * @see ScaledNumberFormat#parseObject(java.lang.String)
	 * @param str The String to be parsed
	 */
	public Object parseObject(String str) throws ParseException {
		return parseObject(str, null);
	}

	/*
	 * Format the given Number as a Scaled Numeral, returning the Stringbuffer
	 * (updated), and ignoring the FieldPosition. Method signature is
	 * overkill, but required as a subclass of Format.
	 * @param on The number to be formatted
	 * @param sb The stringbuffer to contain the results
	 * @param fp The position
	 */
	@Override
	public StringBuffer format(Object on, StringBuffer sb, FieldPosition fp) {
		if (on instanceof String) {
			String son = (String) on;
			if (son.length() == 0) {
				return sb.append("0B");
			}
			on = parseObject(son, null);
		}
		if (!(on instanceof Long)) {
			throw new IllegalArgumentException("Argument " + on
					+ " must be String or Long");
		}
		long n = ((Long) on).longValue();
		sb.append(format(n));
		return sb;
	}

	/**
	 * Non-standard overload:
	 * Format a double as a Scaled Numeral; just truncate to a long, and call
	 * format(long).
	 * @param n Number to format
	 * @return Formatted number
	 */
	public String format(double n) {
		return format((long) n);
	}

	/**
	 * Non-standard overload;
	 * Format a given long as a Scaled Numeral. This method is the REAL
	 * FORMATTING ENGINE.
	 * @param number Number to format
	 * @return the formatted number as a String
	 */
	public String format(long number) {
		long fract = 0;
		int unit = NONE;

		StringBuffer buf = new StringBuffer();

		long abval = Math.abs(number);

		for (int i = 1/* ! */; i < scale_factors.length; i++) {
			if (abval < scale_factors[i]) {
				unit = units[i - 1];
				fract = i == 1 ? 0 : abval % scale_factors[i - 1];
				number /= scale_factors[i - 1];
				break;
			}
		}

		if (fract < 0)
			fract = -fract;

		/* scale fraction to one digit (truncate, not round) */
		while (fract > 9)
			fract /= 10;

		if (number == 0)
			return "0B";
		else if (unit == NONE || number >= 100 || number <= -100) {
			buf.append(df.format(number)).append(scale_chars[unit]);
		} else {
			buf.append(df.format(number)).append('.').append(fract).append(
					scale_chars[unit]);
		}

		return buf.toString();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy