/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.collect; import java.util.function.DoubleBinaryOperator; import java.util.function.DoubleUnaryOperator; import com.google.common.math.DoubleMath; /** * Contains utility methods for maths on double arrays. * <p> * This utility is used throughout the system when working with double arrays. */ public final class DoubleArrayMath { /** * An empty {@code double} array. */ public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; /** * An empty {@code Double} array. */ public static final Double[] EMPTY_DOUBLE_OBJECT_ARRAY = new Double[0]; /** * Restricted constructor. */ private DoubleArrayMath() { } //------------------------------------------------------------------------- /** * Converts a {@code double} array to a {@code Double} array. * * @param array the array to convert * @return the converted array */ public static Double[] toObject(double[] array) { if (array.length == 0) { return EMPTY_DOUBLE_OBJECT_ARRAY; } Double[] result = new Double[array.length]; for (int i = 0; i < array.length; i++) { result[i] = new Double(array[i]); } return result; } /** * Converts a {@code Double} array to a {@code double} array. * <p> * Throws an exception if null is found. * * @param array the array to convert * @return the converted array * @throws NullPointerException if null found */ public static double[] toPrimitive(Double[] array) { if (array.length == 0) { return EMPTY_DOUBLE_ARRAY; } double[] result = new double[array.length]; for (int i = 0; i < array.length; i++) { result[i] = array[i].doubleValue(); } return result; } //------------------------------------------------------------------------- /** * Calculates the sum total of all the elements in the array. * <p> * The input array is not mutated. * * @param array the array to sum * @return the sum total of all the elements */ public static double sum(double[] array) { double total = 0d; for (int i = 0; i < array.length; i++) { total += array[i]; } return total; } //------------------------------------------------------------------------- /** * Applies an addition to each element in the array, returning a new array. * <p> * The result is always a new array. The input array is not mutated. * * @param array the input array, not mutated * @param valueToAdd the value to add * @return the resulting array */ public static double[] applyAddition(double[] array, double valueToAdd) { double[] result = new double[array.length]; for (int i = 0; i < array.length; i++) { result[i] = array[i] + valueToAdd; } return result; } /** * Applies a multiplication to each element in the array, returning a new array. * <p> * The result is always a new array. The input array is not mutated. * * @param array the input array, not mutated * @param valueToMultiplyBy the value to multiply by * @return the resulting array */ public static double[] applyMultiplication(double[] array, double valueToMultiplyBy) { double[] result = new double[array.length]; for (int i = 0; i < array.length; i++) { result[i] = array[i] * valueToMultiplyBy; } return result; } /** * Applies an operator to each element in the array, returning a new array. * <p> * The result is always a new array. The input array is not mutated. * * @param array the input array, not mutated * @param operator the operator to use * @return the resulting array */ public static double[] apply(double[] array, DoubleUnaryOperator operator) { double[] result = new double[array.length]; for (int i = 0; i < array.length; i++) { result[i] = operator.applyAsDouble(array[i]); } return result; } //------------------------------------------------------------------------- /** * Adds a constant value to each element in the array by mutation. * <p> * The input array is mutated. * * @param array the array to mutate * @param valueToAdd the value to add */ public static void mutateByAddition(double[] array, double valueToAdd) { for (int i = 0; i < array.length; i++) { array[i] += valueToAdd; } } /** * Adds values in two arrays together, mutating the first array. * <p> * The arrays must be the same length. Each value in {@code arrayToAdd} is added to the value at the * corresponding index in {@code array}. * * @param array the array to mutate * @param arrayToAdd the array containing values to add */ public static void mutateByAddition(double[] array, double[] arrayToAdd) { int length = length(array, arrayToAdd); for (int i = 0; i < length; i++) { array[i] += arrayToAdd[i]; } } /** * Multiplies each element in the array by a value by mutation. * <p> * The input array is mutated. * * @param array the array to mutate * @param valueToMultiplyBy the value to multiply by */ public static void mutateByMultiplication(double[] array, double valueToMultiplyBy) { for (int i = 0; i < array.length; i++) { array[i] *= valueToMultiplyBy; } } /** * Multiplies values in two arrays, mutating the first array. * <p> * The arrays must be the same length. Each value in {@code array} is multiplied by the value at the * corresponding index in {@code arrayToMultiplyBy}. * * @param array the array to mutate * @param arrayToMultiplyBy the array containing values to multiply by */ public static void mutateByMultiplication(double[] array, double[] arrayToMultiplyBy) { int length = length(array, arrayToMultiplyBy); for (int i = 0; i < length; i++) { array[i] *= arrayToMultiplyBy[i]; } } /** * Mutates each element in the array using an operator by mutation. * <p> * The input array is mutated. * * @param array the array to mutate * @param operator the operator to use to perform the mutation */ public static void mutate(double[] array, DoubleUnaryOperator operator) { for (int i = 0; i < array.length; i++) { array[i] = operator.applyAsDouble(array[i]); } } //------------------------------------------------------------------------- /** * Combines two arrays, returning an array where each element is the sum of the two matching inputs. * <p> * Each element in the result will be the sum of the matching index in the two input arrays. * The two input arrays must have the same length. * <p> * For example: * <pre> * double[] array1 = {1, 5, 9}; * double[] array2 = {2, 3, 2}; * double[] result = DoubleArrayMath.combineByAddition(array1, array2); * // result contains {3, 8, 11} * </pre> * <p> * The result is always a new array. The input arrays are not mutated. * * @param array1 the first array * @param array2 the second array * @return an array combining the two input arrays using the plus operator */ public static double[] combineByAddition(double[] array1, double[] array2) { return combine(array1, array2, (a, b) -> a + b); } /** * Combines two arrays, returning an array where each element is the multiplication of the two matching inputs. * <p> * Each element in the result will be the multiplication of the matching index in the two input arrays. * The two input arrays must have the same length. * <p> * For example: * <pre> * double[] array1 = {1, 5, 9}; * double[] array2 = {2, 3, 4}; * double[] result = DoubleArrayMath.combineByMultiplication(array1, array2); * // result contains {2, 15, 36} * </pre> * <p> * The result is always a new array. The input arrays are not mutated. * * @param array1 the first array * @param array2 the second array * @return an array combining the two input arrays using the multiply operator */ public static double[] combineByMultiplication(double[] array1, double[] array2) { return combine(array1, array2, (a, b) -> a * b); } /** * Combines two arrays, returning an array where each element is the combination of the two matching inputs. * <p> * Each element in the result will be the combination of the matching index in the two * input arrays using the operator. The two input arrays must have the same length. * <p> * The result is always a new array. The input arrays are not mutated. * * @param array1 the first array * @param array2 the second array * @param operator the operator to use when combining values * @return an array combining the two input arrays using the operator */ public static double[] combine(double[] array1, double[] array2, DoubleBinaryOperator operator) { int length = length(array1, array2); double[] result = new double[length]; for (int i = 0; i < length; i++) { result[i] = operator.applyAsDouble(array1[i], array2[i]); } return result; } /** * Combines two arrays, returning an array where each element is the combination of the two matching inputs. * <p> * Each element in the result will be the combination of the matching index in the two * input arrays using the operator. * The result will have the length of the longest of the two inputs. * Where one array is longer than the other, the values from the longer array will be used. * <p> * The result is always a new array. The input arrays are not mutated. * * @param array1 the first array * @param array2 the second array * @param operator the operator to use when combining values * @return an array combining the two input arrays using the operator */ public static double[] combineLenient(double[] array1, double[] array2, DoubleBinaryOperator operator) { int len1 = array1.length; int len2 = array2.length; if (len1 == len2) { return combine(array1, array2, operator); } int size = Math.max(len1, len2); double[] result = new double[size]; for (int i = 0; i < size; i++) { if (i < len1) { if (i < len2) { result[i] = operator.applyAsDouble(array1[i], array2[i]); } else { result[i] = array1[i]; } } else { result[i] = array2[i]; } } return result; } //------------------------------------------------------------------------- /** * Compares each element in the array to zero within a tolerance. * <p> * An empty array returns true; * <p> * The input array is not mutated. * * @param array the array to check * @param tolerance the tolerance to use * @return true if the array is effectively equal to zero */ public static boolean fuzzyEqualsZero(double[] array, double tolerance) { for (int i = 0; i < array.length; i++) { if (!DoubleMath.fuzzyEquals(array[i], 0, tolerance)) { return false; } } return true; } /** * Compares each element in the first array to the matching index in the second array within a tolerance. * <p> * If the arrays differ in length, false is returned. * <p> * The input arrays are not mutated. * * @param array1 the first array to check * @param array2 the second array to check * @param tolerance the tolerance to use * @return true if the arrays are effectively equal */ public static boolean fuzzyEquals(double[] array1, double[] array2, double tolerance) { if (array1.length != array2.length) { return false; } for (int i = 0; i < array1.length; i++) { if (!DoubleMath.fuzzyEquals(array1[i], array2[i], tolerance)) { return false; } } return true; } //------------------------------------------------------------------------- /** * Sorts the two arrays, retaining the associated values with the sorted keys. * <p> * The two arrays must be the same size and represent a pair of key to value. * The sort order is determined by the array of keys. * The position of each value is changed to match that of the sorted keys. * <p> * The input arrays are mutated. * * @param keys the array of keys to sort * @param values the array of associated values to retain */ public static void sortPairs(double[] keys, double[] values) { int len1 = keys.length; if (len1 != values.length) { throw new IllegalArgumentException("Arrays cannot be sorted as they differ in length"); } dualArrayQuickSort(keys, values, 0, len1 - 1); } private static void dualArrayQuickSort(double[] keys, double[] values, int left, int right) { if (right > left) { int pivot = (left + right) >> 1; int pivotNewIndex = partition(keys, values, left, right, pivot); dualArrayQuickSort(keys, values, left, pivotNewIndex - 1); dualArrayQuickSort(keys, values, pivotNewIndex + 1, right); } } private static int partition(double[] keys, double[] values, int left, int right, int pivot) { double pivotValue = keys[pivot]; swap(keys, values, pivot, right); int storeIndex = left; for (int i = left; i < right; i++) { if (keys[i] <= pivotValue) { swap(keys, values, i, storeIndex); storeIndex++; } } swap(keys, values, storeIndex, right); return storeIndex; } private static void swap(double[] keys, double[] values, int first, int second) { double t = keys[first]; keys[first] = keys[second]; keys[second] = t; t = values[first]; values[first] = values[second]; values[second] = t; } //------------------------------------------------------------------------- /** * Sorts the two arrays, retaining the associated values with the sorted keys. * <p> * The two arrays must be the same size and represent a pair of key to value. * The sort order is determined by the array of keys. * The position of each value is changed to match that of the sorted keys. * <p> * The input arrays are mutated. * * @param <V> the type of the values * @param keys the array of keys to sort * @param values the array of associated values to retain */ public static <V> void sortPairs(double[] keys, V[] values) { int len1 = keys.length; if (len1 != values.length) { throw new IllegalArgumentException("Arrays cannot be sorted as they differ in length"); } dualArrayQuickSort(keys, values, 0, len1 - 1); } private static <T> void dualArrayQuickSort(double[] keys, T[] values, int left, int right) { if (right > left) { int pivot = (left + right) >> 1; int pivotNewIndex = partition(keys, values, left, right, pivot); dualArrayQuickSort(keys, values, left, pivotNewIndex - 1); dualArrayQuickSort(keys, values, pivotNewIndex + 1, right); } } private static <T> int partition(double[] keys, T[] values, int left, int right, int pivot) { double pivotValue = keys[pivot]; swap(keys, values, pivot, right); int storeIndex = left; for (int i = left; i < right; i++) { if (keys[i] <= pivotValue) { swap(keys, values, i, storeIndex); storeIndex++; } } swap(keys, values, storeIndex, right); return storeIndex; } private static <T> void swap(double[] keys, T[] values, int first, int second) { double x = keys[first]; keys[first] = keys[second]; keys[second] = x; T t = values[first]; values[first] = values[second]; values[second] = t; } /** * Return the array lengths if they are the same, otherwise throws an {@code IllegalArgumentException}. */ private static int length(double[] array1, double[] array2) { int len1 = array1.length; int len2 = array2.length; if (len1 != len2) { throw new IllegalArgumentException("Arrays cannot be combined as they differ in length"); } return len1; } }