All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.meeuw.math.statistics.StatisticalLong Maven / Gradle / Ivy

/*
 *  Copyright 2022 Michiel Meeuwissen
 *
 *    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
 *
 *        https://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.meeuw.math.statistics;

import lombok.Getter;
import lombok.extern.java.Log;

import java.math.*;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.function.IntConsumer;
import java.util.function.LongConsumer;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.meeuw.math.*;
import org.meeuw.math.exceptions.IllegalLogarithmException;
import org.meeuw.math.exceptions.OverflowException;
import org.meeuw.math.temporal.StatisticalTemporal;
import org.meeuw.math.uncertainnumbers.UncertainNumber;
import org.meeuw.math.uncertainnumbers.field.*;

import static java.lang.Math.*;
import static org.meeuw.math.temporal.UncertainTemporal.Mode.LONG;

/**
 * Keeps tracks the sum and sum of squares of a sequence of long values.
 * 

* It can work in different {@link Mode}s, which indicates how the long value itself must be interpreted. * * @author Michiel Meeuwissen */ @Log public class StatisticalLong extends AbstractStatisticalDouble implements LongConsumer, IntConsumer, StatisticalTemporal { static final long SQUARE_SUM_FAILED = -1; private long sum = 0; private long squareSum = 0; @Getter @NonNull private final Mode mode; @Getter private long min = Long.MAX_VALUE; @Getter private long max = Long.MIN_VALUE; // keep sum around zero, and squareSum not too big. boolean autoGuess = true; /** * The guessed mean is used to offset all values when adding them to the {@link #getUncorrectedSum()} and {@link #getUncorrectedSumOfSquares()}. It defaults to the first value {@link #enter(long...)}ed. * A better value can be determined at any time using {@link #reguess()} */ @Getter private long guessedMean = 0; private double doubleOffset = 0d; public StatisticalLong() { this(null); } public StatisticalLong(@Nullable Mode mode) { this.mode = mode == null ? LONG : mode; } protected StatisticalLong(@NonNull Mode mode, long sum, long squareSum, int count, long guessedMean) { super(count); this.mode = mode; this.squareSum = squareSum; this.sum = sum; this.guessedMean = guessedMean; } @Override public StatisticalLong plus(double summand) { long rounded = DoubleUtils.round(summand); StatisticalLong result = plus(rounded); result.doubleOffset = (summand - (double) rounded); return result; } @Override public StatisticalLong copy() { StatisticalLong c = new StatisticalLong(mode, sum, squareSum, count, guessedMean); c.max = max; c.min = min; c.autoGuess = autoGuess; c.doubleOffset = doubleOffset; return c; } /** * Enters new value(s). * @param ds new values * @return this * @throws ArithmeticException If the sum goes over {@link Long#MAX_VALUE} */ public StatisticalLong enter(long... ds) { for(long d : ds) { if (autoGuess) { guessedMean = d; autoGuess = false; } min = Math.min(min, d); max = Math.max(max, d); d -= guessedMean; sum = addExact(sum, d); if (squareSum != SQUARE_SUM_FAILED) { try { squareSum = addExact(squareSum, multiplyExact(d, d)); } catch (ArithmeticException ae) { log.warning(ae.getMessage() + ": no square of sum any more. Standard deviation will not be available"); squareSum = SQUARE_SUM_FAILED; } } count++; } return this; } /** * Assuming that the measurement m is from the same set, add it to the already existing * statistics. * See also {@link StatisticalLong#plus(UncertainReal)} which is something entirely different. * @param m The other {@link StatisticalLong} which value must be entered into this one */ @Override public StatisticalLong enter(StatisticalLong m) { if (m.count == 0) { return this; } if (this.count == 0) { this.sum = m.sum; this.squareSum = m.squareSum; this.guessedMean = m.guessedMean; this.count = m.count; return this; } long diff = guessedMean - m.guessedMean; sum += m.sum - m.count * diff; squareSum += m.getSumOfSquares(diff); count += m.count; max = Math.max(max, m.max); min = Math.min(min, m.min); return this; } @Override public OptionalDouble optionalDoubleMean() { if (count == 0) { return OptionalDouble.empty(); } else { return OptionalDouble.of((double) guessedMean + ((double) sum / count) + doubleOffset); } } public Optional getOptionalBigMean() { if (count == 0){ return Optional.empty(); } else { if (doubleOffset != 0) { throw new IllegalStateException(); } return Optional.of( BigDecimal.valueOf(guessedMean) .add(BigDecimal.valueOf(sum).divide(BigDecimal.valueOf(count), NANO_PRECISION)) ); } } public long getRoundedMean() { return round(getMean()); } @Override public void accept(int value) { enter(value); } @Override public UncertainReal abs() { if (getValue() >= 0) { return this; } else { return new UncertainDoubleElement(getValue(), getUncertainty()).abs(); } } protected long round(double in) { long orderOfMagnitude = IntegerUtils.positivePow10(DoubleUtils.log10(getStandardDeviation())); return DoubleUtils.round(in) / orderOfMagnitude * orderOfMagnitude; } @Override public UncertainRealField getStructure() { return UncertainRealField.INSTANCE; } @Override public UncertainReal exp() { return immutableInstance(Math.exp(getValue()), getUncertainty()/* TODO */); } @Override @NonAlgebraic(reason = NonAlgebraic.Reason.ELEMENTS, value="Can't be taken of negative values") public UncertainReal ln() throws IllegalLogarithmException { UncertainNumber ln = operations().ln(getValue()); return immutableInstance( ln.getValue(), Math.max( ln.getUncertainty(), operations.lnUncertainty(ln.getValue(), getUncertainty()) ) ); } @Override public UncertainDoubleElement reciprocal() { UncertainNumber reciprocal = operations().reciprocal(getValue()); double v = 1d / getValue(); return immutableInstance( reciprocal.getValue(), Math.max(reciprocal.getUncertainty(), getFractionalUncertainty() * v + DoubleUtils.uncertaintyForDouble(v))); } public static MathContext NANO_PRECISION = new MathContext(6, RoundingMode.HALF_UP); public static long NANOS_IN_MILLIS = 1_000_000; public static BigDecimal BIG_NANOS_IN_MILLIS = BigDecimal.valueOf(NANOS_IN_MILLIS); @Override public Optional optionalInstantValue() { return getOptionalBigMean() .map(bd -> { final BigDecimal nanoTime = bd.multiply(BigDecimal.valueOf(NANOS_IN_MILLIS)); final BigDecimal[] bigDecimals = nanoTime.divideAndRemainder(BIG_NANOS_IN_MILLIS); return Instant.ofEpochMilli(bigDecimals[0].longValue()) .plusNanos( bigDecimals[1].longValue() ); }); } @Override public Optional optionalDurationValue() { return getOptionalBigMean() .map(bd -> { switch(mode) { case DURATION: return Duration .ofMillis(bd.longValue()) .plusNanos( bd.remainder(BigDecimal.ONE).multiply(BIG_NANOS_IN_MILLIS).longValue() ); case DURATION_NS: return Duration.ofNanos(bd.longValue()); default: throw new IllegalStateException(); } }); } /** * @throws OverflowException If sum of square overflowed */ @Override public double doubleStandardDeviation() { if (count == 0) { return Double.NaN; } double mean = ((double) sum) / count; if (squareSum == SQUARE_SUM_FAILED) { throw new OverflowException("square sum overflowed"); } double sq = ((double) squareSum / count) - mean * mean; return Math.sqrt(sq); } public long getSum() { return sum + count * guessedMean; } public long getSumOfSquares() { return getSumOfSquares(-1 * guessedMean); } protected long getSumOfSquares(long offset) { return addExact( subtractExact( squareSum, multiplyExact(multiplyExact(2, offset), sum)), multiplyExact( count, multiplyExact(offset, offset) ) ); } /** * This implementation keeps track of a 'guessedMean' value. The internal value {@link #getUncorrectedSumOfSquares()} is kept small like this, to avoid long overflows. *

* Calculating the {@link #doubleStandardDeviation()} ()} ()} happens using these 'uncorrected' (but smaller) versions, because the value should be the same. The actual sum of squares of all values is given by {@link #getSumOfSquares()}, which is the calculated but may more easily overflow. * @return the uncorrected sum // * @see #getGuessedMean() */ public long getUncorrectedSum() { return sum; } public long getUncorrectedSumOfSquares() { return squareSum; } @Override public StatisticalLong multiply(double d) { sum *= d; squareSum = Math.multiplyExact(squareSum, (long) (d * d)); guessedMean *= d; max = DoubleUtils.round(max * d); min = DoubleUtils.round(min * d); return this; } public StatisticalLong add(long d) { if (mode != Mode.LONG) { throw new IllegalStateException(); } return _add(d); } protected StatisticalLong _add(long d) { reguess(); long dcount = d * count; squareSum = addExact(squareSum, multiplyExact(d, addExact(dcount, multiplyExact(2, sum)))); sum += dcount; autoGuess = false; max = max + d; min = min + d; reguess(); return this; } public StatisticalLong add(Duration d) { switch(mode) { case DURATION: case INSTANT: return _add(d.toMillis()); case DURATION_NS: return _add(d.toNanos()); default: throw new IllegalStateException(); } } public StatisticalLong plus(Duration d) { return copy().add(d); } public StatisticalLong plus(long d) { return copy().add(d); } @Override public void accept(long value) { enter(value); } public void enter(Instant... instants) { if (mode != Mode.INSTANT) { throw new IllegalStateException(); } for (Instant i : instants) { long d = i.toEpochMilli(); accept(d); } } public void enter(Duration... duration) { if (! durationMode()) { throw new IllegalStateException(); } for (Duration d : duration) { accept(mode == Mode.DURATION ? d.toMillis() : d.toNanos()); } } protected boolean durationMode() { return mode == Mode.DURATION || mode == Mode.DURATION_NS; } @Override public void reset() { super.reset(); sum = 0; squareSum = 0; autoGuess = true; max = Long.MIN_VALUE; min = Long.MAX_VALUE; } /** * Uses the current {@link #getMean()} value as a new offset for values when keeping track of the sum and sum of squares of the values. * @return this */ public StatisticalLong reguess() { final long newGuess = longValue(); final long diff = newGuess - guessedMean; this.squareSum = getSumOfSquares(diff); this.sum = sum - count * diff; this.guessedMean = newGuess; return this; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy