// 
// Decompiled by Procyon v0.6.0
// 

package ch.randelshofer.fastdoubleparser;

import java.math.BigInteger;

final class FftMultiplier
{
    public static final double COS_0_25;
    public static final double SIN_0_25;
    private static final int FFT_THRESHOLD = 33220;
    private static final int MAX_MAG_LENGTH = 67108864;
    private static final int ROOTS3_CACHE_SIZE = 20;
    private static final int ROOTS_CACHE2_SIZE = 20;
    private static final int TOOM_COOK_THRESHOLD = 1920;
    private static volatile ComplexVector[] ROOTS2_CACHE;
    private static volatile ComplexVector[] ROOTS3_CACHE;
    
    static int bitsPerFftPoint(final int bitLen) {
        if (bitLen <= 9728) {
            return 19;
        }
        if (bitLen <= 18432) {
            return 18;
        }
        if (bitLen <= 69632) {
            return 17;
        }
        if (bitLen <= 262144) {
            return 16;
        }
        if (bitLen <= 983040) {
            return 15;
        }
        if (bitLen <= 3670016) {
            return 14;
        }
        if (bitLen <= 13631488) {
            return 13;
        }
        if (bitLen <= 25165824) {
            return 12;
        }
        if (bitLen <= 92274688) {
            return 11;
        }
        if (bitLen <= 335544320) {
            return 10;
        }
        if (bitLen <= 1207959552) {
            return 9;
        }
        return 8;
    }
    
    private static ComplexVector calculateRootsOfUnity(final int n) {
        if (n == 1) {
            final ComplexVector v = new ComplexVector(1);
            v.real(0, 1.0);
            v.imag(0, 0.0);
            return v;
        }
        final ComplexVector roots = new ComplexVector(n);
        roots.set(0, 1.0, 0.0);
        double cos = FftMultiplier.COS_0_25;
        double sin = FftMultiplier.SIN_0_25;
        roots.set(n / 2, cos, sin);
        final double angleTerm = 1.5707963267948966 / n;
        for (int i = 1; i < n / 2; ++i) {
            final double angle = angleTerm * i;
            cos = Math.cos(angle);
            sin = Math.sin(angle);
            roots.set(i, cos, sin);
            roots.set(n - i, sin, cos);
        }
        return roots;
    }
    
    private static void fft(final ComplexVector a, final ComplexVector[] roots) {
        final int n = a.length;
        final int logN = 31 - Integer.numberOfLeadingZeros(n);
        final MutableComplex a2 = new MutableComplex();
        final MutableComplex a3 = new MutableComplex();
        final MutableComplex a4 = new MutableComplex();
        final MutableComplex a5 = new MutableComplex();
        final MutableComplex omega1 = new MutableComplex();
        final MutableComplex omega2 = new MutableComplex();
        int s;
        for (s = logN; s >= 2; s -= 2) {
            final ComplexVector rootsS = roots[s - 2];
            for (int m = 1 << s, i = 0; i < n; i += m) {
                for (int j = 0; j < m / 4; ++j) {
                    omega1.set(rootsS, j);
                    omega1.squareInto(omega2);
                    final int idx0 = i + j;
                    final int idx2 = i + j + m / 4;
                    final int idx3 = i + j + m / 2;
                    final int idx4 = i + j + m * 3 / 4;
                    a.addInto(idx0, a, idx2, a2);
                    a2.add(a, idx3);
                    a2.add(a, idx4);
                    a.subtractTimesIInto(idx0, a, idx2, a3);
                    a3.subtract(a, idx3);
                    a3.addTimesI(a, idx4);
                    a3.multiplyConjugate(omega1);
                    a.subtractInto(idx0, a, idx2, a4);
                    a4.add(a, idx3);
                    a4.subtract(a, idx4);
                    a4.multiplyConjugate(omega2);
                    a.addTimesIInto(idx0, a, idx2, a5);
                    a5.subtract(a, idx3);
                    a5.subtractTimesI(a, idx4);
                    a5.multiply(omega1);
                    a2.copyInto(a, idx0);
                    a3.copyInto(a, idx2);
                    a4.copyInto(a, idx3);
                    a5.copyInto(a, idx4);
                }
            }
        }
        if (s > 0) {
            for (int k = 0; k < n; k += 2) {
                a.copyInto(k, a2);
                a.copyInto(k + 1, a3);
                a.add(k, a3);
                a2.subtractInto(a3, a, k + 1);
            }
        }
    }
    
    private static void fft3(final ComplexVector a0, final ComplexVector a1, final ComplexVector a2, final int sign, final double scale) {
        final double omegaImag = sign * -0.5 * Math.sqrt(3.0);
        for (int i = 0; i < a0.length; ++i) {
            final double a0Real = a0.real(i) + a1.real(i) + a2.real(i);
            final double a0Imag = a0.imag(i) + a1.imag(i) + a2.imag(i);
            final double c = omegaImag * (a2.imag(i) - a1.imag(i));
            final double d = omegaImag * (a1.real(i) - a2.real(i));
            final double e = 0.5 * (a1.real(i) + a2.real(i));
            final double f = 0.5 * (a1.imag(i) + a2.imag(i));
            final double a1Real = a0.real(i) - e + c;
            final double a1Imag = a0.imag(i) + d - f;
            final double a2Real = a0.real(i) - e - c;
            final double a2Imag = a0.imag(i) - d - f;
            a0.real(i, a0Real * scale);
            a0.imag(i, a0Imag * scale);
            a1.real(i, a1Real * scale);
            a1.imag(i, a1Imag * scale);
            a2.real(i, a2Real * scale);
            a2.imag(i, a2Imag * scale);
        }
    }
    
    private static void fftMixedRadix(final ComplexVector a, final ComplexVector[] roots2, final ComplexVector roots3) {
        final int oneThird = a.length / 3;
        final ComplexVector a2 = new ComplexVector(a, 0, oneThird);
        final ComplexVector a3 = new ComplexVector(a, oneThird, oneThird * 2);
        final ComplexVector a4 = new ComplexVector(a, oneThird * 2, a.length);
        fft3(a2, a3, a4, 1, 1.0);
        final MutableComplex omega = new MutableComplex();
        for (int i = 0; i < a.length / 4; ++i) {
            omega.set(roots3, i);
            a3.multiplyConjugate(i, omega);
            a4.multiplyConjugate(i, omega);
            a4.multiplyConjugate(i, omega);
        }
        for (int i = a.length / 4; i < oneThird; ++i) {
            omega.set(roots3, i - a.length / 4);
            a3.multiplyConjugateTimesI(i, omega);
            a4.multiplyConjugateTimesI(i, omega);
            a4.multiplyConjugateTimesI(i, omega);
        }
        fft(a2, roots2);
        fft(a3, roots2);
        fft(a4, roots2);
    }
    
    static BigInteger fromFftVector(final ComplexVector fftVec, final int signum, final int bitsPerFftPoint) {
        assert bitsPerFftPoint <= 25 : bitsPerFftPoint + " does not fit into an int with slack";
        final int fftLen = (int)Math.min(fftVec.length, 2147483648L / bitsPerFftPoint + 1L);
        final int magLen = (int)(8L * (fftLen * (long)bitsPerFftPoint + 31L) / 32L);
        final byte[] mag = new byte[magLen];
        final int base = 1 << bitsPerFftPoint;
        final int bitMask = base - 1;
        final int bitPadding = 32 - bitsPerFftPoint;
        long carry = 0L;
        final int bitLength = mag.length * 8;
        int bitIdx = bitLength - bitsPerFftPoint;
        int magComponent = 0;
        int prevIdx = Math.min(Math.max(0, bitIdx >> 3), mag.length - 4);
        for (int part = 0; part <= 1; ++part) {
            for (int fftIdx = 0; fftIdx < fftLen; ++fftIdx) {
                final long fftElem = Math.round(fftVec.part(fftIdx, part)) + carry;
                carry = fftElem >> bitsPerFftPoint;
                final int idx = Math.min(Math.max(0, bitIdx >> 3), mag.length - 4);
                magComponent >>>= prevIdx - idx << 3;
                final int shift = bitPadding - bitIdx + (idx << 3);
                magComponent |= (int)((fftElem & (long)bitMask) << shift);
                FastDoubleSwar.writeIntBE(mag, idx, magComponent);
                prevIdx = idx;
                bitIdx -= bitsPerFftPoint;
            }
        }
        return new BigInteger(signum, mag);
    }
    
