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

net.algart.matrices.morphology.Percentiler Maven / Gradle / Ivy

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2007-2024 Daniel Alievsky, AlgART Laboratory (http://algart.net)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.algart.matrices.morphology;

import net.algart.arrays.*;
import net.algart.math.functions.Func;

import java.util.Objects;

class Percentiler extends RankOperationProcessor {
    private final boolean optimizeGetData = OPTIMIZE_GET_DATA;
    private final boolean optimizeDirectArrays = OPTIMIZE_DIRECT_ARRAYS;
    private final boolean inlineOneLevel = INLINE_ONE_LEVEL;
    final boolean interpolated;

    Percentiler(ArrayContext context, boolean interpolated, int[] bitLevels) {
        super(context, bitLevels);
        this.interpolated = interpolated;
    }

    @Override
    PArray asProcessed(Class desiredType, PArray src, PArray[] additional,
        long[] dimensions, final long[] shifts, final long[] left, final long[] right)
    {
        if (additional.length == 0) {
            throw new IllegalArgumentException("One additional matrix is required (percentile indexes)");
        }
        assert shifts.length > 0;
        assert left.length == right.length;
        final boolean direct = optimizeDirectArrays &&
            src instanceof DirectAccessible && ((DirectAccessible)src).hasJavaArray();
        final PArray fPerc = additional[0] instanceof PIntegerArray || src instanceof PFloatingArray || interpolated ?
            additional[0] :
            Arrays.asFuncArray(true, Func.IDENTITY, LongArray.class, additional[0]);
        final PIntegerArray iPerc = fPerc instanceof PIntegerArray ? (PIntegerArray)fPerc : null;
        final ArrayPool ap =
            optimizeDirectArrays && (Arrays.isNCopies(fPerc) || SimpleMemoryModel.isSimpleArray(fPerc)) ? null :
                ArrayPool.getInstance(Arrays.SMM, fPerc.elementType(), BUFFER_BLOCK_SIZE);
        if (src instanceof BitArray) {
            final BitArray a = (BitArray)src;
            final HistogramCache histogramCache = new HistogramCache();
            return new AbstractBitArray(src.length(), true, src) {
                public boolean getBit(long index) {
                    long r;
                    final double rank;
                    if (iPerc != null) {
                        rank = -1.0;
                        r = iPerc.getLong(index);
                    } else {
                        rank = fPerc.getDouble(index);
                        checkNaN(rank);
                        r = (long)rank;
                    }
                    if (r >= shifts.length) {
                        return true;
                    }
                    int leftBar = 0;
                    for (long shift : shifts) {
                        long i = index - shift;
                        if (i < 0) {
                            i += length;
                        }
                        if (!a.getBit(i)) {
                            leftBar++;
                        }
                    }
                    if (r < 0) {
                        return leftBar == 0;
                    }
                    if (iPerc != null) {
                        return r >= leftBar;
                    } else {
                        if (r >= leftBar || r == shifts.length - 1) {
                            return leftBar < shifts.length; // inside the bar [1,2[ or the latest narrow bar
                        } else if (r < leftBar - 1) {
                            assert leftBar > 0; // because r>=0 and leftBar>=r+1
                            return false; // inside the bar [0,1-1/leftBar[
                        } else {
                            assert r >= 0 && r == leftBar - 1; // so, leftBar > 0
                            final int rightBar = shifts.length - leftBar;
                            final double leftStripe = leftBar == 1 ? 1.0 : 1.0 / leftBar;
                            final double fraction = rank - r;
                            final double newPreciseValue = (leftBar - 1 + fraction) * leftStripe;
                            final double weightedMeanStripe = leftBar == rightBar ?
                                leftStripe :
                                leftStripe + (rank - r) * (1.0 / rightBar - leftStripe);
                            assert weightedMeanStripe >= -0.001;
                            final double rangeCenter = newPreciseValue + 0.5 * Math.max(weightedMeanStripe, 1e-10);
                            // to be on the safe side, we guarantee that rangeCenter > newPreciseValue
                            // (any real stripe width is not less than 1/Integer.MAX_VALUE>1e-10)
                            return rangeCenter >= 1.0;
                        }
                    }
                }

                public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                    if (!optimizeGetData) {
                        super.getData(arrayPos, destArray, destArrayOffset, count);
                        return;
                    }
                    Objects.requireNonNull(destArray, "Null destArray argument");
                    checkRanges(length, arrayPos, count);
                    if (count == 0) {
                        return;
                    }
                    boolean[] dest = (boolean[])destArray;
                    UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                    final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                    int[] hist = histogramCache.get(arrayPos); // here we save initialization stage
                    if (hist == null) {
                        hist = new int[1];
                        for (long shift : shifts) {
                            long i = arrayPos - shift;
                            if (i < 0) {
                                i += length;
                            }
                            if (!a.getBit(i)) {
                                hist[0]++;
                            }
                        }
                    }
                    for (; count > 0; count -= bufLen) {
                        final int len = Math.min(bufLen, count);
                        if (ap != null) {
                            buf.copy(fPerc.subArr(arrayPos, len));
                        }
                        for (int k = 0; k < len; k++, destArrayOffset++) {
                            final boolean v;
                            if (iPerc != null) {
                                final long r = ap != null ? ((PIntegerArray)buf).getLong(k) : iPerc.getLong(arrayPos);
                                if (r < 0) {
                                    v = hist[0] == 0;
                                } else {
                                    v = r >= hist[0]; // in particular, if r >= shifts.length
                                }
                            } else { // to understand the code below, please see Histogram.moveToPreciseRank
                                final double rank = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                checkNaN(rank);
                                final long r = (long)rank;
                                final int leftBar = hist[0];
                                if (r >= shifts.length) {
                                    v = true;
                                } else if (r >= leftBar || r == shifts.length - 1) {
                                    v = leftBar < shifts.length; // inside the bar [1,2[ or the latest narrow bar
                                } else if (r < 0 || r < leftBar - 1) {
                                    v = leftBar == 0; // < 0 or inside the bar [0,1-1/leftBar[
                                } else {
                                    assert r >= 0 && r == leftBar - 1; // so, leftBar > 0
                                    final int rightBar = shifts.length - leftBar;
                                    final double leftStripe = leftBar == 1 ? 1.0 : 1.0 / leftBar;
                                    final double fraction = rank - r;
                                    final double newPreciseValue = (leftBar - 1 + fraction) * leftStripe;
                                    final double weightedMeanStripe = leftBar == rightBar ?
                                        leftStripe :
                                        leftStripe + (rank - r) * (1.0 / rightBar - leftStripe);
                                    assert weightedMeanStripe >= -0.001;
                                    final double rangeCenter = newPreciseValue + 0.5 * Math.max(weightedMeanStripe, 1e-10);
                                    // to be on the safe side, we guarantee that rangeCenter > newPreciseValue
                                    // (any real stripe width is not less than 1/Integer.MAX_VALUE>1e-10)
                                    v = rangeCenter >= 1.0;
                                }
                            }
                            dest[destArrayOffset] = v;
                            for (long shift : right) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                if (!a.getBit(i)) {
                                    hist[0]--;
                                    assert hist[0] >= 0 : "Unbalanced 0 and 1 bits";
                                }
                            }
                            arrayPos++;
                            if (arrayPos == length) {
                                arrayPos = 0;
                            }
                            for (long shift : left) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                if (!a.getBit(i)) {
                                    hist[0]++;
                                }
                            }
                        }
                    }
                    histogramCache.put(arrayPos, hist);
                    if (ap != null) {
                        ap.releaseArray(buf);
                    }
                }
            };
        }

        //[[Repeat() 0xFFFF          ==> 0xFF,,0xFFFF;;
        //           \(char\)0;      ==> 0;,,...;;
        //           (return\s+)\(char\) ==> $1,,$1;;
        //           char            ==> byte,,short;;
        //           (\w+\s+)getChar ==> int getByte,,int getShort;;
        //           Char            ==> Byte,,Short;;
        //           16              ==> 8,,16 ]]
        if (src instanceof CharArray) {
            final CharArray a = (CharArray)src;
            final int nab = Math.min(numberOfAnalyzedBits, 16);
            final int bs = 16 - nab;
            if (direct) {
                final char[] ja = (char[])((DirectAccessible)a).javaArray();
                final int jaOfs = ((DirectAccessible)a).javaArrayOffset();
                final boolean implementHere = inlineOneLevel && bitLevels.length == 0;
                if (implementHere && iPerc != null) { // char A: simple histogram, direct, without Histogram class
                    final HistogramCache histogramCache = new HistogramCache();
                    return new AbstractCharArray(src.length(), true, src) {
                        public char getChar(long index) {
                            long percentileIndex = iPerc.getLong(index);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                            }
                            int p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                            return (char)(p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            char[] dest = (char[])destArray;
                            UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            int[] hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = new int[1 << nab];
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                                }
                            }
                            int currentIValue = 0;
                            int currentIRank = 0; // number of elements less than currentIValue
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                    if (percentileIndex < 0) {
                                        percentileIndex = 0;
                                    } else if (percentileIndex > shifts.length) {
                                        percentileIndex = shifts.length;
                                    }
                                    if (percentileIndex < currentIRank) {
                                        do {
                                            --currentIValue;
                                            currentIRank -= hist[currentIValue];
                                        } while (percentileIndex < currentIRank);
                                        assert currentIRank >= 0;
                                    } else if (percentileIndex < shifts.length) {
                                        int b = hist[currentIValue];
                                        while (percentileIndex >= currentIRank + b) {
                                            currentIRank += b;
                                            ++currentIValue;
                                            b = hist[currentIValue];
                                        }
                                        assert currentIRank < shifts.length;
                                    } else if (currentIRank == shifts.length) {
                                        // special decreasing branch: percentileIndex == shifts.length
                                        assert currentIValue == hist.length || hist[currentIValue] == 0;
                                        assert currentIValue > 0;
                                        while (hist[currentIValue - 1] == 0) {
                                            --currentIValue;
                                            assert currentIValue > 0;
                                        }
                                    } else {
                                        // special increasing branch: percentileIndex == shifts.length
                                        assert currentIRank < shifts.length;
                                        do {
                                            currentIRank += hist[currentIValue];
                                            ++currentIValue;
                                        } while (currentIRank < shifts.length);
                                        assert currentIRank == shifts.length;
                                    }
                                    dest[destArrayOffset] = (char)(Math.min(currentIValue, hist.length - 1) << bs);
                                    for (long shift : right) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                        if (--hist[value] < 0) {
                                            throw new AssertionError("Disbalance in the histogram: negative number "
                                                + hist[value] + " of occurrences of " + value + " value");
                                        }
                                        if (value < currentIValue) {
                                            --currentIRank;
                                        }
                                    }
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (long shift : left) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                        ++hist[value];
                                        if (value < currentIValue) {
                                            ++currentIRank;
                                        }
                                    }
                                }
                            }
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else if (implementHere) { // char B: precise percentile, direct, without Histogram class
                    assert iPerc == null;
                    final HistogramCache histogramCache = new HistogramCache();
                    return new AbstractCharArray(src.length(), true, src) {
                        public char getChar(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                            }
                            int p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                            return (char)(p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            char[] dest = (char[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            int[] hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = new int[1 << nab];
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                                }
                            }
                            int currentIValue = 0;
                            int currentIRank = 0; // number of elements less than currentIValue
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    checkNaN(percentileIndex);
                                    final int r;
                                    if (percentileIndex < 0) {
                                        percentileIndex = r = 0;
                                    } else if (percentileIndex > shifts.length) {
                                        percentileIndex = r = shifts.length;
                                    } else {
                                        r = (int)percentileIndex;
                                    }
                                    if (r < currentIRank) {
                                        do {
                                            --currentIValue;
                                            currentIRank -= hist[currentIValue];
                                        } while (r < currentIRank);
                                        assert currentIRank >= 0;
                                    } else if (r < shifts.length) {
                                        int b = hist[currentIValue];
                                        while (r >= currentIRank + b) {
                                            currentIRank += b;
                                            ++currentIValue;
                                            b = hist[currentIValue];
                                        }
                                        assert currentIRank < shifts.length;
                                    } else if (currentIRank == shifts.length) {
                                        // special decreasing branch: r == shifts.length
                                        assert currentIValue == hist.length || hist[currentIValue] == 0;
                                        assert currentIValue > 0;
                                        while (hist[currentIValue - 1] == 0) {
                                            --currentIValue;
                                            assert currentIValue > 0;
                                        }
                                    } else {
                                        // special increasing branch: r == shifts.length
                                        assert currentIRank < shifts.length;
                                        do {
                                            currentIRank += hist[currentIValue];
                                            ++currentIValue;
                                        } while (currentIRank < shifts.length);
                                        assert currentIRank == shifts.length;
                                    }
                                    if (r < shifts.length - 1) { // the rightmost range (b-1)/b..1.0 is not corrected
                                        final int leftValue = currentIValue;
                                        final int indexInBar = r - currentIRank;
                                        final int leftBar = hist[currentIValue];
                                        assert indexInBar < leftBar;
                                        if (r != percentileIndex && indexInBar == leftBar - 1) {
                                            final int leftRank = currentIRank;
                                            final double leftPreciseValue = leftBar == 1 ? leftValue :
                                                leftValue + (double)indexInBar / (double)leftBar;
                                            currentIRank += leftBar;
                                            assert r + 1 == currentIRank;
                                            ++currentIValue;
                                            int rightBar = hist[currentIValue];
                                            while (rightBar == 0) {
                                                ++currentIValue;
                                                rightBar = hist[currentIValue];
                                            }
                                            assert currentIRank < shifts.length;
                                            final double newPreciseValue = leftPreciseValue
                                                + (percentileIndex - r) * (currentIValue - leftPreciseValue);
                                            final double leftStripe = leftBar == 1 ? 1.0 : 1.0 / leftBar;
                                            final double weightedMeanStripe = leftBar == rightBar ?
                                                leftStripe :
                                                leftStripe + (percentileIndex - r) * (1.0 / rightBar - leftStripe);
                                            assert weightedMeanStripe >= -0.001;
                                            final double rangeCenter = newPreciseValue
                                                + 0.5 * Math.max(weightedMeanStripe, 1e-10);
                                            // to be on the safe side, we guarantee that newValue+1 > newPreciseValue
                                            // (any real stripe width is not less than 1/Integer.MAX_VALUE>1e-10)
                                            currentIValue = (int)rangeCenter;
                                            if (currentIValue == leftValue) {
                                                currentIRank = leftRank;
                                            }
                                        }
                                    }
                                    dest[destArrayOffset] = (char)(Math.min(currentIValue, hist.length - 1) << bs);
                                    for (long shift : right) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                        if (--hist[value] < 0) {
                                            throw new AssertionError("Disbalance in the histogram: negative number "
                                                + hist[value] + " of occurrences of " + value + " value");
                                        }
                                        if (value < currentIValue) {
                                            --currentIRank;
                                        }
                                    }
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (long shift : left) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                        ++hist[value];
                                        if (value < currentIValue) {
                                            ++currentIRank;
                                        }
                                    }
                                }
                            }
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else if (iPerc != null) { // char C: simple percentile, direct
                    assert !implementHere;
                    final HistogramCache histogramCache = new HistogramCache();
                    final JArrayPool indexesPool = JArrayPool.getInstance(int.class, left.length);
                    return new AbstractCharArray(src.length(), true, src) {
                        public char getChar(long index) {
                            long percentileIndex = iPerc.getLong(index);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                            }
                            int p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                            return (char)(p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            char[] dest = (char[])destArray;
                            UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include((ja[jaOfs + (int)i] & 0xFFFF) >> bs); // &0xFFFF for preprocessing
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                    hist.moveToIRank(percentileIndex);
                                    dest[destArrayOffset] = (char)(Math.min(hist.currentIValue(),
                                        hist.length() - 1) << bs);
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else { // char D: precise percentile, direct
                    assert iPerc == null;
                    final HistogramCache histogramCache = new HistogramCache();
                    final JArrayPool indexesPool = JArrayPool.getInstance(int.class, left.length);
                    return new AbstractCharArray(src.length(), true, src) {
                        public char getChar(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                            }
                            int p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                            return (char)(p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            char[] dest = (char[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include((ja[jaOfs + (int)i] & 0xFFFF) >> bs); // &0xFFFF for preprocessing
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    hist.moveToPreciseRank(percentileIndex);
                                    dest[destArrayOffset] = (char)(Math.min(hist.currentIValue(),
                                        hist.length() - 1) << bs);
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                }
            } else if (iPerc != null) { // char E: simple percentile, indirect
                final HistogramCache histogramCache = new HistogramCache();
                return new AbstractCharArray(src.length(), true, src) {
                    public char getChar(long index) {
                        long percentileIndex = iPerc.getLong(index);
                        int[] hist = new int[1 << nab];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            hist[a.getInt(i) >> bs]++;
                        }
                        int p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                        return (char)(p << bs);
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        char[] dest = (char[])destArray;
                        UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist.include(a.getInt(i) >> bs);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                hist.moveToIRank(percentileIndex);
                                dest[destArrayOffset] = (char)(Math.min(hist.currentIValue(),
                                    hist.length() - 1) << bs);
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.exclude(a.getInt(i) >> bs);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include(a.getInt(i) >> bs);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            } else { // char F: precise percentile, indirect
                assert iPerc == null;
                final HistogramCache histogramCache = new HistogramCache();
                return new AbstractCharArray(src.length(), true, src) {
                    public char getChar(long index) {
                        double percentileIndex = fPerc.getDouble(index);
                        checkNaN(percentileIndex);
                        int[] hist = new int[1 << nab];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            hist[a.getInt(i) >> bs]++;
                        }
                        int p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                        return (char)(p << bs);
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        char[] dest = (char[])destArray;
                        UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist.include(a.getInt(i) >> bs);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                hist.moveToPreciseRank(percentileIndex);
                                dest[destArrayOffset] = (char)(Math.min(hist.currentIValue(),
                                    hist.length() - 1) << bs);
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.exclude(a.getInt(i) >> bs);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include(a.getInt(i) >> bs);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            }
        }
        //[[Repeat.AutoGeneratedStart !! Auto-generated: NOT EDIT !! ]]
        if (src instanceof ByteArray) {
            final ByteArray a = (ByteArray)src;
            final int nab = Math.min(numberOfAnalyzedBits, 8);
            final int bs = 8 - nab;
            if (direct) {
                final byte[] ja = (byte[])((DirectAccessible)a).javaArray();
                final int jaOfs = ((DirectAccessible)a).javaArrayOffset();
                final boolean implementHere = inlineOneLevel && bitLevels.length == 0;
                if (implementHere && iPerc != null) { // byte A: simple histogram, direct, without Histogram class
                    final HistogramCache histogramCache = new HistogramCache();
                    return new AbstractByteArray(src.length(), true, src) {
                        public int getByte(long index) {
                            long percentileIndex = iPerc.getLong(index);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFF) >> bs]++; // &0xFF for preprocessing
                            }
                            int p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                            return (p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            byte[] dest = (byte[])destArray;
                            UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            int[] hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = new int[1 << nab];
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist[(ja[jaOfs + (int)i] & 0xFF) >> bs]++; // &0xFF for preprocessing
                                }
                            }
                            int currentIValue = 0;
                            int currentIRank = 0; // number of elements less than currentIValue
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                    if (percentileIndex < 0) {
                                        percentileIndex = 0;
                                    } else if (percentileIndex > shifts.length) {
                                        percentileIndex = shifts.length;
                                    }
                                    if (percentileIndex < currentIRank) {
                                        do {
                                            --currentIValue;
                                            currentIRank -= hist[currentIValue];
                                        } while (percentileIndex < currentIRank);
                                        assert currentIRank >= 0;
                                    } else if (percentileIndex < shifts.length) {
                                        int b = hist[currentIValue];
                                        while (percentileIndex >= currentIRank + b) {
                                            currentIRank += b;
                                            ++currentIValue;
                                            b = hist[currentIValue];
                                        }
                                        assert currentIRank < shifts.length;
                                    } else if (currentIRank == shifts.length) {
                                        // special decreasing branch: percentileIndex == shifts.length
                                        assert currentIValue == hist.length || hist[currentIValue] == 0;
                                        assert currentIValue > 0;
                                        while (hist[currentIValue - 1] == 0) {
                                            --currentIValue;
                                            assert currentIValue > 0;
                                        }
                                    } else {
                                        // special increasing branch: percentileIndex == shifts.length
                                        assert currentIRank < shifts.length;
                                        do {
                                            currentIRank += hist[currentIValue];
                                            ++currentIValue;
                                        } while (currentIRank < shifts.length);
                                        assert currentIRank == shifts.length;
                                    }
                                    dest[destArrayOffset] = (byte)(Math.min(currentIValue, hist.length - 1) << bs);
                                    for (long shift : right) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFF) >> bs;
                                        if (--hist[value] < 0) {
                                            throw new AssertionError("Disbalance in the histogram: negative number "
                                                + hist[value] + " of occurrences of " + value + " value");
                                        }
                                        if (value < currentIValue) {
                                            --currentIRank;
                                        }
                                    }
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (long shift : left) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFF) >> bs;
                                        ++hist[value];
                                        if (value < currentIValue) {
                                            ++currentIRank;
                                        }
                                    }
                                }
                            }
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else if (implementHere) { // byte B: precise percentile, direct, without Histogram class
                    assert iPerc == null;
                    final HistogramCache histogramCache = new HistogramCache();
                    return new AbstractByteArray(src.length(), true, src) {
                        public int getByte(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFF) >> bs]++; // &0xFF for preprocessing
                            }
                            int p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                            return (p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            byte[] dest = (byte[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            int[] hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = new int[1 << nab];
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist[(ja[jaOfs + (int)i] & 0xFF) >> bs]++; // &0xFF for preprocessing
                                }
                            }
                            int currentIValue = 0;
                            int currentIRank = 0; // number of elements less than currentIValue
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    checkNaN(percentileIndex);
                                    final int r;
                                    if (percentileIndex < 0) {
                                        percentileIndex = r = 0;
                                    } else if (percentileIndex > shifts.length) {
                                        percentileIndex = r = shifts.length;
                                    } else {
                                        r = (int)percentileIndex;
                                    }
                                    if (r < currentIRank) {
                                        do {
                                            --currentIValue;
                                            currentIRank -= hist[currentIValue];
                                        } while (r < currentIRank);
                                        assert currentIRank >= 0;
                                    } else if (r < shifts.length) {
                                        int b = hist[currentIValue];
                                        while (r >= currentIRank + b) {
                                            currentIRank += b;
                                            ++currentIValue;
                                            b = hist[currentIValue];
                                        }
                                        assert currentIRank < shifts.length;
                                    } else if (currentIRank == shifts.length) {
                                        // special decreasing branch: r == shifts.length
                                        assert currentIValue == hist.length || hist[currentIValue] == 0;
                                        assert currentIValue > 0;
                                        while (hist[currentIValue - 1] == 0) {
                                            --currentIValue;
                                            assert currentIValue > 0;
                                        }
                                    } else {
                                        // special increasing branch: r == shifts.length
                                        assert currentIRank < shifts.length;
                                        do {
                                            currentIRank += hist[currentIValue];
                                            ++currentIValue;
                                        } while (currentIRank < shifts.length);
                                        assert currentIRank == shifts.length;
                                    }
                                    if (r < shifts.length - 1) { // the rightmost range (b-1)/b..1.0 is not corrected
                                        final int leftValue = currentIValue;
                                        final int indexInBar = r - currentIRank;
                                        final int leftBar = hist[currentIValue];
                                        assert indexInBar < leftBar;
                                        if (r != percentileIndex && indexInBar == leftBar - 1) {
                                            final int leftRank = currentIRank;
                                            final double leftPreciseValue = leftBar == 1 ? leftValue :
                                                leftValue + (double)indexInBar / (double)leftBar;
                                            currentIRank += leftBar;
                                            assert r + 1 == currentIRank;
                                            ++currentIValue;
                                            int rightBar = hist[currentIValue];
                                            while (rightBar == 0) {
                                                ++currentIValue;
                                                rightBar = hist[currentIValue];
                                            }
                                            assert currentIRank < shifts.length;
                                            final double newPreciseValue = leftPreciseValue
                                                + (percentileIndex - r) * (currentIValue - leftPreciseValue);
                                            final double leftStripe = leftBar == 1 ? 1.0 : 1.0 / leftBar;
                                            final double weightedMeanStripe = leftBar == rightBar ?
                                                leftStripe :
                                                leftStripe + (percentileIndex - r) * (1.0 / rightBar - leftStripe);
                                            assert weightedMeanStripe >= -0.001;
                                            final double rangeCenter = newPreciseValue
                                                + 0.5 * Math.max(weightedMeanStripe, 1e-10);
                                            // to be on the safe side, we guarantee that newValue+1 > newPreciseValue
                                            // (any real stripe width is not less than 1/Integer.MAX_VALUE>1e-10)
                                            currentIValue = (int)rangeCenter;
                                            if (currentIValue == leftValue) {
                                                currentIRank = leftRank;
                                            }
                                        }
                                    }
                                    dest[destArrayOffset] = (byte)(Math.min(currentIValue, hist.length - 1) << bs);
                                    for (long shift : right) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFF) >> bs;
                                        if (--hist[value] < 0) {
                                            throw new AssertionError("Disbalance in the histogram: negative number "
                                                + hist[value] + " of occurrences of " + value + " value");
                                        }
                                        if (value < currentIValue) {
                                            --currentIRank;
                                        }
                                    }
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (long shift : left) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFF) >> bs;
                                        ++hist[value];
                                        if (value < currentIValue) {
                                            ++currentIRank;
                                        }
                                    }
                                }
                            }
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else if (iPerc != null) { // byte C: simple percentile, direct
                    assert !implementHere;
                    final HistogramCache histogramCache = new HistogramCache();
                    final JArrayPool indexesPool = JArrayPool.getInstance(int.class, left.length);
                    return new AbstractByteArray(src.length(), true, src) {
                        public int getByte(long index) {
                            long percentileIndex = iPerc.getLong(index);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFF) >> bs]++; // &0xFF for preprocessing
                            }
                            int p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                            return (p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            byte[] dest = (byte[])destArray;
                            UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include((ja[jaOfs + (int)i] & 0xFF) >> bs); // &0xFF for preprocessing
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                    hist.moveToIRank(percentileIndex);
                                    dest[destArrayOffset] = (byte)(Math.min(hist.currentIValue(),
                                        hist.length() - 1) << bs);
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFF) >> bs;
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFF) >> bs;
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else { // byte D: precise percentile, direct
                    assert iPerc == null;
                    final HistogramCache histogramCache = new HistogramCache();
                    final JArrayPool indexesPool = JArrayPool.getInstance(int.class, left.length);
                    return new AbstractByteArray(src.length(), true, src) {
                        public int getByte(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFF) >> bs]++; // &0xFF for preprocessing
                            }
                            int p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                            return (p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            byte[] dest = (byte[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include((ja[jaOfs + (int)i] & 0xFF) >> bs); // &0xFF for preprocessing
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    hist.moveToPreciseRank(percentileIndex);
                                    dest[destArrayOffset] = (byte)(Math.min(hist.currentIValue(),
                                        hist.length() - 1) << bs);
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFF) >> bs;
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFF) >> bs;
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                }
            } else if (iPerc != null) { // byte E: simple percentile, indirect
                final HistogramCache histogramCache = new HistogramCache();
                return new AbstractByteArray(src.length(), true, src) {
                    public int getByte(long index) {
                        long percentileIndex = iPerc.getLong(index);
                        int[] hist = new int[1 << nab];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            hist[a.getInt(i) >> bs]++;
                        }
                        int p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                        return (p << bs);
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        byte[] dest = (byte[])destArray;
                        UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist.include(a.getInt(i) >> bs);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                hist.moveToIRank(percentileIndex);
                                dest[destArrayOffset] = (byte)(Math.min(hist.currentIValue(),
                                    hist.length() - 1) << bs);
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.exclude(a.getInt(i) >> bs);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include(a.getInt(i) >> bs);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            } else { // byte F: precise percentile, indirect
                assert iPerc == null;
                final HistogramCache histogramCache = new HistogramCache();
                return new AbstractByteArray(src.length(), true, src) {
                    public int getByte(long index) {
                        double percentileIndex = fPerc.getDouble(index);
                        checkNaN(percentileIndex);
                        int[] hist = new int[1 << nab];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            hist[a.getInt(i) >> bs]++;
                        }
                        int p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                        return (p << bs);
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        byte[] dest = (byte[])destArray;
                        UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist.include(a.getInt(i) >> bs);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                hist.moveToPreciseRank(percentileIndex);
                                dest[destArrayOffset] = (byte)(Math.min(hist.currentIValue(),
                                    hist.length() - 1) << bs);
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.exclude(a.getInt(i) >> bs);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include(a.getInt(i) >> bs);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            }
        }
        if (src instanceof ShortArray) {
            final ShortArray a = (ShortArray)src;
            final int nab = Math.min(numberOfAnalyzedBits, 16);
            final int bs = 16 - nab;
            if (direct) {
                final short[] ja = (short[])((DirectAccessible)a).javaArray();
                final int jaOfs = ((DirectAccessible)a).javaArrayOffset();
                final boolean implementHere = inlineOneLevel && bitLevels.length == 0;
                if (implementHere && iPerc != null) { // short A: simple histogram, direct, without Histogram class
                    final HistogramCache histogramCache = new HistogramCache();
                    return new AbstractShortArray(src.length(), true, src) {
                        public int getShort(long index) {
                            long percentileIndex = iPerc.getLong(index);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                            }
                            int p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                            return (p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            short[] dest = (short[])destArray;
                            UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            int[] hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = new int[1 << nab];
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                                }
                            }
                            int currentIValue = 0;
                            int currentIRank = 0; // number of elements less than currentIValue
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                    if (percentileIndex < 0) {
                                        percentileIndex = 0;
                                    } else if (percentileIndex > shifts.length) {
                                        percentileIndex = shifts.length;
                                    }
                                    if (percentileIndex < currentIRank) {
                                        do {
                                            --currentIValue;
                                            currentIRank -= hist[currentIValue];
                                        } while (percentileIndex < currentIRank);
                                        assert currentIRank >= 0;
                                    } else if (percentileIndex < shifts.length) {
                                        int b = hist[currentIValue];
                                        while (percentileIndex >= currentIRank + b) {
                                            currentIRank += b;
                                            ++currentIValue;
                                            b = hist[currentIValue];
                                        }
                                        assert currentIRank < shifts.length;
                                    } else if (currentIRank == shifts.length) {
                                        // special decreasing branch: percentileIndex == shifts.length
                                        assert currentIValue == hist.length || hist[currentIValue] == 0;
                                        assert currentIValue > 0;
                                        while (hist[currentIValue - 1] == 0) {
                                            --currentIValue;
                                            assert currentIValue > 0;
                                        }
                                    } else {
                                        // special increasing branch: percentileIndex == shifts.length
                                        assert currentIRank < shifts.length;
                                        do {
                                            currentIRank += hist[currentIValue];
                                            ++currentIValue;
                                        } while (currentIRank < shifts.length);
                                        assert currentIRank == shifts.length;
                                    }
                                    dest[destArrayOffset] = (short)(Math.min(currentIValue, hist.length - 1) << bs);
                                    for (long shift : right) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                        if (--hist[value] < 0) {
                                            throw new AssertionError("Disbalance in the histogram: negative number "
                                                + hist[value] + " of occurrences of " + value + " value");
                                        }
                                        if (value < currentIValue) {
                                            --currentIRank;
                                        }
                                    }
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (long shift : left) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                        ++hist[value];
                                        if (value < currentIValue) {
                                            ++currentIRank;
                                        }
                                    }
                                }
                            }
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else if (implementHere) { // short B: precise percentile, direct, without Histogram class
                    assert iPerc == null;
                    final HistogramCache histogramCache = new HistogramCache();
                    return new AbstractShortArray(src.length(), true, src) {
                        public int getShort(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                            }
                            int p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                            return (p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            short[] dest = (short[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            int[] hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = new int[1 << nab];
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                                }
                            }
                            int currentIValue = 0;
                            int currentIRank = 0; // number of elements less than currentIValue
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    checkNaN(percentileIndex);
                                    final int r;
                                    if (percentileIndex < 0) {
                                        percentileIndex = r = 0;
                                    } else if (percentileIndex > shifts.length) {
                                        percentileIndex = r = shifts.length;
                                    } else {
                                        r = (int)percentileIndex;
                                    }
                                    if (r < currentIRank) {
                                        do {
                                            --currentIValue;
                                            currentIRank -= hist[currentIValue];
                                        } while (r < currentIRank);
                                        assert currentIRank >= 0;
                                    } else if (r < shifts.length) {
                                        int b = hist[currentIValue];
                                        while (r >= currentIRank + b) {
                                            currentIRank += b;
                                            ++currentIValue;
                                            b = hist[currentIValue];
                                        }
                                        assert currentIRank < shifts.length;
                                    } else if (currentIRank == shifts.length) {
                                        // special decreasing branch: r == shifts.length
                                        assert currentIValue == hist.length || hist[currentIValue] == 0;
                                        assert currentIValue > 0;
                                        while (hist[currentIValue - 1] == 0) {
                                            --currentIValue;
                                            assert currentIValue > 0;
                                        }
                                    } else {
                                        // special increasing branch: r == shifts.length
                                        assert currentIRank < shifts.length;
                                        do {
                                            currentIRank += hist[currentIValue];
                                            ++currentIValue;
                                        } while (currentIRank < shifts.length);
                                        assert currentIRank == shifts.length;
                                    }
                                    if (r < shifts.length - 1) { // the rightmost range (b-1)/b..1.0 is not corrected
                                        final int leftValue = currentIValue;
                                        final int indexInBar = r - currentIRank;
                                        final int leftBar = hist[currentIValue];
                                        assert indexInBar < leftBar;
                                        if (r != percentileIndex && indexInBar == leftBar - 1) {
                                            final int leftRank = currentIRank;
                                            final double leftPreciseValue = leftBar == 1 ? leftValue :
                                                leftValue + (double)indexInBar / (double)leftBar;
                                            currentIRank += leftBar;
                                            assert r + 1 == currentIRank;
                                            ++currentIValue;
                                            int rightBar = hist[currentIValue];
                                            while (rightBar == 0) {
                                                ++currentIValue;
                                                rightBar = hist[currentIValue];
                                            }
                                            assert currentIRank < shifts.length;
                                            final double newPreciseValue = leftPreciseValue
                                                + (percentileIndex - r) * (currentIValue - leftPreciseValue);
                                            final double leftStripe = leftBar == 1 ? 1.0 : 1.0 / leftBar;
                                            final double weightedMeanStripe = leftBar == rightBar ?
                                                leftStripe :
                                                leftStripe + (percentileIndex - r) * (1.0 / rightBar - leftStripe);
                                            assert weightedMeanStripe >= -0.001;
                                            final double rangeCenter = newPreciseValue
                                                + 0.5 * Math.max(weightedMeanStripe, 1e-10);
                                            // to be on the safe side, we guarantee that newValue+1 > newPreciseValue
                                            // (any real stripe width is not less than 1/Integer.MAX_VALUE>1e-10)
                                            currentIValue = (int)rangeCenter;
                                            if (currentIValue == leftValue) {
                                                currentIRank = leftRank;
                                            }
                                        }
                                    }
                                    dest[destArrayOffset] = (short)(Math.min(currentIValue, hist.length - 1) << bs);
                                    for (long shift : right) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                        if (--hist[value] < 0) {
                                            throw new AssertionError("Disbalance in the histogram: negative number "
                                                + hist[value] + " of occurrences of " + value + " value");
                                        }
                                        if (value < currentIValue) {
                                            --currentIRank;
                                        }
                                    }
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (long shift : left) {
                                        long i = arrayPos - shift;
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int value = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                        ++hist[value];
                                        if (value < currentIValue) {
                                            ++currentIRank;
                                        }
                                    }
                                }
                            }
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else if (iPerc != null) { // short C: simple percentile, direct
                    assert !implementHere;
                    final HistogramCache histogramCache = new HistogramCache();
                    final JArrayPool indexesPool = JArrayPool.getInstance(int.class, left.length);
                    return new AbstractShortArray(src.length(), true, src) {
                        public int getShort(long index) {
                            long percentileIndex = iPerc.getLong(index);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                            }
                            int p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                            return (p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            short[] dest = (short[])destArray;
                            UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include((ja[jaOfs + (int)i] & 0xFFFF) >> bs); // &0xFFFF for preprocessing
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                    hist.moveToIRank(percentileIndex);
                                    dest[destArrayOffset] = (short)(Math.min(hist.currentIValue(),
                                        hist.length() - 1) << bs);
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else { // short D: precise percentile, direct
                    assert iPerc == null;
                    final HistogramCache histogramCache = new HistogramCache();
                    final JArrayPool indexesPool = JArrayPool.getInstance(int.class, left.length);
                    return new AbstractShortArray(src.length(), true, src) {
                        public int getShort(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist[(ja[jaOfs + (int)i] & 0xFFFF) >> bs]++; // &0xFFFF for preprocessing
                            }
                            int p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                            return (p << bs);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            short[] dest = (short[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include((ja[jaOfs + (int)i] & 0xFFFF) >> bs); // &0xFFFF for preprocessing
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    hist.moveToPreciseRank(percentileIndex);
                                    dest[destArrayOffset] = (short)(Math.min(hist.currentIValue(),
                                        hist.length() - 1) << bs);
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        barIndexes[j] = (ja[jaOfs + (int)i] & 0xFFFF) >> bs;
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                }
            } else if (iPerc != null) { // short E: simple percentile, indirect
                final HistogramCache histogramCache = new HistogramCache();
                return new AbstractShortArray(src.length(), true, src) {
                    public int getShort(long index) {
                        long percentileIndex = iPerc.getLong(index);
                        int[] hist = new int[1 << nab];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            hist[a.getInt(i) >> bs]++;
                        }
                        int p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                        return (p << bs);
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        short[] dest = (short[])destArray;
                        UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist.include(a.getInt(i) >> bs);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                hist.moveToIRank(percentileIndex);
                                dest[destArrayOffset] = (short)(Math.min(hist.currentIValue(),
                                    hist.length() - 1) << bs);
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.exclude(a.getInt(i) >> bs);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include(a.getInt(i) >> bs);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            } else { // short F: precise percentile, indirect
                assert iPerc == null;
                final HistogramCache histogramCache = new HistogramCache();
                return new AbstractShortArray(src.length(), true, src) {
                    public int getShort(long index) {
                        double percentileIndex = fPerc.getDouble(index);
                        checkNaN(percentileIndex);
                        int[] hist = new int[1 << nab];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            hist[a.getInt(i) >> bs]++;
                        }
                        int p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                        return (p << bs);
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        short[] dest = (short[])destArray;
                        UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                hist.include(a.getInt(i) >> bs);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                hist.moveToPreciseRank(percentileIndex);
                                dest[destArrayOffset] = (short)(Math.min(hist.currentIValue(),
                                    hist.length() - 1) << bs);
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.exclude(a.getInt(i) >> bs);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    hist.include(a.getInt(i) >> bs);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            }
        }
        //[[Repeat.AutoGeneratedEnd]]

        //[[Repeat() int\s+getInt     ==> long getLong;;
        //           getInt           ==> getLong;;
        //           Integer(?!Array) ==> Long;;
        //           IntArray         ==> LongArray;;
        //           int(\s+[ABCD]\:|\[\]\s+ja|\[\]\s+dest|\s+v\b|\s+p\b) ==> long$1;;
        //           \(int\[\]\)(?!indexesPool) ==> (long[]);;
        //           (v\s*\>\>\s*bs)  ==> (int)($1);;
        //           31               ==> 63 ]]
        if (src instanceof IntArray) {
            final IntArray a = (IntArray)src;
            final int nab = Math.min(numberOfAnalyzedBits, 30);
            final int bs = 31 - nab;
            final HistogramCache histogramCache = new HistogramCache();
            if (direct) {
                final int[] ja = (int[])((DirectAccessible)a).javaArray();
                final int jaOfs = ((DirectAccessible)a).javaArrayOffset();
                final JArrayPool indexesPool = JArrayPool.getInstance(int.class, left.length);
                if (iPerc != null) { // int A: simple percentile, direct
                    return new AbstractIntArray(src.length(), true, src) {
                        public int getInt(long index) {
                            long percentileIndex = iPerc.getLong(index);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                int v = ja[jaOfs + (int)i];
                                if (v < 0) {
                                    v = 0;
                                }
                                hist[v >> bs]++;
                            }
                            int p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                            return p << bs;
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            int[] dest = (int[])destArray;
                            UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    int v = ja[jaOfs + (int)i];
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.include(v >> bs);
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                    hist.moveToIRank(percentileIndex);
                                    int p = Math.min(hist.currentIValue(), hist.length() - 1);
                                    dest[destArrayOffset] = p << bs;
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int v = ja[jaOfs + (int)i];
                                        if (v < 0) {
                                            v = 0;
                                        }
                                        barIndexes[j] = v >> bs;
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int v = ja[jaOfs + (int)i];
                                        if (v < 0) {
                                            v = 0;
                                        }
                                        barIndexes[j] = v >> bs;
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else { // int B: precise percentile, direct
                    assert iPerc == null;
                    return new AbstractIntArray(src.length(), true, src) {
                        public int getInt(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                int v = ja[jaOfs + (int)i];
                                if (v < 0) {
                                    v = 0;
                                }
                                hist[v >> bs]++;
                            }
                            int p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                            return p << bs;
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            int[] dest = (int[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    int v = ja[jaOfs + (int)i];
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.include(v >> bs);
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    hist.moveToPreciseRank(percentileIndex);
                                    int p = Math.min(hist.currentIValue(), hist.length() - 1);
                                    dest[destArrayOffset] = p << bs;
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int v = ja[jaOfs + (int)i];
                                        if (v < 0) {
                                            v = 0;
                                        }
                                        barIndexes[j] = v >> bs;
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        int v = ja[jaOfs + (int)i];
                                        if (v < 0) {
                                            v = 0;
                                        }
                                        barIndexes[j] = v >> bs;
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                }
            } else if (iPerc != null) { // int C: simple percentile, indirect
                return new AbstractIntArray(src.length(), true, src) {
                    public int getInt(long index) {
                        long percentileIndex = iPerc.getLong(index);
                        int[] hist = new int[1 << nab];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            int v = a.getInt(i);
                            if (v < 0) {
                                v = 0;
                            }
                            hist[v >> bs]++;
                        }
                        int p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                        return p << bs;
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        int[] dest = (int[])destArray;
                        UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                int v = a.getInt(i);
                                if (v < 0) {
                                    v = 0;
                                }
                                hist.include(v >> bs);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                hist.moveToIRank(percentileIndex);
                                int p = Math.min(hist.currentIValue(), hist.length() - 1);
                                dest[destArrayOffset] = p << bs;
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    int v = a.getInt(i);
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.exclude(v >> bs);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    int v = a.getInt(i);
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.include(v >> bs);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            } else { // int D: precise percentile, indirect
                assert iPerc == null;
                return new AbstractIntArray(src.length(), true, src) {
                    public int getInt(long index) {
                        double percentileIndex = fPerc.getDouble(index);
                        checkNaN(percentileIndex);
                        int[] hist = new int[1 << nab];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            int v = a.getInt(i);
                            if (v < 0) {
                                v = 0;
                            }
                            hist[v >> bs]++;
                        }
                        int p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                        return p << bs;
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        int[] dest = (int[])destArray;
                        UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                int v = a.getInt(i);
                                if (v < 0) {
                                    v = 0;
                                }
                                hist.include(v >> bs);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                hist.moveToPreciseRank(percentileIndex);
                                int p = Math.min(hist.currentIValue(), hist.length() - 1);
                                dest[destArrayOffset] = p << bs;
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    int v = a.getInt(i);
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.exclude(v >> bs);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    int v = a.getInt(i);
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.include(v >> bs);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            }
        }
        //[[Repeat.AutoGeneratedStart !! Auto-generated: NOT EDIT !! ]]
        if (src instanceof LongArray) {
            final LongArray a = (LongArray)src;
            final int nab = Math.min(numberOfAnalyzedBits, 30);
            final int bs = 63 - nab;
            final HistogramCache histogramCache = new HistogramCache();
            if (direct) {
                final long[] ja = (long[])((DirectAccessible)a).javaArray();
                final int jaOfs = ((DirectAccessible)a).javaArrayOffset();
                final JArrayPool indexesPool = JArrayPool.getInstance(int.class, left.length);
                if (iPerc != null) { // long A: simple percentile, direct
                    return new AbstractLongArray(src.length(), true, src) {
                        public long getLong(long index) {
                            long percentileIndex = iPerc.getLong(index);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                long v = ja[jaOfs + (int)i];
                                if (v < 0) {
                                    v = 0;
                                }
                                hist[(int)(v >> bs)]++;
                            }
                            long p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                            return p << bs;
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            long[] dest = (long[])destArray;
                            UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    long v = ja[jaOfs + (int)i];
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.include((int)(v >> bs));
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                    hist.moveToIRank(percentileIndex);
                                    long p = Math.min(hist.currentIValue(), hist.length() - 1);
                                    dest[destArrayOffset] = p << bs;
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        long v = ja[jaOfs + (int)i];
                                        if (v < 0) {
                                            v = 0;
                                        }
                                        barIndexes[j] = (int)(v >> bs);
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        long v = ja[jaOfs + (int)i];
                                        if (v < 0) {
                                            v = 0;
                                        }
                                        barIndexes[j] = (int)(v >> bs);
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else { // long B: precise percentile, direct
                    assert iPerc == null;
                    return new AbstractLongArray(src.length(), true, src) {
                        public long getLong(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[1 << nab];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                long v = ja[jaOfs + (int)i];
                                if (v < 0) {
                                    v = 0;
                                }
                                hist[(int)(v >> bs)]++;
                            }
                            long p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                            return p << bs;
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            long[] dest = (long[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    long v = ja[jaOfs + (int)i];
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.include((int)(v >> bs));
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    hist.moveToPreciseRank(percentileIndex);
                                    long p = Math.min(hist.currentIValue(), hist.length() - 1);
                                    dest[destArrayOffset] = p << bs;
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        long v = ja[jaOfs + (int)i];
                                        if (v < 0) {
                                            v = 0;
                                        }
                                        barIndexes[j] = (int)(v >> bs);
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        long v = ja[jaOfs + (int)i];
                                        if (v < 0) {
                                            v = 0;
                                        }
                                        barIndexes[j] = (int)(v >> bs);
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                }
            } else if (iPerc != null) { // long C: simple percentile, indirect
                return new AbstractLongArray(src.length(), true, src) {
                    public long getLong(long index) {
                        long percentileIndex = iPerc.getLong(index);
                        int[] hist = new int[1 << nab];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            long v = a.getLong(i);
                            if (v < 0) {
                                v = 0;
                            }
                            hist[(int)(v >> bs)]++;
                        }
                        long p = Math.min(Histogram.iValue(hist, percentileIndex), hist.length - 1);
                        return p << bs;
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        long[] dest = (long[])destArray;
                        UpdatablePIntegerArray buf = ap == null ? null : (UpdatablePIntegerArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                long v = a.getLong(i);
                                if (v < 0) {
                                    v = 0;
                                }
                                hist.include((int)(v >> bs));
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                long percentileIndex = ap != null ? buf.getLong(k) : iPerc.getLong(arrayPos);
                                hist.moveToIRank(percentileIndex);
                                long p = Math.min(hist.currentIValue(), hist.length() - 1);
                                dest[destArrayOffset] = p << bs;
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    long v = a.getLong(i);
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.exclude((int)(v >> bs));
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    long v = a.getLong(i);
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.include((int)(v >> bs));
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            } else { // long D: precise percentile, indirect
                assert iPerc == null;
                return new AbstractLongArray(src.length(), true, src) {
                    public long getLong(long index) {
                        double percentileIndex = fPerc.getDouble(index);
                        checkNaN(percentileIndex);
                        int[] hist = new int[1 << nab];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            long v = a.getLong(i);
                            if (v < 0) {
                                v = 0;
                            }
                            hist[(int)(v >> bs)]++;
                        }
                        long p = Math.min(Histogram.iPreciseValue(hist, percentileIndex), hist.length - 1);
                        return p << bs;
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        long[] dest = (long[])destArray;
                        UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(1 << nab, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                long v = a.getLong(i);
                                if (v < 0) {
                                    v = 0;
                                }
                                hist.include((int)(v >> bs));
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                hist.moveToPreciseRank(percentileIndex);
                                long p = Math.min(hist.currentIValue(), hist.length() - 1);
                                dest[destArrayOffset] = p << bs;
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    long v = a.getLong(i);
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.exclude((int)(v >> bs));
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    long v = a.getLong(i);
                                    if (v < 0) {
                                        v = 0;
                                    }
                                    hist.include((int)(v >> bs));
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            }
        }
        //[[Repeat.AutoGeneratedEnd]]

        //[[Repeat() \(float\) ==> ;;
        //           float ==> double;;
        //           Float ==> Double;;
        //           \.0f ==> .0]]
        if (src instanceof FloatArray) {
            final FloatArray a = (FloatArray)src;
            final int histLength = 1 << numberOfAnalyzedBits;
            final double multiplier = histLength - 1; // only strict 1.0 is transformed to histLength-1
            final double multiplierInv = 1.0 / multiplier;
            final HistogramCache histogramCache = new HistogramCache();
            if (direct) {
                final float[] ja = (float[])((DirectAccessible)a).javaArray();
                final int jaOfs = ((DirectAccessible)a).javaArrayOffset();
                final JArrayPool indexesPool = JArrayPool.getInstance(int.class, left.length);
                if (!interpolated) { // float A: simple percentile, direct
                    return new AbstractFloatArray(src.length(), true, src) {
                        public float getFloat(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[histLength];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                double v = ja[jaOfs + (int)i];
                                int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                hist[histIndex]++;
                            }
                            double p = Histogram.value(hist, percentileIndex);
                            return (float)(p * multiplierInv);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            float[] dest = (float[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(histLength, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = ja[jaOfs + (int)i];
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                    hist.include(histIndex);
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    hist.moveToRank(percentileIndex);
                                    dest[destArrayOffset] = (float)(hist.currentValue() * multiplierInv);
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        double v = ja[jaOfs + (int)i];
                                        barIndexes[j] = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                            (int)(v * multiplier);
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        double v = ja[jaOfs + (int)i];
                                        barIndexes[j] = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                            (int)(v * multiplier);
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else { // float B: precise percentile, direct
                    return new AbstractFloatArray(src.length(), true, src) {
                        public float getFloat(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[histLength];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                double v = ja[jaOfs + (int)i];
                                int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                hist[histIndex]++;
                            }
                            double p = Histogram.preciseValue(hist, percentileIndex);
                            return (float)(p * multiplierInv);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            float[] dest = (float[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(histLength, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = ja[jaOfs + (int)i];
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                    hist.include(histIndex);
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    hist.moveToPreciseRank(percentileIndex);
                                    dest[destArrayOffset] = (float)(hist.currentValue() * multiplierInv);
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        double v = ja[jaOfs + (int)i];
                                        barIndexes[j] = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                            (int)(v * multiplier);
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        double v = ja[jaOfs + (int)i];
                                        barIndexes[j] = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                            (int)(v * multiplier);
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                }
            } else if (!interpolated) { // float C: simple percentile, indirect
                return new AbstractFloatArray(src.length(), true, src) {
                    public float getFloat(long index) {
                        double percentileIndex = fPerc.getDouble(index);
                        checkNaN(percentileIndex);
                        int[] hist = new int[histLength];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            double v = a.getDouble(i);
                            int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                            hist[histIndex]++;
                        }
                        double p = Histogram.value(hist, percentileIndex);
                        return (float)(p * multiplierInv);
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        float[] dest = (float[])destArray;
                        UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(histLength, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                double v = a.getDouble(i);
                                int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                hist.include(histIndex);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                hist.moveToRank(percentileIndex);
                                dest[destArrayOffset] = (float)(hist.currentValue() * multiplierInv);
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = a.getDouble(i);
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                        (int)(v * multiplier);
                                    hist.exclude(histIndex);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = a.getDouble(i);
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                        (int)(v * multiplier);
                                    hist.include(histIndex);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            } else { // float D: precise percentile, direct
                return new AbstractFloatArray(src.length(), true, src) {
                    public float getFloat(long index) {
                        double percentileIndex = fPerc.getDouble(index);
                        checkNaN(percentileIndex);
                        int[] hist = new int[histLength];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            double v = a.getDouble(i);
                            int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                            hist[histIndex]++;
                        }
                        double p = Histogram.preciseValue(hist, percentileIndex);
                        return (float)(p * multiplierInv);
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        float[] dest = (float[])destArray;
                        UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(histLength, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                double v = a.getDouble(i);
                                int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                hist.include(histIndex);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                hist.moveToPreciseRank(percentileIndex);
                                dest[destArrayOffset] = (float)(hist.currentValue() * multiplierInv);
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = a.getDouble(i);
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                        (int)(v * multiplier);
                                    hist.exclude(histIndex);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = a.getDouble(i);
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                        (int)(v * multiplier);
                                    hist.include(histIndex);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            }
        }
        //[[Repeat.AutoGeneratedStart !! Auto-generated: NOT EDIT !! ]]
        if (src instanceof DoubleArray) {
            final DoubleArray a = (DoubleArray)src;
            final int histLength = 1 << numberOfAnalyzedBits;
            final double multiplier = histLength - 1; // only strict 1.0 is transformed to histLength-1
            final double multiplierInv = 1.0 / multiplier;
            final HistogramCache histogramCache = new HistogramCache();
            if (direct) {
                final double[] ja = (double[])((DirectAccessible)a).javaArray();
                final int jaOfs = ((DirectAccessible)a).javaArrayOffset();
                final JArrayPool indexesPool = JArrayPool.getInstance(int.class, left.length);
                if (!interpolated) { // double A: simple percentile, direct
                    return new AbstractDoubleArray(src.length(), true, src) {
                        public double getDouble(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[histLength];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                double v = ja[jaOfs + (int)i];
                                int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                hist[histIndex]++;
                            }
                            double p = Histogram.value(hist, percentileIndex);
                            return (p * multiplierInv);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            double[] dest = (double[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(histLength, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = ja[jaOfs + (int)i];
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                    hist.include(histIndex);
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    hist.moveToRank(percentileIndex);
                                    dest[destArrayOffset] = (hist.currentValue() * multiplierInv);
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        double v = ja[jaOfs + (int)i];
                                        barIndexes[j] = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                            (int)(v * multiplier);
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        double v = ja[jaOfs + (int)i];
                                        barIndexes[j] = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                            (int)(v * multiplier);
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                } else { // double B: precise percentile, direct
                    return new AbstractDoubleArray(src.length(), true, src) {
                        public double getDouble(long index) {
                            double percentileIndex = fPerc.getDouble(index);
                            checkNaN(percentileIndex);
                            int[] hist = new int[histLength];
                            for (long shift : shifts) {
                                long i = index - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                double v = ja[jaOfs + (int)i];
                                int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                hist[histIndex]++;
                            }
                            double p = Histogram.preciseValue(hist, percentileIndex);
                            return (p * multiplierInv);
                        }

                        public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                            if (!optimizeGetData) {
                                super.getData(arrayPos, destArray, destArrayOffset, count);
                                return;
                            }
                            Objects.requireNonNull(destArray, "Null destArray argument");
                            checkRanges(length, arrayPos, count);
                            if (count == 0) {
                                return;
                            }
                            double[] dest = (double[])destArray;
                            UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                            final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                            Histogram hist = histogramCache.get(arrayPos);
                            if (hist == null) {
                                hist = Histogram.newIntHistogram(histLength, bitLevels);
                                for (long shift : shifts) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = ja[jaOfs + (int)i];
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                    hist.include(histIndex);
                                }
                            }
                            int[] barIndexes = (int[])indexesPool.requestArray();
                            for (; count > 0; count -= bufLen) {
                                final int len = Math.min(bufLen, count);
                                if (ap != null) {
                                    buf.copy(fPerc.subArr(arrayPos, len));
                                }
                                for (int k = 0; k < len; k++, destArrayOffset++) {
                                    double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                    hist.moveToPreciseRank(percentileIndex);
                                    dest[destArrayOffset] = (hist.currentValue() * multiplierInv);
                                    for (int j = 0; j < right.length; j++) {
                                        long i = arrayPos - right[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        double v = ja[jaOfs + (int)i];
                                        barIndexes[j] = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                            (int)(v * multiplier);
                                    }
                                    hist.exclude(barIndexes);
                                    arrayPos++;
                                    if (arrayPos == length) {
                                        arrayPos = 0;
                                    }
                                    for (int j = 0; j < left.length; j++) {
                                        long i = arrayPos - left[j];
                                        if (i < 0) {
                                            i += length;
                                        }
                                        double v = ja[jaOfs + (int)i];
                                        barIndexes[j] = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                            (int)(v * multiplier);
                                    }
                                    hist.include(barIndexes);
                                }
                            }
                            indexesPool.releaseArray(barIndexes);
                            histogramCache.put(arrayPos, hist);
                            if (ap != null) {
                                ap.releaseArray(buf);
                            }
                        }
                    };
                }
            } else if (!interpolated) { // double C: simple percentile, indirect
                return new AbstractDoubleArray(src.length(), true, src) {
                    public double getDouble(long index) {
                        double percentileIndex = fPerc.getDouble(index);
                        checkNaN(percentileIndex);
                        int[] hist = new int[histLength];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            double v = a.getDouble(i);
                            int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                            hist[histIndex]++;
                        }
                        double p = Histogram.value(hist, percentileIndex);
                        return (p * multiplierInv);
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        double[] dest = (double[])destArray;
                        UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(histLength, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                double v = a.getDouble(i);
                                int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                hist.include(histIndex);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                hist.moveToRank(percentileIndex);
                                dest[destArrayOffset] = (hist.currentValue() * multiplierInv);
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = a.getDouble(i);
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                        (int)(v * multiplier);
                                    hist.exclude(histIndex);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = a.getDouble(i);
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                        (int)(v * multiplier);
                                    hist.include(histIndex);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            } else { // double D: precise percentile, direct
                return new AbstractDoubleArray(src.length(), true, src) {
                    public double getDouble(long index) {
                        double percentileIndex = fPerc.getDouble(index);
                        checkNaN(percentileIndex);
                        int[] hist = new int[histLength];
                        for (long shift : shifts) {
                            long i = index - shift;
                            if (i < 0) {
                                i += length;
                            }
                            double v = a.getDouble(i);
                            int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                            hist[histIndex]++;
                        }
                        double p = Histogram.preciseValue(hist, percentileIndex);
                        return (p * multiplierInv);
                    }

                    public void getData(long arrayPos, Object destArray, int destArrayOffset, int count) {
                        if (!optimizeGetData) {
                            super.getData(arrayPos, destArray, destArrayOffset, count);
                            return;
                        }
                        Objects.requireNonNull(destArray, "Null destArray argument");
                        checkRanges(length, arrayPos, count);
                        if (count == 0) {
                            return;
                        }
                        double[] dest = (double[])destArray;
                        UpdatablePArray buf = ap == null ? null : (UpdatablePArray)ap.requestArray();
                        final int bufLen = ap == null ? count : BUFFER_BLOCK_SIZE;
                        Histogram hist = histogramCache.get(arrayPos);
                        if (hist == null) {
                            hist = Histogram.newIntHistogram(histLength, bitLevels);
                            for (long shift : shifts) {
                                long i = arrayPos - shift;
                                if (i < 0) {
                                    i += length;
                                }
                                double v = a.getDouble(i);
                                int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 : (int)(v * multiplier);
                                hist.include(histIndex);
                            }
                        }
                        for (; count > 0; count -= bufLen) {
                            final int len = Math.min(bufLen, count);
                            if (ap != null) {
                                buf.copy(fPerc.subArr(arrayPos, len));
                            }
                            for (int k = 0; k < len; k++, destArrayOffset++) {
                                double percentileIndex = ap != null ? buf.getDouble(k) : fPerc.getDouble(arrayPos);
                                hist.moveToPreciseRank(percentileIndex);
                                dest[destArrayOffset] = (hist.currentValue() * multiplierInv);
                                for (long shift : right) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = a.getDouble(i);
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                        (int)(v * multiplier);
                                    hist.exclude(histIndex);
                                }
                                arrayPos++;
                                if (arrayPos == length) {
                                    arrayPos = 0;
                                }
                                for (long shift : left) {
                                    long i = arrayPos - shift;
                                    if (i < 0) {
                                        i += length;
                                    }
                                    double v = a.getDouble(i);
                                    int histIndex = v < 0.0 ? 0 : v >= 1.0 ? histLength - 1 :
                                        (int)(v * multiplier);
                                    hist.include(histIndex);
                                }
                            }
                        }
                        histogramCache.put(arrayPos, hist);
                        if (ap != null) {
                            ap.releaseArray(buf);
                        }
                    }
                };
            }
        }
        //[[Repeat.AutoGeneratedEnd]]
        throw new AssertionError("Illegal array type (" + src.getClass()
            + "): it must implement one of primitive XxxArray interfaces");
    }

    private static void checkRanges(long length, long arrayPos, int count) {
        if (count < 0) {
            throw new IllegalArgumentException("Negative number of loaded elements (" + count + ")");
        }
        if (arrayPos < 0) {
            throw new IndexOutOfBoundsException("arrayPos = " + arrayPos + " < 0");
        }
        if (arrayPos > length - count) {
            throw new IndexOutOfBoundsException("arrayPos+count = " + arrayPos + "+" + count + " > length=" + length);
        }
    }

    private static void checkNaN(double rank) {
        if (Double.isNaN(rank)) {
            throw new IllegalArgumentException("Illegal rank (NaN) in some percentile indexes");
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy