Number.java Source code

Java tutorial

Introduction

Here is the source code for Number.java

Source

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * 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.
 */

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Various number-related routines and classes that are frequently used.
 * 
 * @author barclay
 */
public class Number {
    /**
     * Default number of decimal places to round to:
     */
    public static final int DECIMAL_PLACES = 2;

    /**
     * Round a float to the default number of decimal places.
     * 
     * @param value
     *          The value to round.
     * @return The rounded value as a float.
     * @see #DECIMAL_PLACES
     */
    public static float Round(float value) {
        return Round(value, DECIMAL_PLACES);
    }

    /**
     * Round a float to the specified number of decimal places.
     * 
     * @param value
     *          The value to round.
     * @param places
     *          The number of decimal points.
     * @return The rounded value as a float.
     */
    public static float Round(float value, int places) {
        float p = (float) Math.pow(10, places);
        value = value * p;
        float tmp = Math.round(value);
        return (float) tmp / p;
    }

    /**
     * Clamp a <code>value</code> to <code>min</code> or <code>max</code>,
     * inclusive.
     * 
     * @param value
     *          The value to clamp.
     * @param min
     *          The minimum value.
     * @param max
     *          The maximum value.
     * @return If <code>value</code> is greater than <code>max</code> then
     *         <code>max</code>, else if <code>value</code> is less than
     *         <code>min</code> then <code>min</code>, else <code>value</code>.
     */
    public static float Clamp(float value, float min, float max) {
        if (value > max)
            return max;
        if (value < min)
            return min;
        return value;
    }

    public enum TrendState {
        DOWN_15_GOOD, DOWN_15_BAD, DOWN_30_GOOD, DOWN_30_BAD, DOWN_45_GOOD, DOWN_45_BAD, UP_15_GOOD, UP_15_BAD, UP_30_GOOD, UP_30_BAD, UP_45_GOOD, UP_45_BAD, DOWN_15, UP_15, FLAT, FLAT_GOAL, UNKNOWN
    };

    public static TrendState getTrendState(float oldTrend, float newTrend, float goal, float sensitivity,
            float stdDev) {
        sensitivity = sensitivity * stdDev;
        float half = sensitivity / 2.0f;
        float quarter = sensitivity / 4.0f;

        if (oldTrend == newTrend) {
            // truly flat trend
            if (newTrend == goal)
                // perfect!
                return TrendState.FLAT_GOAL;
            else if (newTrend < goal && newTrend + quarter > goal)
                // flat near the goal!
                return TrendState.FLAT_GOAL;
            else if (newTrend > goal && newTrend - quarter < goal)
                // flat near the goal!
                return TrendState.FLAT_GOAL;
            else
                return TrendState.FLAT;
        } else if (oldTrend > newTrend) {
            // going down
            if (oldTrend > goal && newTrend > goal) {
                // toward goal
                if (oldTrend - newTrend > sensitivity)
                    // huge drop
                    return TrendState.DOWN_45_GOOD;
                else if (oldTrend - newTrend > half)
                    // big drop
                    return TrendState.DOWN_30_GOOD;
                else if (oldTrend - newTrend > quarter)
                    // little drop
                    return TrendState.DOWN_15_GOOD;
                else {
                    // under bounds for flat
                    if (newTrend - quarter < goal)
                        // flat near the goal!
                        return TrendState.FLAT_GOAL;
                    else
                        // flat elsewhere
                        return TrendState.FLAT;
                }
            } else if (oldTrend < goal && newTrend < goal) {
                // away from goal
                if (oldTrend - newTrend > sensitivity)
                    // huge drop
                    return TrendState.DOWN_45_BAD;
                else if (oldTrend - newTrend > half)
                    // big drop
                    return TrendState.DOWN_30_BAD;
                else if (oldTrend - newTrend > quarter)
                    // little drop
                    return TrendState.DOWN_15_BAD;
                else {
                    // under bounds for flat
                    if (newTrend + quarter > goal)
                        // flat near the goal!
                        return TrendState.FLAT_GOAL;
                    else
                        // flat elsewhere
                        return TrendState.FLAT;
                }
            } else
                // crossing goal line
                return TrendState.DOWN_15;
        } else if (oldTrend < newTrend) {
            // going up
            if (oldTrend < goal && newTrend < goal) {
                // toward goal
                if (newTrend - oldTrend > sensitivity)
                    // big rise
                    return TrendState.UP_45_GOOD;
                else if (newTrend - oldTrend > half)
                    // little rise
                    return TrendState.UP_30_GOOD;
                else if (newTrend - oldTrend > quarter)
                    // little rise
                    return TrendState.UP_15_GOOD;
                else {
                    // under bounds for flat
                    if (newTrend + quarter > goal)
                        // flat near the goal!
                        return TrendState.FLAT_GOAL;
                    else
                        // flat elsewhere
                        return TrendState.FLAT;
                }
            } else if (oldTrend > goal && newTrend > goal) {
                // away from goal
                if (newTrend - oldTrend > sensitivity)
                    // big rise
                    return TrendState.UP_45_BAD;
                else if (newTrend - oldTrend > half)
                    // little rise
                    return TrendState.UP_30_BAD;
                else if (newTrend - oldTrend > quarter)
                    // little rise
                    return TrendState.UP_15_BAD;
                else {
                    // under bounds for flat
                    if (newTrend - quarter < goal)
                        // flat near the goal!
                        return TrendState.FLAT_GOAL;
                    else
                        // flat elsewhere
                        return TrendState.FLAT;
                }
            } else {
                // crossing goal line
                return TrendState.UP_15;
            }
        } else
            // ??
            return TrendState.UNKNOWN;
    }