    private static ComplexVector[] getRootsOfUnity2(final int logN) {
        final ComplexVector[] roots = new ComplexVector[logN + 1];
        for (int i = logN; i >= 0; i -= 2) {
            if (i < 20) {
                if (FftMultiplier.ROOTS2_CACHE[i] == null) {
                    FftMultiplier.ROOTS2_CACHE[i] = calculateRootsOfUnity(1 << i);
                }
                roots[i] = FftMultiplier.ROOTS2_CACHE[i];
            }
            else {
                roots[i] = calculateRootsOfUnity(1 << i);
            }
        }
        return roots;
    }
    
    private static ComplexVector getRootsOfUnity3(final int logN) {
        if (logN < 20) {
            if (FftMultiplier.ROOTS3_CACHE[logN] == null) {
                FftMultiplier.ROOTS3_CACHE[logN] = calculateRootsOfUnity(3 << logN);
            }
            return FftMultiplier.ROOTS3_CACHE[logN];
        }
        return calculateRootsOfUnity(3 << logN);
    }
    
    private static void ifft(final ComplexVector a, final ComplexVector[] roots) {
        final int n = a.length;
        final int logN = 31 - Integer.numberOfLeadingZeros(n);
        final MutableComplex a2 = new MutableComplex();
        final MutableComplex a3 = new MutableComplex();
        final MutableComplex a4 = new MutableComplex();
        final MutableComplex a5 = new MutableComplex();
        final MutableComplex b0 = new MutableComplex();
        final MutableComplex b2 = new MutableComplex();
        final MutableComplex b3 = new MutableComplex();
        final MutableComplex b4 = new MutableComplex();
        int s = 1;
        if (logN % 2 != 0) {
            for (int i = 0; i < n; i += 2) {
                a.copyInto(i + 1, a4);
                a.copyInto(i, a2);
                a.add(i, a4);
                a2.subtractInto(a4, a, i + 1);
            }
            ++s;
        }
        final MutableComplex omega1 = new MutableComplex();
        final MutableComplex omega2 = new MutableComplex();
        while (s <= logN) {
            final ComplexVector rootsS = roots[s - 1];
            for (int m = 1 << s + 1, j = 0; j < n; j += m) {
                for (int k = 0; k < m / 4; ++k) {
                    omega1.set(rootsS, k);
                    omega1.squareInto(omega2);
                    final int idx0 = j + k;
                    final int idx2 = j + k + m / 4;
                    final int idx3 = j + k + m / 2;
                    final int idx4 = j + k + m * 3 / 4;
                    a.copyInto(idx0, a2);
                    a.multiplyInto(idx2, omega1, a3);
                    a.multiplyInto(idx3, omega2, a4);
                    a.multiplyConjugateInto(idx4, omega1, a5);
                    a2.addInto(a3, b0);
                    b0.add(a4);
                    b0.add(a5);
                    a2.addTimesIInto(a3, b2);
                    b2.subtract(a4);
                    b2.subtractTimesI(a5);
                    a2.subtractInto(a3, b3);
                    b3.add(a4);
                    b3.subtract(a5);
                    a2.subtractTimesIInto(a3, b4);
                    b4.subtract(a4);
                    b4.addTimesI(a5);
                    b0.copyInto(a, idx0);
                    b2.copyInto(a, idx2);
                    b3.copyInto(a, idx3);
                    b4.copyInto(a, idx4);
                }
            }
            s += 2;
        }
        for (int l = 0; l < n; ++l) {
            a.timesTwoToThe(l, -logN);
        }
    }
    
