/**
 * Copyright 2010, 2011 Jingjing Li.
 *
 * This file is part of jplot2d.
 *
 * jplot2d is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or any later version.
 *
 * jplot2d 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 Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with jplot2d. If not, see .
 */
package org.jplot2d.util;

import java.util.Locale;

/**
 * @author Jingjing Li
 * 
 */
public class NumberUtils {

	/**
	 * The number of logical bits in the significand of a double number, including the implicit bit.
	 */
	private static final int SIGNIFICAND_WIDTH = 53;

	/**
	 * Bias used in representing a double exponent.
	 */
	private static final int EXP_BIAS = 1023;

	/**
	 * Bit mask to isolate the sign bit of a double.
	 */
	private static final long SIGN_BIT_MASK = 0x8000000000000000L;

	/**
	 * Bit mask to isolate the exponent field of a double.
	 */
	private static final long EXP_BIT_MASK = 0x7FF0000000000000L;

	/**
	 * Bit mask to isolate the significand field of a double.
	 */
	private static final long SIGNIF_BIT_MASK = 0x000FFFFFFFFFFFFFL;

	/**
	 * @param a
	 * @param b
	 * @param bit
	 *            the number of bits that the different is tolerated.
	 * @return
	 */
	public static boolean approximate(double a, double b, int bit) {
		if (a == b) {
			return true;
		} else if (bit == 0) {
			return false;
		}

		long lba = Double.doubleToLongBits(a);
		long lbb = Double.doubleToLongBits(b);
		if ((lba & SIGN_BIT_MASK) != (lbb & SIGN_BIT_MASK)) {
			return false;
		}
		int expa = (int) (((lba & EXP_BIT_MASK) >> (SIGNIFICAND_WIDTH - 1)) - EXP_BIAS);
		int expb = (int) (((lbb & EXP_BIT_MASK) >> (SIGNIFICAND_WIDTH - 1)) - EXP_BIAS);
		if (expa - expb > 1 || expa - expb < -1) {
			return false;
		}
		long ma = (lba & SIGNIF_BIT_MASK) | 0x10000000000000L;
		long mb = (lbb & SIGNIF_BIT_MASK) | 0x10000000000000L;
		if (expa > expb) {
			ma <<= 1;
		}
		if (expa < expb) {
			mb <<= 1;
		}
		long delta = Math.abs(ma - mb);
		long mask = (1L << bit) - 1L;
		return (delta & ~mask) == 0;
	}

	/**
	 * Find a proper format for the given significant digits limit.
	 * 
	 * @param value
	 *            the value
	 * @param digits
	 *            significant digits
	 * @return the format string for java Formatter
	 */
	public static String calcFormatStr(double value, int digits) {

		if (value == 0 || Double.isNaN(value) || Double.isInfinite(value)) {
			return "%.0f";
		}

		/* number of significant digits */
		int maxPrec = 0;
		/* number of fraction digits */
		int maxFractionDigits = 0;

		double v = Math.abs(value);

		String s = String.format((Locale) null, "%." + (digits - 1) + "e", v);
		int eidx = s.lastIndexOf('e');
		int ensi = eidx + 1; // the start index of exponent number
		if (s.charAt(ensi) == '+') {
			ensi++;
		}
		int mag = Integer.parseInt(s.substring(ensi));
		/* number of significant digits, eg. the pp1 for 1.1 is 2 */
		int pp1 = -1;
		for (int i = eidx - 1; i >= 0; i--) {
			if (s.charAt(i) != '0') {
				pp1 = i;
				break;
			}
		}

		if (maxPrec < pp1) {
			maxPrec = pp1;
		}
		if (maxFractionDigits < pp1 - mag - 1) {
			maxFractionDigits = pp1 - mag - 1;
		}

		/* the number of 0 we can save */
		int n0;
		if (mag >= 0) {
			n0 = mag - maxPrec + 1;
		} else if (mag <= 0) {
			n0 = -mag;
		} else {
			n0 = 0;
		}

		String format;
		if (n0 < 4 && mag >= -4 && mag < 6) {
			format = "%." + maxFractionDigits + "f";
		} else {
			format = "%." + (maxPrec - 1) + "e";
		}
		return format;

	}

	/**
	 * Format the value to avoid the round error. The max precision is 6 significant digits.
	 */
	public static String toString(float v) {
		return toString(v, 6);
	}

	/**
	 * Format the value to the given significant digits limit. If the limit is 0, to avoid the round error, 6
	 * significant digits is applied.
	 */
	public static String toString(float v, int digits) {
		if (digits == 0) {
			digits = 6;
		}
		return String.format((Locale) null, calcFormatStr(v, digits), v);
	}

	/**
	 * Format the value to avoid the round error. The max precision is 15 significant digits.
	 */
	public static String toString(double v) {
		return toString(v, 15);
	}

	/**
	 * Format the value to the given significant digits limit. If the limit is 0, to avoid the round error, 15
	 * significant digits is applied.
	 */
	public static String toString(double v, int digits) {
		if (digits == 0) {
			digits = 15;
		}
		return String.format((Locale) null, calcFormatStr(v, digits), v);
	}

	/**
	 * Find a proper format to show the delta digit.
	 * 
	 * @param v
	 * @param delta
	 * @return the format string for java Formatter
	 */
	public static String calcDeltaFormatStr(double v, double delta) {

		if (v == 0 || Double.isNaN(v) || Double.isInfinite(v)) {
			return "%.0f";
		}

		int deltaExp = (int) Math.floor(Math.log10(delta));
		int aExp = (int) Math.floor(Math.log10(Math.abs(v)));
		// Significant digits - 1
		int precision = aExp - deltaExp;

		if (precision >= 0) {
			if (-4 <= aExp && aExp < 6) {
				if (deltaExp >= 0) {
					return "%.0f";
				} else {
					return "%." + -deltaExp + "f";
				}
			} else {
				return "%." + precision + "e";
			}
		} else {
			if (-4 <= deltaExp && deltaExp < 0) {
				return "%." + -deltaExp + "f";
			} else {
				return "0";
			}
		}
	}

}

 

Ads help maintain this website.