    /**
     * An exponentially smoothed weighted moving average. Not thread safe. Trend
     * is calculated thusly: <br>
     * <code>
     * trend[n] := trend[n-1] + smoothing_percentage * (value[n] - value[n-1])
     * </code>
     * 
     * @author barclay
     */
    public static class Trend {
        /**
         * Default smoothing percentage. This value will be used to scale the
         * previous entry by multiplying the previous entry's value and adding that
         * to the current value, so a smoothing percentage of 0.1 is 10%.
         */
        private static final float DEFAULT_SMOOTHING = 0.1f;

        private int mNEntries = 0;
        private float mSmoothing = DEFAULT_SMOOTHING;
        private float mSum = 0.0f;
        private boolean mFirst = true;
        private float mTrendLast = 0.0f;

        public float mTrendPrev = 0.0f;
        public float mTrend = 0.0f;
        public float mMin = 0.0f;
        public float mMax = 0.0f;
        public float mMean = 0.0f;

        /**
         * Default constructor. Set the smoothing percentage to the default.
         * 
         * @see #DEFAULT_SMOOTHING
         */
        public Trend() {
        }

        /**
         * Constructor
         * 
         * @param smoothing
         *          Sets the smoothing percentage to the specified value.
         * @see #DEFAULT_SMOOTHING
         */
        public Trend(float smoothing) {
            mSmoothing = smoothing;
        }

        /**
         * Copy Constructor
         * 
         * @param source
         *          Returns a new instance of Trend with all data set to source.
         * @see #DEFAULT_SMOOTHING
         */
        public Trend(Trend source) {
            mNEntries = source.mNEntries;
            mSmoothing = source.mSmoothing;
            mSum = source.mSum;
            mFirst = source.mFirst;
            mTrendLast = source.mTrendLast;
            mTrendPrev = source.mTrendPrev;
            mTrend = source.mTrend;
            mMin = source.mMin;
            mMax = source.mMax;
            mMean = source.mMean;
        }

        /**
         * Return the smoothing constant used by the Trend.
         * 
         * @return The smoothing constant as a float.
         */
        public float getSmoothing() {
            return mSmoothing;
        }

        /**
         * Update the trend with a new value. The value is implicitly "later" in the
         * sequence than all previous values.
         * 
         * @param val
         *          The value to add to the series.
         */
        public void update(float val) {
            mNEntries++;
            mSum += val;

            float oldMean = mMean;
            mMean += (val - oldMean) / mNEntries;

            // T(n) = T(n-1) + 0.1(V(n) - T(n-1))
            // : T(n) is the trend number for day n
            // : V(n) is the value number for day n
            // : S is the smoothing factor (default 0.1)
            if (mFirst == true) {
                mFirst = false;
                mTrend = val;
                mMin = val;
                mMax = val;
            } else {
                mTrend = mTrendLast + (mSmoothing * (val - mTrendLast));
                if (mTrend < mMin)
                    mMin = mTrend;
                if (mTrend > mMax)
                    mMax = mTrend;
            }
            mTrendPrev = mTrendLast;
            mTrendLast = mTrend;
        }
    }

    /**
     * Class for keeping track of various statistics intended to be updated
     * incrementally, including total number of updates, sum, mean, variance, and
     * standard deviation of the series. Not thread safe.
     * 
     * @author barclay
     */
    public static class RunningStats {
        public int mNDatapoints = 0;
        public int mNEntries = 0;
        public float mSum = 0.0f;
        public float mMean = 0.0f;
        public float mEntryMean = 0.0f;
        public float mVarSum = 0.0f;
        public float mVar = 0.0f;
        public float mStdDev = 0.0f;

        /**
         * Sole constructor. Initializes all stats to 0.
         */
        public RunningStats() {
        }