    private static void ifftMixedRadix(final ComplexVector a, final ComplexVector[] roots2, final ComplexVector roots3) {
        final int oneThird = a.length / 3;
        final ComplexVector a2 = new ComplexVector(a, 0, oneThird);
        final ComplexVector a3 = new ComplexVector(a, oneThird, oneThird * 2);
        final ComplexVector a4 = new ComplexVector(a, oneThird * 2, a.length);
        ifft(a2, roots2);
        ifft(a3, roots2);
        ifft(a4, roots2);
        final MutableComplex omega = new MutableComplex();
        for (int i = 0; i < a.length / 4; ++i) {
            omega.set(roots3, i);
            a3.multiply(i, omega);
            a4.multiply(i, omega);
            a4.multiply(i, omega);
        }
        for (int i = a.length / 4; i < oneThird; ++i) {
            omega.set(roots3, i - a.length / 4);
            a3.multiplyByIAnd(i, omega);
            a4.multiplyByIAnd(i, omega);
            a4.multiplyByIAnd(i, omega);
        }
        fft3(a2, a3, a4, -1, 0.3333333333333333);
    }
    
    static BigInteger multiply(final BigInteger a, final BigInteger b) {
        assert a != null : "a==null";
        assert b != null : "b==null";
        if (b.signum() == 0 || a.signum() == 0) {
            return BigInteger.ZERO;
        }
        if (b == a) {
            return square(b);
        }
        final int xlen = a.bitLength();
        final int ylen = b.bitLength();
        if (xlen + (long)ylen > 2147483648L) {
            throw new ArithmeticException("BigInteger would overflow supported range");
        }
        if (xlen > 1920 && ylen > 1920 && (xlen > 33220 || ylen > 33220)) {
            return multiplyFft(a, b);
        }
        return a.multiply(b);
    }
    
    static BigInteger multiplyFft(final BigInteger a, final BigInteger b) {
        final int signum = a.signum() * b.signum();
        final byte[] aMag = ((a.signum() < 0) ? a.negate() : a).toByteArray();
        final byte[] bMag = ((b.signum() < 0) ? b.negate() : b).toByteArray();
        final int bitLen = Math.max(aMag.length, bMag.length) * 8;
        final int bitsPerPoint = bitsPerFftPoint(bitLen);
        final int fftLen = (bitLen + bitsPerPoint - 1) / bitsPerPoint + 1;
        final int logFFTLen = 32 - Integer.numberOfLeadingZeros(fftLen - 1);
        final int fftLen2 = 1 << logFFTLen;
        final int fftLen3 = fftLen2 * 3 / 4;
        if (fftLen < fftLen3 && logFFTLen > 3) {
            final ComplexVector[] roots2 = getRootsOfUnity2(logFFTLen - 2);
            final ComplexVector weights = getRootsOfUnity3(logFFTLen - 2);
            final ComplexVector twiddles = getRootsOfUnity3(logFFTLen - 4);
            final ComplexVector aVec = toFftVector(aMag, fftLen3, bitsPerPoint);
            aVec.applyWeights(weights);
            fftMixedRadix(aVec, roots2, twiddles);
            final ComplexVector bVec = toFftVector(bMag, fftLen3, bitsPerPoint);
            bVec.applyWeights(weights);
            fftMixedRadix(bVec, roots2, twiddles);
            aVec.multiplyPointwise(bVec);
            ifftMixedRadix(aVec, roots2, twiddles);
            aVec.applyInverseWeights(weights);
            return fromFftVector(aVec, signum, bitsPerPoint);
        }
        final ComplexVector[] roots3 = getRootsOfUnity2(logFFTLen);
        final ComplexVector aVec2 = toFftVector(aMag, fftLen2, bitsPerPoint);
        aVec2.applyWeights(roots3[logFFTLen]);
        fft(aVec2, roots3);
        final ComplexVector bVec2 = toFftVector(bMag, fftLen2, bitsPerPoint);
        bVec2.applyWeights(roots3[logFFTLen]);
        fft(bVec2, roots3);
        aVec2.multiplyPointwise(bVec2);
        ifft(aVec2, roots3);
        aVec2.applyInverseWeights(roots3[logFFTLen]);
        return fromFftVector(aVec2, signum, bitsPerPoint);
    }
    
    static BigInteger square(final BigInteger a) {
        if (a.signum() == 0) {
            return BigInteger.ZERO;
        }
        return (a.bitLength() < 33220) ? a.multiply(a) : squareFft(a);
    }
    
    static BigInteger squareFft(final BigInteger a) {
        final byte[] mag = a.toByteArray();
        final int bitLen = mag.length * 8;
        final int bitsPerPoint = bitsPerFftPoint(bitLen);
        int fftLen = (bitLen + bitsPerPoint - 1) / bitsPerPoint + 1;
        final int logFFTLen = 32 - Integer.numberOfLeadingZeros(fftLen - 1);
        final int fftLen2 = 1 << logFFTLen;
        final int fftLen3 = fftLen2 * 3 / 4;
        if (fftLen < fftLen3) {
            fftLen = fftLen3;
            final ComplexVector vec = toFftVector(mag, fftLen, bitsPerPoint);
            final ComplexVector[] roots2 = getRootsOfUnity2(logFFTLen - 2);
            final ComplexVector weights = getRootsOfUnity3(logFFTLen - 2);
            final ComplexVector twiddles = getRootsOfUnity3(logFFTLen - 4);
            vec.applyWeights(weights);
            fftMixedRadix(vec, roots2, twiddles);
            vec.squarePointwise();
            ifftMixedRadix(vec, roots2, twiddles);
            vec.applyInverseWeights(weights);
            return fromFftVector(vec, 1, bitsPerPoint);
        }
        fftLen = fftLen2;
        final ComplexVector vec = toFftVector(mag, fftLen, bitsPerPoint);
        final ComplexVector[] roots3 = getRootsOfUnity2(logFFTLen);
        vec.applyWeights(roots3[logFFTLen]);
        fft(vec, roots3);
        vec.squarePointwise();
        ifft(vec, roots3);
        vec.applyInverseWeights(roots3[logFFTLen]);
        return fromFftVector(vec, 1, bitsPerPoint);
    }
    
    static ComplexVector toFftVector(byte[] mag, final int fftLen, final int bitsPerFftPoint) {
        assert bitsPerFftPoint <= 25 : bitsPerFftPoint + " does not fit into an int with slack";
        final ComplexVector fftVec = new ComplexVector(fftLen);
        if (mag.length < 4) {
            final byte[] paddedMag = new byte[4];
            System.arraycopy(mag, 0, paddedMag, 4 - mag.length, mag.length);
            mag = paddedMag;
        }
        final int base = 1 << bitsPerFftPoint;
        final int halfBase = base / 2;
        final int bitMask = base - 1;
        final int bitPadding = 32 - bitsPerFftPoint;
        final int bitLength = mag.length * 8;
        int carry = 0;
        int fftIdx = 0;
        for (int bitIdx = bitLength - bitsPerFftPoint; bitIdx > -bitsPerFftPoint; bitIdx -= bitsPerFftPoint) {
            final int idx = Math.min(Math.max(0, bitIdx >> 3), mag.length - 4);
            final int shift = bitPadding - bitIdx + (idx << 3);
            int fftPoint = FastDoubleSwar.readIntBE(mag, idx) >>> shift & bitMask;
            fftPoint += carry;
            carry = halfBase - fftPoint >>> 31;
            fftPoint -= (base & -carry);
            fftVec.real(fftIdx, fftPoint);
            ++fftIdx;
        }
        if (carry > 0) {
            fftVec.real(fftIdx, carry);
        }
        return fftVec;
    }
    
    static {
        COS_0_25 = Math.cos(0.7853981633974483);
        SIN_0_25 = Math.sin(0.7853981633974483);
        FftMultiplier.ROOTS2_CACHE = new ComplexVector[20];
        FftMultiplier.ROOTS3_CACHE = new ComplexVector[20];
    }
    
    static final class ComplexVector
    {
        private static final int COMPLEX_SIZE_SHIFT = 1;
        static final int IMAG = 1;
        static final int REAL = 0;
        private final double[] a;
        private final int length;
        private final int offset;
        
        ComplexVector(final int length) {
            this.a = new double[length << 1];
            this.length = length;
            this.offset = 0;
        }
        
        ComplexVector(final ComplexVector c, final int from, final int to) {
            this.length = to - from;
            this.a = c.a;
            this.offset = from << 1;
        }
        
        void add(final int idxa, final MutableComplex c) {
            final double[] a = this.a;
            final int realIdx = this.realIdx(idxa);
            a[realIdx] += c.real;
            final double[] a2 = this.a;
            final int imagIdx = this.imagIdx(idxa);
            a2[imagIdx] += c.imag;
        }
        
        void addInto(final int idxa, final ComplexVector c, final int idxc, final MutableComplex destination) {
            destination.real = this.a[this.realIdx(idxa)] + c.real(idxc);
            destination.imag = this.a[this.imagIdx(idxa)] + c.imag(idxc);
        }
        
        void addTimesIInto(final int idxa, final ComplexVector c, final int idxc, final MutableComplex destination) {
            destination.real = this.a[this.realIdx(idxa)] - c.imag(idxc);
            destination.imag = this.a[this.imagIdx(idxa)] + c.real(idxc);
        }
        
        void applyInverseWeights(final ComplexVector weights) {
            int offw = weights.offset;
            final double[] w = weights.a;
            for (int end = this.offset + this.length << 1, offa = this.offset; offa < end; offa += 2) {
                final double real = this.a[offa + 0];
                final double imag = this.a[offa + 1];
                this.a[offa] = FastDoubleSwar.fma(real, w[offw + 0], imag * w[offw + 1]);
                this.a[offa + 1] = FastDoubleSwar.fma(-real, w[offw + 1], imag * w[offw + 0]);
                offw += 2;
            }
        }
        
        void applyWeights(final ComplexVector weights) {
            int offw = weights.offset;
            final double[] w = weights.a;
            for (int end = this.offset + this.length << 1, offa = this.offset; offa < end; offa += 2) {
                final double real = this.a[offa + 0];
                this.a[offa + 0] = real * w[offw + 0];
                this.a[offa + 1] = real * w[offw + 1];
                offw += 2;
            }
        }
        
        void copyInto(final int idxa, final MutableComplex destination) {
            destination.real = this.a[this.realIdx(idxa)];
            destination.imag = this.a[this.imagIdx(idxa)];
        }
        
        double imag(final int idxa) {
            return this.a[(idxa << 1) + this.offset + 1];
        }
        
        void imag(final int idxa, final double value) {
            this.a[(idxa << 1) + this.offset + 1] = value;
        }
        
        private int imagIdx(final int idxa) {
            return (idxa << 1) + this.offset + 1;
        }
        
        void multiply(final int idxa, final MutableComplex c) {
            final int ri = this.realIdx(idxa);
            final int ii = this.imagIdx(idxa);
            final double real = this.a[ri];
            final double imag = this.a[ii];
            this.a[ri] = FastDoubleSwar.fma(real, c.real, -imag * c.imag);
            this.a[ii] = FastDoubleSwar.fma(real, c.imag, imag * c.real);
        }
        
