Norm.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.commons.numbers.core;
/**
* <a href="https://en.wikipedia.org/wiki/Norm_(mathematics)">Norm</a> functions.
*
* <p>The implementations provide increased numerical accuracy.
* Algorithms primary source is the 2005 paper
* <a href="https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.1547">
* Accurate Sum and Dot Product</a> by Takeshi Ogita, Siegfried M. Rump,
* and Shin'ichi Oishi published in <em>SIAM J. Sci. Comput</em>.
*/
public enum Norm {
/**
* <a href="https://en.wikipedia.org/wiki/Norm_(mathematics)#Taxicab_norm_or_Manhattan_norm">
* Manhattan norm</a> (sum of the absolute values of the arguments).
*/
L1(Norm::manhattan, Norm::manhattan, Norm::manhattan),
/** Alias for {@link #L1}. */
MANHATTAN(L1),
/** <a href="https://en.wikipedia.org/wiki/Norm_(mathematics)#Euclidean_norm">Euclidean norm</a>. */
L2(Norm::euclidean, Norm::euclidean, Norm::euclidean),
/** Alias for {@link #L2}. */
EUCLIDEAN(L2),
/**
* <a href="https://en.wikipedia.org/wiki/Norm_(mathematics)#Maximum_norm_(special_case_of:_infinity_norm,_uniform_norm,_or_supremum_norm)">
* Maximum norm</a> (maximum of the absolute values of the arguments).
*/
LINF(Norm::maximum, Norm::maximum, Norm::maximum),
/** Alias for {@link #LINF}. */
MAXIMUM(LINF);
/**
* Threshold for scaling small numbers. This value is chosen such that doubles
* set to this value can be squared without underflow. Values less than this must
* be scaled up.
*/
private static final double SMALL_THRESH = 0x1.0p-511;
/**
* Threshold for scaling large numbers. This value is chosen such that 2^31 doubles
* set to this value can be squared and added without overflow. Values greater than
* this must be scaled down.
*/
private static final double LARGE_THRESH = 0x1.0p+496;
/**
* Threshold for scaling up a single value by {@link #SCALE_UP} without risking
* overflow when the value is squared.
*/
private static final double SAFE_SCALE_UP_THRESH = 0x1.0p-100;
/** Value used to scale down large numbers. */
private static final double SCALE_DOWN = 0x1.0p-600;
/** Value used to scale up small numbers. */
private static final double SCALE_UP = 0x1.0p+600;
/** Threshold for the difference between the exponents of two Euclidean 2D input values
* where the larger value dominates the calculation.
*/
private static final int EXP_DIFF_THRESHOLD_2D = 54;
/** Function of 2 arguments. */
private final Two two;
/** Function of 3 arguments. */
private final Three three;
/** Function of array argument. */
private final Array array;
/** Function of 2 arguments. */
@FunctionalInterface
private interface Two {
/**
* @param x Argument.
* @param y Argument.
* @return the norm.
*/
double of(double x, double y);
}
/** Function of 3 arguments. */
@FunctionalInterface
private interface Three {
/**
* @param x Argument.
* @param y Argument.
* @param z Argument.
* @return the norm.
*/
double of(double x, double y, double z);
}
/** Function of array argument. */
@FunctionalInterface
private interface Array {
/**
* @param v Array of arguments.
* @return the norm.
*/
double of(double[] v);
}
/**
* @param two Function of 2 arguments.
* @param three Function of 3 arguments.
* @param array Function of array argument.
*/
Norm(Two two,
Three three,
Array array) {
this.two = two;
this.three = three;
this.array = array;
}
/**
* @param alias Alternative name.
*/
Norm(Norm alias) {
this.two = alias.two;
this.three = alias.three;
this.array = alias.array;
}
/**
* Computes the norm.
*
* <p>Special cases:
* <ul>
* <li>If either value is {@link Double#NaN}, then the result is {@link Double#NaN}.</li>
* <li>If either value is infinite and the other value is not {@link Double#NaN}, then
* the result is {@link Double#POSITIVE_INFINITY}.</li>
* </ul>
*
* @param x Argument.
* @param y Argument.
* @return the norm.
*/
public final double of(double x,
double y) {
return two.of(x, y);
}
/**
* Computes the norm.
*
* <p>Special cases:
* <ul>
* <li>If any value is {@link Double#NaN}, then the result is {@link Double#NaN}.</li>
* <li>If any value is infinite and no value is not {@link Double#NaN}, then the
* result is {@link Double#POSITIVE_INFINITY}.</li>
* </ul>
*
* @param x Argument.
* @param y Argument.
* @param z Argument.
* @return the norm.
*/
public final double of(double x,
double y,
double z) {
return three.of(x, y, z);
}
/**
* Computes the norm.
*
* <p>Special cases:
* <ul>
* <li>If any value is {@link Double#NaN}, then the result is {@link Double#NaN}.</li>
* <li>If any value is infinite and no value is not {@link Double#NaN}, then the
* result is {@link Double#POSITIVE_INFINITY}.</li>
* </ul>
*
* @param v Argument.
* @return the norm.
* @throws IllegalArgumentException if the array is empty.
*/
public final double of(double[] v) {
ensureNonEmpty(v);
return array.of(v);
}
/** Computes the Manhattan norm.
*
* @param x first input value
* @param y second input value
* @return \(|x| + |y|\).
*
* @see #L1
* @see #MANHATTAN
* @see #of(double,double)
*/
private static double manhattan(final double x,
final double y) {
return Math.abs(x) + Math.abs(y);
}
/** Computes the Manhattan norm.
*
* @param x first input value
* @param y second input value
* @param z third input value
* @return \(|x| + |y| + |z|\)
*
* @see #L1
* @see #MANHATTAN
* @see #of(double,double,double)
*/
private static double manhattan(final double x,
final double y,
final double z) {
return Sum.of(Math.abs(x))
.add(Math.abs(y))
.add(Math.abs(z))
.getAsDouble();
}
/** Computes the Manhattan norm.
*
* @param v input values
* @return \(|v_0| + ... + |v_i|\)
*
* @see #L1
* @see #MANHATTAN
* @see #of(double[])
*/
private static double manhattan(final double[] v) {
final Sum sum = Sum.create();
for (final double d : v) {
sum.add(Math.abs(d));
}
return sum.getAsDouble();
}
/** Computes the Euclidean norm.
* This implementation handles possible overflow or underflow.
*
* <p><strong>Comparison with Math.hypot()</strong>
* While not guaranteed to return the same result, this method provides
* similar error bounds as {@link Math#hypot(double, double)} (and may run faster on
* some JVM).
*
* @param x first input
* @param y second input
* @return \(\sqrt{x^2 + y^2}\).
*
* @see #L2
* @see #EUCLIDEAN
* @see #of(double,double)
*/
private static double euclidean(final double x,
final double y) {
final double xabs = Math.abs(x);
final double yabs = Math.abs(y);
final double max;
final double min;
// the compare method considers NaN greater than other values, meaning that our
// check for if the max is finite later on will detect NaNs correctly
if (Double.compare(xabs, yabs) > 0) {
max = xabs;
min = yabs;
} else {
max = yabs;
min = xabs;
}
// if the max is not finite, then one of the inputs must not have
// been finite
if (!Double.isFinite(max)) {
// let the standard multiply operation determine whether to return NaN or infinite
return xabs * yabs;
} else if (Math.getExponent(max) - Math.getExponent(min) > EXP_DIFF_THRESHOLD_2D) {
// value is completely dominated by max; just return max
return max;
}
// compute the scale and rescale values
final double scale;
final double rescale;
if (max > LARGE_THRESH) {
scale = SCALE_DOWN;
rescale = SCALE_UP;
} else if (max < SAFE_SCALE_UP_THRESH) {
scale = SCALE_UP;
rescale = SCALE_DOWN;
} else {
scale = 1d;
rescale = 1d;
}
// initialise sum and compensation using scaled x
final double sx = xabs * scale;
double sum = sx * sx;
double comp = DD.twoSquareLow(sx, sum);
// add scaled y
final double sy = yabs * scale;
final double py = sy * sy;
comp += DD.twoSquareLow(sy, py);
final double sumPy = sum + py;
comp += DD.twoSumLow(sum, py, sumPy);
sum = sumPy;
return Math.sqrt(sum + comp) * rescale;
}
/** Computes the Euclidean norm.
* This implementation handles possible overflow or underflow.
*
* @param x first input
* @param y second input
* @param z third input
* @return \(\sqrt{x^2 + y^2 + z^2}\)
*
* @see #L2
* @see #EUCLIDEAN
* @see #of(double,double,double)
*/
private static double euclidean(final double x,
final double y,
final double z) {
final double xabs = Math.abs(x);
final double yabs = Math.abs(y);
final double zabs = Math.abs(z);
final double max = Math.max(Math.max(xabs, yabs), zabs);
// if the max is not finite, then one of the inputs must not have
// been finite
if (!Double.isFinite(max)) {
// let the standard multiply operation determine whether to
// return NaN or infinite
return xabs * yabs * zabs;
}
// compute the scale and rescale values
final double scale;
final double rescale;
if (max > LARGE_THRESH) {
scale = SCALE_DOWN;
rescale = SCALE_UP;
} else if (max < SAFE_SCALE_UP_THRESH) {
scale = SCALE_UP;
rescale = SCALE_DOWN;
} else {
scale = 1d;
rescale = 1d;
}
// initialise sum and compensation using scaled x
final double sx = xabs * scale;
double sum = sx * sx;
double comp = DD.twoSquareLow(sx, sum);
// add scaled y
final double sy = yabs * scale;
final double py = sy * sy;
comp += DD.twoSquareLow(sy, py);
final double sumPy = sum + py;
comp += DD.twoSumLow(sum, py, sumPy);
sum = sumPy;
// add scaled z
final double sz = zabs * scale;
final double pz = sz * sz;
comp += DD.twoSquareLow(sz, pz);
final double sumPz = sum + pz;
comp += DD.twoSumLow(sum, pz, sumPz);
sum = sumPz;
return Math.sqrt(sum + comp) * rescale;
}
/** Computes the Euclidean norm.
* This implementation handles possible overflow or underflow.
*
* @param v input values
* @return \(\sqrt{v_0^2 + ... + v_{n-1}^2}\).
*
* @see #L2
* @see #EUCLIDEAN
* @see #of(double[])
*/
private static double euclidean(final double[] v) {
// sum of big, normal and small numbers
double s1 = 0;
double s2 = 0;
double s3 = 0;
// sum compensation values
double c1 = 0;
double c2 = 0;
double c3 = 0;
for (int i = 0; i < v.length; ++i) {
final double x = Math.abs(v[i]);
if (!Double.isFinite(x)) {
// not finite; determine whether to return NaN or positive infinity
return euclideanNormSpecial(v, i);
} else if (x > LARGE_THRESH) {
// scale down
final double sx = x * SCALE_DOWN;
// compute the product and product compensation
final double p = sx * sx;
final double cp = DD.twoSquareLow(sx, p);
// compute the running sum and sum compensation
final double s = s1 + p;
final double cs = DD.twoSumLow(s1, p, s);
// update running totals
c1 += cp + cs;
s1 = s;
} else if (x < SMALL_THRESH) {
// scale up
final double sx = x * SCALE_UP;
// compute the product and product compensation
final double p = sx * sx;
final double cp = DD.twoSquareLow(sx, p);
// compute the running sum and sum compensation
final double s = s3 + p;
final double cs = DD.twoSumLow(s3, p, s);
// update running totals
c3 += cp + cs;
s3 = s;
} else {
// no scaling
// compute the product and product compensation
final double p = x * x;
final double cp = DD.twoSquareLow(x, p);
// compute the running sum and sum compensation
final double s = s2 + p;
final double cs = DD.twoSumLow(s2, p, s);
// update running totals
c2 += cp + cs;
s2 = s;
}
}
// The highest sum is the significant component. Add the next significant.
// Note that the "x * SCALE_DOWN * SCALE_DOWN" expressions must be executed
// in the order given. If the two scale factors are multiplied together first,
// they will underflow to zero.
if (s1 != 0) {
// add s1, s2, c1, c2
final double s2Adj = s2 * SCALE_DOWN * SCALE_DOWN;
final double sum = s1 + s2Adj;
final double comp = DD.twoSumLow(s1, s2Adj, sum) +
c1 + (c2 * SCALE_DOWN * SCALE_DOWN);
return Math.sqrt(sum + comp) * SCALE_UP;
} else if (s2 != 0) {
// add s2, s3, c2, c3
final double s3Adj = s3 * SCALE_DOWN * SCALE_DOWN;
final double sum = s2 + s3Adj;
final double comp = DD.twoSumLow(s2, s3Adj, sum) +
c2 + (c3 * SCALE_DOWN * SCALE_DOWN);
return Math.sqrt(sum + comp);
}
// add s3, c3
return Math.sqrt(s3 + c3) * SCALE_DOWN;
}
/** Special cases of non-finite input.
*
* @param v input vector
* @param start index to start examining the input vector from
* @return Euclidean norm special value
*/
private static double euclideanNormSpecial(final double[] v,
final int start) {
for (int i = start; i < v.length; ++i) {
if (Double.isNaN(v[i])) {
return Double.NaN;
}
}
return Double.POSITIVE_INFINITY;
}
/** Computes the maximum norm.
*
* @param x first input
* @param y second input
* @return \(\max{(|x|, |y|)}\).
*
* @see #LINF
* @see #MAXIMUM
* @see #of(double,double)
*/
private static double maximum(final double x,
final double y) {
return Math.max(Math.abs(x), Math.abs(y));
}
/** Computes the maximum norm.
*
* @param x first input
* @param y second input
* @param z third input
* @return \(\max{(|x|, |y|, |z|)}\).
*
* @see #LINF
* @see #MAXIMUM
* @see #of(double,double,double)
*/
private static double maximum(final double x,
final double y,
final double z) {
return Math.max(Math.abs(x),
Math.max(Math.abs(y),
Math.abs(z)));
}
/** Computes the maximum norm.
*
* @param v input values
* @return \(\max{(|v_0|, \ldots, |v_{n-1}|)}\)
*
* @see #LINF
* @see #MAXIMUM
* @see #of(double[])
*/
private static double maximum(final double[] v) {
double max = 0d;
for (final double d : v) {
max = Math.max(max, Math.abs(d));
}
return max;
}
/**
* @param a Array.
* @throws IllegalArgumentException for zero-size array.
*/
private static void ensureNonEmpty(double[] a) {
if (a.length == 0) {
throw new IllegalArgumentException("Empty array");
}
}
}