        /**
         * Copy Constructor
         * 
         * @param source
         *          Returns a new instance of RunningStats with all data set to
         *          source.
         */
        public RunningStats(RunningStats source) {
            mNDatapoints = source.mNDatapoints;
            mNEntries = source.mNEntries;
            mSum = source.mSum;
            mMean = source.mMean;
            mEntryMean = source.mEntryMean;
            mVarSum = source.mVarSum;
            mVar = source.mVar;
            mStdDev = source.mStdDev;
        }

        /**
         * update the statistics with the specified value. The value may be an
         * aggregate of several other values, as indicated by the second parameter.
         * A separate value will be recored for per-entry and and per-update means.
         * 
         * @param val
         *          The value to update the statistics with.
         * @param nEntries
         *          The number of entries this value is an aggregate of.
         */
        public void update(float val, int nEntries) {
            mNDatapoints++;
            mNEntries += nEntries;
            mSum += val;

            // Mean is calculated thusly to avoid float expansion and contraction,
            // which
            // would minimize accuracy.
            float oldMean = mMean;
            mMean += (val - oldMean) / mNDatapoints;
            mVarSum += (val - oldMean) * (val - mMean);
            mVar = mVarSum / mNDatapoints;
            mStdDev = (float) Math.sqrt(mVar);

            if (mNEntries > 0) {
                float oldEntryMean = mEntryMean;
                mEntryMean += (val - oldEntryMean) / mNEntries;
            }

            return;
        }
    }

    /**
     * Class for keeping track of the Standard Deviation over the last X values.
     * 
     * @author barclay
     */
    public static class WindowedStdDev {
        private Lock mLock;
        private ArrayList<Float> mValues;
        private int mHistory;

        /**
         * Sole constructor. Initializes all stats to 0.
         */
        public WindowedStdDev(int history) {
            mHistory = history;
            mValues = new ArrayList<Float>(mHistory);
            mLock = new ReentrantLock();
        }

        /**
         * Copy Constructor
         * 
         * @param source
         *          Returns a new instance of WindowedStdDev with all data set to
         *          source.
         */
        public WindowedStdDev(WindowedStdDev source) {
            mLock = new ReentrantLock();
            source.waitForLock();
            mHistory = source.mHistory;
            mValues = new ArrayList<Float>(mHistory);
            for (int i = 0; i < source.mValues.size(); i++) {
                try {
                    mValues.add(new Float(source.mValues.get(i)));
                } catch (IndexOutOfBoundsException e) {
                    break;
                }
            }
            source.unlock();
        }

        public void waitForLock() {
            while (lock() == false) {
            }
        }

        public boolean lock() {
            try {
                return mLock.tryLock(250L, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                return false;
            }
        }

        public void unlock() {
            mLock.unlock();
        }

        /**
         * update the std dev with the specified value.
         * 
         * @param val
         *          The value to update the statistics with.
         */
        public void update(float val) {
            waitForLock();
            mValues.add(new Float(val));
            if (mValues.size() > mHistory) {
                try {
                    mValues.remove(0);
                } catch (IndexOutOfBoundsException e) {
                    // nothing
                }
            }
            unlock();
            return;
        }

        /**
         * Fetch current Standard Deviation.
         * 
         * @param val
         *          The value to update the statistics with.
         */
        public float getStandardDev() {
            float mean = 0.0f;
            float meanSqr = 0.0f;
            float variance = 0.0f;
            float delta = 0.0f;
            float val = 0.0f;

            waitForLock();

            int nValues = mValues.size();
            for (int i = 0; i < nValues; i++) {
                try {
                    val = mValues.get(i);
                    delta = val - mean;
                    mean = mean + delta / (i + 1);
                    meanSqr = meanSqr + delta * (val - mean);
                } catch (IndexOutOfBoundsException e) {
                    break;
                }
            }
            unlock();

            variance = meanSqr / nValues;
            return (float) Math.sqrt(variance);
        }
    }

    /**
     * Performs a standard Pearson linear correlation on multiple series of data
     * at one, returning the results in a matrix.
     * 
     * @author barclay
     */
    public static class LinearMatrixCorrelation {
        private int mNumSeries = 0;
        private int mNEntries = 0;

        private Float[] mSum;
        private Float[] mMean;
        private Float[] mSumSquare;
        private Float[] mStdDev;
        private Float[][] mSumCoproduct;
        private Float[][] mCovariance;
        private Float[][] mCorrelation;

        /**
         * Constructor. Allocates internal data structures and zero's out the output
         * matrix data.
         * 
         * @param numSeries
         *          The number of series that will be included in each call to
         *          <code>update()</code>.
         */
        public LinearMatrixCorrelation(int numSeries) {
            mNumSeries = numSeries;

            mSum = new Float[numSeries];
            mMean = new Float[numSeries];
            mSumSquare = new Float[numSeries];
            mStdDev = new Float[numSeries];

            mSumCoproduct = new Float[numSeries][];
            mCovariance = new Float[numSeries][];
            mCorrelation = new Float[numSeries][];

            for (int i = 0; i < numSeries; i++) {
                mSum[i] = 0.0f;
                mMean[i] = 0.0f;
                mSumSquare[i] = 0.0f;
                mStdDev[i] = 0.0f;

                mSumCoproduct[i] = new Float[numSeries];
                mCovariance[i] = new Float[numSeries];
                mCorrelation[i] = new Float[numSeries];

                for (int j = 0; j < numSeries; j++) {
                    mSumCoproduct[i][j] = 0.0f;
                    mCovariance[i][j] = 0.0f;
                    mCorrelation[i][j] = 0.0f;
                }
            }
        }

        /**
         * Adds a vector (array) of values, 1 per series, to the calculations.
         * Graphically, all values are considered to be the at the same X (or Y)
         * position, and the values in the array argument denote the corresponding Y
         * (or X) value specific to the each series. Thus, the parameter x is an
         * array of values x[0 .. numSeries-1], one value per series, all of which
         * occured at the same "time." If a series has no such value, the entry in
         * the array should be null, as the length of the array must match the
         * <code>numSeries</code> past into the constructor at each invocation.
         * 
         * @param x
         *          The values for each series as Floats.
         * @return true if the input acceptable, else false if the length of
         *         <code>x[]</code> does not match <code>numSeries</code> or a value
         *         less than 1 was passed to the constructor.
         * @see LinearMatrixCorrelation#LinearMatrixCorrelation
         */
        public boolean update(Float[] x) {
            float oldMean;

            if (x.length != mNumSeries)
                return false;

            if (mNEntries + 1 > mNumSeries)
                return false;

            mNEntries++;

            float sweep = (mNEntries - 1.0f) / mNEntries;
            for (int i = 0; i < x.length; i++) {
                if (x[i] != null) {
                    mSum[i] += x[i];
                    oldMean = mMean[i];
                    mMean[i] += (x[i] - oldMean) / mNEntries;
                    mSumSquare[i] += (x[i] - oldMean) * (x[i] - mMean[i]);
                    mStdDev[i] = (float) Math.sqrt(mSumSquare[i] * sweep);
                }
            }

            for (int i = 0; i < x.length; i++) {
                if (x[i] != null) {
                    for (int j = i + 1; j < x.length; j++) {
                        if (x[j] != null) {
                            mSumCoproduct[i][j] += (x[i] - mMean[i]) * (x[j] - mMean[j]);
                            mCovariance[i][j] = mSumCoproduct[i][j] * sweep;
                            mCorrelation[i][j] = mCovariance[i][j] / (mStdDev[i] * mStdDev[j]);
                            mCorrelation[j][i] = mCovariance[i][j] / (mStdDev[i] * mStdDev[j]);
                            mCorrelation[i][j] = mCovariance[i][j] / (mStdDev[i] * mStdDev[j]);
                        }
                    }
                }
                mCorrelation[i][i] = 1.0f;
            }

            return true;
        }

        /**
         * Returns a reference to the correlation output matrix. The upper right
         * triangle is a mirror of the lower left, and the dividing diagonal the
         * identity correlation, i.e., 1.0f. In order to run through the matrix
         * without duplicates (e.g., processing both output[i][j] and output[j][i],
         * use a construct like: <br>
         * 
         * <pre>
         * for (int i = 0; i &lt; output.length; i++) {
         *   for (int j = i+1; j &lt; output.length; j++) {
         *     if (output[i][j] != null) { ... }
         *   }
         * }
         * </pre>
         * 
         * Note that any correlations that could not be calculated, either due to
         * lack of datapoints or structure of the data, will be null.
         * 
         * @return Float[][], the output correlation matrix.
         */
        public Float[][] getCorrelations() {
            return mCorrelation;
        }

        /**
         * Return a string interpretation of the linear correlation value. Note that
         * this is highly dependent on the data being correlated, and should be no
         * means be taken as gospel.
         * 
         * @param c
         *          The correlation value, should be -1.0f <= c <= 1.0f.
         * @return A string interpretation.
         */
        public static String correlationToString(float c) {
            if (c > 0.5)
                return "Strong";
            if (c < -0.5)
                return "Inverse Strong";
            if (c > 0.3)
                return "Medium";
            if (c < -0.3)
                return "Inverse Medium";
            return "Weak";
        }
    }
}