        void multiplyByIAnd(final int idxa, final MutableComplex c) {
            final int ri = this.realIdx(idxa);
            final int ii = this.imagIdx(idxa);
            final double real = this.a[ri];
            final double imag = this.a[ii];
            this.a[ri] = FastDoubleSwar.fma(-real, c.imag, -imag * c.real);
            this.a[ii] = FastDoubleSwar.fma(real, c.real, -imag * c.imag);
        }
        
        void multiplyConjugate(final int idxa, final MutableComplex c) {
            final int ri = this.realIdx(idxa);
            final int ii = this.imagIdx(idxa);
            final double real = this.a[ri];
            final double imag = this.a[ii];
            this.a[ri] = FastDoubleSwar.fma(real, c.real, imag * c.imag);
            this.a[ii] = FastDoubleSwar.fma(-real, c.imag, imag * c.real);
        }
        
        void multiplyConjugateInto(final int idxa, final MutableComplex c, final MutableComplex destination) {
            final double real = this.a[this.realIdx(idxa)];
            final double imag = this.a[this.imagIdx(idxa)];
            destination.real = FastDoubleSwar.fma(real, c.real, imag * c.imag);
            destination.imag = FastDoubleSwar.fma(-real, c.imag, imag * c.real);
        }
        
        void multiplyConjugateTimesI(final int idxa, final MutableComplex c) {
            final int ri = this.realIdx(idxa);
            final int ii = this.imagIdx(idxa);
            final double real = this.a[ri];
            final double imag = this.a[ii];
            this.a[ri] = FastDoubleSwar.fma(-real, c.imag, imag * c.real);
            this.a[ii] = FastDoubleSwar.fma(-real, c.real, -imag * c.imag);
        }
        
        void multiplyInto(final int idxa, final MutableComplex c, final MutableComplex destination) {
            final double real = this.a[this.realIdx(idxa)];
            final double imag = this.a[this.imagIdx(idxa)];
            destination.real = FastDoubleSwar.fma(real, c.real, -imag * c.imag);
            destination.imag = FastDoubleSwar.fma(real, c.imag, imag * c.real);
        }
        
        void multiplyPointwise(final ComplexVector cvec) {
            int offc = cvec.offset;
            final double[] c = cvec.a;
            for (int end = this.offset + this.length << 1, offa = this.offset; offa < end; offa += 2) {
                final double real = this.a[offa + 0];
                final double imag = this.a[offa + 1];
                final double creal = c[offc + 0];
                final double cimag = c[offc + 1];
                this.a[offa + 0] = FastDoubleSwar.fma(real, creal, -imag * cimag);
                this.a[offa + 1] = FastDoubleSwar.fma(real, cimag, imag * creal);
                offc += 2;
            }
        }
        
        double part(final int idxa, final int part) {
            return this.a[(idxa << 1) + part];
        }
        
        double real(final int idxa) {
            return this.a[(idxa << 1) + this.offset];
        }
        
        void real(final int idxa, final double value) {
            this.a[(idxa << 1) + this.offset] = value;
        }
        
        private int realIdx(final int idxa) {
            return (idxa << 1) + this.offset;
        }
        
        void set(final int idxa, final double real, final double imag) {
            final int idx = this.realIdx(idxa);
            this.a[idx] = real;
            this.a[idx + 1] = imag;
        }
        
        void squarePointwise() {
            for (int end = this.offset + this.length << 1, offa = this.offset; offa < end; offa += 2) {
                final double real = this.a[offa + 0];
                final double imag = this.a[offa + 1];
                this.a[offa + 0] = FastDoubleSwar.fma(real, real, -imag * imag);
                this.a[offa + 1] = 2.0 * real * imag;
            }
        }
        
        void subtractInto(final int idxa, final ComplexVector c, final int idxc, final MutableComplex destination) {
            destination.real = this.a[this.realIdx(idxa)] - c.real(idxc);
            destination.imag = this.a[this.imagIdx(idxa)] - c.imag(idxc);
        }
        
