/* * Copyright 2004-2005 Revolution Systems Inc. * * Licensed 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 com.revolsys.util; import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.List; import java.util.Random; import com.revolsys.datatype.DataType; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.model.LineString; import com.revolsys.math.Angle; import com.revolsys.util.number.Doubles; /** * The MathUtil class is a utility class for handling integer, percent and * currency BigDecimal values. * * @author Paul Austin */ public interface MathUtil { int BYTES_IN_DOUBLE = 8; int BYTES_IN_INT = 4; int BYTES_IN_LONG = 8; int BYTES_IN_SHORT = 2; /** The number of cents in a dollar. */ BigDecimal CURRENCY_CENTS_PER_DOLLAR = getInteger(100); /** The scale for currency numbers. */ int CURRENCY_SCALE = 2; /** A 0 currency. */ BigDecimal CURRENCY0 = getCurrency(0); /** The scale for integer numbers. */ int INTEGER_SCALE = 0; /** A 0 integer. */ BigDecimal INTEGER0 = getInteger(0); /** A 1 integer. */ BigDecimal INTEGER1 = getInteger(1); String MAX_DOUBLE_STRING = Doubles.toString(Double.MAX_VALUE); String MIN_DOUBLE_STRING = Doubles.toString(-Double.MAX_VALUE); /** The scale for percent numbers. */ int PERCENT_SCALE = 4; /** A 0 percent. */ BigDecimal PERCENT0 = getPercent(0); /** A 1000 percent. */ BigDecimal PERCENT100 = getPercent(1); double PI_OVER_2 = Math.PI / 2.0; double PI_OVER_4 = Math.PI / 4.0; double PI_TIMES_2 = 2.0 * Math.PI; Random RANDOM = new Random(); /** * * @param left The left operand. * @param right The right operand. * @return The new amount. */ static BigDecimal add(final BigDecimal left, final Number right) { return left.add(new BigDecimal(DataTypes.toString(right))); } @SuppressWarnings("unchecked") static <V extends Number> V add(final Number left, final Number right, final DataType resultType) { final BigDecimal a = getBigDecimal(left); final BigDecimal b = getBigDecimal(right); final BigDecimal result = a.add(b); return (V)resultType.toObject(result); } static void append(final StringBuilder string, final double number) { if (Double.isNaN(number)) { string.append("NaN"); } else { DoubleFormatUtil.formatDoublePrecise(number, 19, 19, string); } } static double avg(final double a, final double b) { return (a + b) / 2d; } /** * Clamps a <tt>double</tt> value to a given range. * @param x the value to clamp * @param min the minimum value of the range * @param max the maximum value of the range * @return the clamped value */ static double clamp(final double x, final double min, final double max) { if (x < min) { return min; } if (x > max) { return max; } return x; } /** * Clamps an <tt>int</tt> value to a given range. * @param x the value to clamp * @param min the minimum value of the range * @param max the maximum value of the range * @return the clamped value */ static int clamp(final int x, final int min, final int max) { if (x < min) { return min; } if (x > max) { return max; } return x; } /** * Convert a BigDecimal amount to a currency string prefixed by the "$" sign. * * @param amount The BigDecimal amount. * @return The currency String */ static String currencyToString(final BigDecimal amount) { if (amount != null) { return "$" + getCurrency(amount); } else { return null; } } /** * Calculate the distance between two coordinates. * * @param x1 The first x coordinate. * @param y1 The first y coordinate. * @param x2 The second x coordinate. * @param y2 The second y coordinate. * @return The distance. */ static double distance(final double x1, final double y1, final double x2, final double y2) { final double dx = x2 - x1; final double dy = y2 - y1; final double distanceSquared = dx * dx + dy * dy; final double distance = Math.sqrt(distanceSquared); return distance; } static double distanceInt(final int x1, final int y1, final int x2, final int y2) { final long dx = x2 - x1; final int dy = y2 - y1; final long distanceSquared = dx * dx + dy * dy; final double distance = Math.sqrt(distanceSquared); return distance; } /** * Divide two currency amounts, setting the scale to {@link #CURRENCY_SCALE} * and rounding 1/2 u * * @param left The left operand. * @param right The right operand. * @return The new amount. */ static BigDecimal divideCurrency(final BigDecimal left, final BigDecimal right) { return left.divide(right, CURRENCY_SCALE, BigDecimal.ROUND_HALF_UP); } /** * Divide two percent amounts, setting the scale to {@link #CURRENCY_SCALE} * and rounding 1/2 u * * @param left The left operand. * @param right The right operand. * @return The new amount. */ static BigDecimal dividePercent(final BigDecimal left, final BigDecimal right) { return left.divide(right, PERCENT_SCALE, BigDecimal.ROUND_HALF_UP); } /** * Divide two percent amounts, setting the scale to {@link #CURRENCY_SCALE} * and rounding 1/2 u * * @param left The left operand. * @param right The right operand. * @return The new amount. */ static BigDecimal dividePercent(final double left, final double right) { return dividePercent(new BigDecimal(left), new BigDecimal(right)); } /** * Divide two percent amounts, setting the scale to {@link #CURRENCY_SCALE} * and rounding 1/2 u * * @param left The left operand. * @param right The right operand. * @return The new amount. */ static BigDecimal dividePercent(final double left, final int right) { return dividePercent(new BigDecimal(left), new BigDecimal(right)); } static String format(final String pattern, final Number number) { return new DecimalFormat(pattern).format(number); } /** * Code taken from DRA FME scripts to calculate angles. * * @param points * @param i1 * @param i2 * @return */ static double getAngle(final LineString points, final int i1, final int i2, final boolean start) { final double x1 = points.getX(i1); final double y1 = points.getY(i1); final double x2 = points.getX(i2); final double y2 = points.getY(i2); if (distance(x1, y1, x2, y2) == 0) { // TODO if (start) { if (i2 + 1 < points.getVertexCount()) { return getAngle(points, i1, i2 + 1, start); } } else { if (i1 - 1 > 0) { return getAngle(points, i1 - 1, i2, start); } } } return Angle.angleNorthDegrees(x1, y1, x2, y2); } static BigDecimal getBigDecimal(final Object value) { if (value == null) { return null; } else { try { final String stringValue = DataTypes.toString(value); return new BigDecimal(stringValue); } catch (final NumberFormatException e) { return null; } } } /** * Convert a BigDecimal amount into a currency BigDecimal. * * @param amount The ammount. * @return The currency. */ static BigDecimal getCurrency(final BigDecimal amount) { if (amount != null) { return amount.setScale(CURRENCY_SCALE, BigDecimal.ROUND_HALF_UP); } else { return null; } } /** * Convert a double amount into a currency BigDecimal. * * @param amount The ammount. * @return The currency. */ static BigDecimal getCurrency(final double amount) { return getCurrency(new BigDecimal(amount)); } /** * Convert a BigDecimal into an ineteger BigDecimal. * * @param value The BigDecimal value. * @return The ineteger BigDecimal. */ static BigDecimal getInteger(final BigDecimal value) { if (value != null) { return value.setScale(INTEGER_SCALE, BigDecimal.ROUND_DOWN); } else { return null; } } /** * Convert a int into an ineteger BigDecimal. * * @param value The int value. * @return The ineteger BigDecimal. */ static BigDecimal getInteger(final int value) { return getInteger(new BigDecimal((double)value)); } static Object getMaxValue(final Class<?> dataType) { if (dataType == Byte.class || dataType == Byte.TYPE) { return Byte.MAX_VALUE; } else if (dataType.equals(Short.class) || dataType.equals(Short.TYPE)) { return Short.MAX_VALUE; } else if (dataType.equals(Integer.class) || dataType.equals(Integer.TYPE)) { return Integer.MAX_VALUE; } else if (dataType.equals(Long.class) || dataType.equals(Long.TYPE)) { return Long.MAX_VALUE; } else { return null; } } static Object getMinValue(final Class<?> dataType) { if (dataType == Byte.class || dataType == Byte.TYPE) { return Byte.MIN_VALUE; } else if (dataType.equals(Short.class) || dataType.equals(Short.TYPE)) { return Short.MIN_VALUE; } else if (dataType.equals(Integer.class) || dataType.equals(Integer.TYPE)) { return Integer.MIN_VALUE; } else if (dataType.equals(Long.class) || dataType.equals(Long.TYPE)) { return Long.MIN_VALUE; } else { return null; } } static double getNorthClockwiseAngle(final double angle) { final double northAngle = (450 - angle) % 360; return northAngle; } /** * Convert a BigDecimal decimal percent (e.g. 0.5 is 50%) into an percent * BigDecimal. * * @param decimalPercent The decimal percent value. * @return The currency. */ static BigDecimal getPercent(final BigDecimal decimalPercent) { if (decimalPercent != null) { return decimalPercent.setScale(PERCENT_SCALE, BigDecimal.ROUND_HALF_UP); } else { return null; } } /** * Convert a double decimal percent (e.g. 0.5 is 50%) into an percent * BigDecimal. * * @param decimalPercent The decimal percent value. * @return The currency. */ static BigDecimal getPercent(final double decimalPercent) { return getPercent(new BigDecimal(decimalPercent)); } /** * Convert a String decimal percent (e.g. 0.5 is 50%) into an percent * BigDecimal. * * @param decimalPercent The decimal percent value. * @return The currency. */ static BigDecimal getPercent(final String decimalPercent) { return getPercent(new BigDecimal(decimalPercent)); } static int hashCode(final double d) { final long f = Double.doubleToLongBits(d); return (int)(f ^ f >>> 32); } /** sqrt(a^2 + b^2) without under/overflow. **/ static double hypot(final double a, final double b) { double r; if (Math.abs(a) > Math.abs(b)) { r = b / a; r = Math.abs(a) * Math.sqrt(1 + r * r); } else if (b != 0) { r = a / b; r = Math.abs(b) * Math.sqrt(1 + r * r); } else { r = 0.0; } return r; } /** * Convert a BigDecimal integer to a string. * * @param integer The BigDecimal integer. * @return The integer String */ static String integerToString(final BigDecimal integer) { return getInteger(integer).toString(); } static boolean isAcute(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3) { final double dx0 = x1 - x2; final double dy0 = y1 - y2; final double dx1 = x3 - x2; final double dy1 = y3 - y2; final double dotprod = dx0 * dx1 + dy0 * dy1; return dotprod > 0; } static boolean isNanOrInfinite(final double... values) { for (final double value : values) { if (!Double.isFinite(value)) { return true; } } return false; } static double max(final double... values) { double max = -Double.MAX_VALUE; for (final double value : values) { if (value > max) { max = value; } } return max; } static int max(final int... values) { int max = Integer.MIN_VALUE; for (final int value : values) { if (value > max) { max = value; } } return max; } static double max(final Iterable<? extends Number> numbers) { double max = -Double.MAX_VALUE; for (final Number number : numbers) { final double value = number.doubleValue(); if (value > max) { max = value; } } return max; } static int maxInt(final Iterable<Integer> numbers) { int min = Integer.MIN_VALUE; for (final Integer number : numbers) { final int value = number.intValue(); if (value > min) { min = value; } } return min; } static double midpoint(final double d1, final double d2) { return d1 + (d2 - d1) / 2; } static double min(final double... values) { double min = Double.MAX_VALUE; for (final double value : values) { if (value < min) { min = value; } } return min; } static int min(final int... values) { int min = Integer.MAX_VALUE; for (final int value : values) { if (value < min) { min = value; } } return min; } static double min(final Iterable<? extends Number> numbers) { double min = Double.MAX_VALUE; for (final Number number : numbers) { final double value = number.doubleValue(); if (value < min) { min = value; } } return min; } static int minInt(final Iterable<Integer> numbers) { int max = Integer.MAX_VALUE; for (final Integer number : numbers) { final int value = number.intValue(); if (value < max) { max = value; } } return max; } /** * Convert a BigDecimal decimal percent to a percent string suffixed by the * "%" sign. * * @param decimalPercent The BigDecimal percent. * @return The percent String */ static String percentToString(final BigDecimal decimalPercent) { return percentToString(decimalPercent, PERCENT_SCALE); } /** * Convert a BigDecimal decimal percent to a percent string suffixed by the * "%" sign with the specified number of decimal places. * * @param decimalPercent The BigDecimal percent. * @param scale The number of decimal places to show. * @return The percent String */ static String percentToString(final BigDecimal decimalPercent, final int scale) { if (decimalPercent != null) { final DecimalFormat format = new DecimalFormat(); format.setMinimumFractionDigits(0); format.setMaximumFractionDigits(scale); final String string = format.format( decimalPercent.multiply(new BigDecimal(100)).setScale(scale, BigDecimal.ROUND_HALF_UP)) + "%"; return string; } else { return null; } } static double pointLineDistance(final double x, final double y, final double x1, final double y1, final double x2, final double y2) { // if start==end, then use pt distance if (x1 == x2 && y1 == y2) { return distance(x, y, x1, y1); } // otherwise use comgraphics.algorithms Frequently Asked Questions method /* * (1) AC dot AB r = --------- ||AB||^2 r has the following meaning: r=0 P = * A r=1 P = B r<0 P is on the backward extension of AB r>1 P is on the * forward extension of AB 0<r<1 P is interior to AB */ final double r = ((x - x1) * (x2 - x1) + (y - y1) * (y2 - y1)) / ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); if (r <= 0.0) { return distance(x, y, x1, y1); } if (r >= 1.0) { return distance(x, y, x2, y2); } /* * (2) (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) s = ----------------------------- L^2 * Then the distance from C to P = |s|*L. */ final double s = ((y1 - y) * (x2 - x1) - (x1 - x) * (y2 - y1)) / ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); return Math.abs(s) * Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); } static boolean precisionEqual(final double value1, final double value2, final double scale) { if (Double.isNaN(value1) && Double.isNaN(value2)) { return true; } else if (Double.isInfinite(value1) || Double.isInfinite(value2)) { return true; } else { final double multiply1 = value1 * scale; final long rounded1 = Math.round(multiply1); final double multiply2 = value2 * scale; final long rounded2 = Math.round(multiply2); if (rounded1 == rounded2) { return true; } else { return false; } } } static double randomGaussian(final double mean, final double variance) { return mean + RANDOM.nextGaussian() * variance; } static double randomRange(final double min, final double max) { return min + RANDOM.nextDouble() * (max - min); } static byte sgn(final byte x) { if (x > 0) { return 1; } if (x < 0) { return -1; } return 0; } static int sgn(final double x) { if (x > 0.0D) { return 1; } if (x < 0.0D) { return -1; } return 0; } static int sgn(final float x) { if (x > 0.0F) { return 1; } if (x < 0.0F) { return -1; } return 0; } static int sgn(final int x) { if (x > 0) { return 1; } if (x < 0) { return -1; } return 0; } static int sgn(final long x) { if (x > 0L) { return 1; } if (x < 0L) { return -1; } return 0; } static short sgn(final short x) { if (x > 0) { return 1; } if (x < 0) { return -1; } return 0; } @SuppressWarnings("unchecked") static <V extends Number> V subtract(final Number left, final Number right, final DataType resultType) { if (left == null) { return null; } else if (right == null) { return (V)resultType.toObject(left); } else { final BigDecimal a = getBigDecimal(left); final BigDecimal b = getBigDecimal(right); final BigDecimal result = a.subtract(b); return (V)resultType.toObject(result); } } static Double toDouble(final Object value) { if (!Property.hasValue(value)) { throw new NumberFormatException("Numbers cannot be empty"); } else { final String string = value.toString(); if ("NaN".equalsIgnoreCase(string)) { return Double.NaN; } else if ("-Infinity".equalsIgnoreCase(string)) { return Double.NEGATIVE_INFINITY; } else if ("Infinity".equalsIgnoreCase(string)) { return Double.POSITIVE_INFINITY; } else { return Double.valueOf(string); } } } static double[] toDoubleArray(final List<? extends Number> numbers) { final double[] doubles = new double[numbers.size()]; for (int i = 0; i < doubles.length; i++) { final Number number = numbers.get(i); doubles[i] = number.doubleValue(); } return doubles; } static double[] toDoubleArray(final String... values) { final double[] doubles = new double[values.length]; for (int i = 0; i < doubles.length; i++) { doubles[i] = Double.valueOf(values[i]); } return doubles; } static double[] toDoubleArraySplit(final String value) { return toDoubleArray(value.split(",")); } static double[] toDoubleArraySplit(final String value, final String regex) { return toDoubleArray(value.split(regex)); } static Double toDoubleValue(final Object value) { if (!Property.hasValue(value)) { return null; } else { final String string = value.toString(); if ("NaN".equalsIgnoreCase(string)) { return Double.NaN; } else if ("-Infinity".equalsIgnoreCase(string)) { return Double.NEGATIVE_INFINITY; } else if ("Infinity".equalsIgnoreCase(string)) { return Double.POSITIVE_INFINITY; } else { return Double.valueOf(string); } } } static int toInt(final byte[] bytes, final int offset) { final byte b1 = bytes[offset]; final byte b2 = bytes[offset + 1]; final byte b3 = bytes[offset + 2]; final byte b4 = bytes[offset + 3]; return b1 << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | b4 & 0xFF; } static int[] toIntArray(final List<? extends Number> numbers) { final int[] ints = new int[numbers.size()]; for (int i = 0; i < ints.length; i++) { final Number number = numbers.get(i); ints[i] = number.intValue(); } return ints; } static int[] toIntArray(final String... values) { final int[] ints = new int[values.length]; for (int i = 0; i < ints.length; i++) { ints[i] = Integer.parseInt(values[i]); } return ints; } static int[] toIntArraySplit(final String value, final String regex) { return toIntArray(value.split(regex)); } static long toLong(final byte[] bytes, final int offset) { final long high = (long)toInt(bytes, offset) << 32; final long low = (long)toInt(bytes, offset + 4) << 32 >>> 32; return high | low; } }