        void subtractTimesIInto(final int idxa, final ComplexVector c, final int idxc, final MutableComplex destination) {
            destination.real = this.a[this.realIdx(idxa)] + c.imag(idxc);
            destination.imag = this.a[this.imagIdx(idxa)] - c.real(idxc);
        }
        
        void timesTwoToThe(final int idxa, final int n) {
            final int ri = this.realIdx(idxa);
            final int ii = this.imagIdx(idxa);
            final double real = this.a[ri];
            final double imag = this.a[ii];
            this.a[ri] = FastDoubleMath.fastScalb(real, n);
            this.a[ii] = FastDoubleMath.fastScalb(imag, n);
        }
    }
    
    static final class MutableComplex
    {
        double real;
        double imag;
        
        void add(final MutableComplex c) {
            this.real += c.real;
            this.imag += c.imag;
        }
        
        void add(final ComplexVector c, final int idxc) {
            this.real += c.real(idxc);
            this.imag += c.imag(idxc);
        }
        
        void addInto(final MutableComplex c, final MutableComplex destination) {
            destination.real = this.real + c.real;
            destination.imag = this.imag + c.imag;
        }
        
        void addTimesI(final MutableComplex c) {
            this.real -= c.imag;
            this.imag += c.real;
        }
        
        void addTimesI(final ComplexVector c, final int idxc) {
            this.real -= c.imag(idxc);
            this.imag += c.real(idxc);
        }
        
        void addTimesIInto(final MutableComplex c, final MutableComplex destination) {
            destination.real = this.real - c.imag;
            destination.imag = this.imag + c.real;
        }
        
        void copyInto(final ComplexVector c, final int idxc) {
            c.real(idxc, this.real);
            c.imag(idxc, this.imag);
        }
        
        void multiply(final MutableComplex c) {
            final double temp = this.real;
            this.real = FastDoubleSwar.fma(temp, c.real, -this.imag * c.imag);
            this.imag = FastDoubleSwar.fma(temp, c.imag, this.imag * c.real);
        }
        
        void multiplyConjugate(final MutableComplex c) {
            final double temp = this.real;
            this.real = FastDoubleSwar.fma(temp, c.real, this.imag * c.imag);
            this.imag = FastDoubleSwar.fma(-temp, c.imag, this.imag * c.real);
        }
        
        void set(final ComplexVector c, final int idxc) {
            this.real = c.real(idxc);
            this.imag = c.imag(idxc);
        }
        
        void squareInto(final MutableComplex destination) {
            destination.real = FastDoubleSwar.fma(this.real, this.real, -this.imag * this.imag);
            destination.imag = 2.0 * this.real * this.imag;
        }
        
        void subtract(final MutableComplex c) {
            this.real -= c.real;
            this.imag -= c.imag;
        }
        
        void subtract(final ComplexVector c, final int idxc) {
            this.real -= c.real(idxc);
            this.imag -= c.imag(idxc);
        }
        
        void subtractInto(final MutableComplex c, final MutableComplex destination) {
            destination.real = this.real - c.real;
            destination.imag = this.imag - c.imag;
        }
        
        void subtractInto(final MutableComplex c, final ComplexVector destination, final int idxd) {
            destination.real(idxd, this.real - c.real);
            destination.imag(idxd, this.imag - c.imag);
        }
        
        void subtractTimesI(final MutableComplex c) {
            this.real += c.imag;
            this.imag -= c.real;
        }
        
        void subtractTimesI(final ComplexVector c, final int idxc) {
            this.real += c.imag(idxc);
            this.imag -= c.real(idxc);
        }
        
        void subtractTimesIInto(final MutableComplex c, final MutableComplex destination) {
            destination.real = this.real + c.imag;
            destination.imag = this.imag - c.real;
        }
    }
}
