Skip to main content
  1. Java NIO (New I/O)/

DoubleBuffer

10 mins

The DoubleBuffer class is a specialized buffer for 64-bit floating-point values, providing efficient storage and manipulation of double-precision data. It’s essential for high-precision scientific computing, financial calculations, and any application requiring maximum floating-point accuracy.

Source Code #

View Source on GitHub

Core Implementation #

public abstract class DoubleBuffer extends Buffer implements Comparable<DoubleBuffer> {
    // Factory methods
    public static DoubleBuffer allocate(int capacity) {
        return new HeapDoubleBuffer(capacity, capacity);
    }
    
    public static DoubleBuffer wrap(double[] array) {
        return new HeapDoubleBuffer(array, 0, array.length);
    }
    
    public static DoubleBuffer wrap(double[] array, int offset, int length) {
        return new HeapDoubleBuffer(array, offset, length);
    }
    
    // Double operations
    public abstract double get();
    public abstract double get(int index);
    public abstract DoubleBuffer put(double d);
    public abstract DoubleBuffer put(int index, double d);
    
    // Bulk operations
    public DoubleBuffer get(double[] dst) {
        return get(dst, 0, dst.length);
    }
    
    public DoubleBuffer get(double[] dst, int offset, int length) {
        checkBounds(offset, length, dst.length);
        if (length > remaining())
            throw new BufferUnderflowException();
        for (int i = offset; i < offset + length; i++)
            dst[i] = get();
        return this;
    }
    
    public DoubleBuffer put(double[] src) {
        return put(src, 0, src.length);
    }
    
    public DoubleBuffer put(double[] src, int offset, int length) {
        checkBounds(offset, length, src.length);
        if (length > remaining())
            throw new BufferOverflowException();
        for (int i = offset; i < offset + length; i++)
            put(src[i]);
        return this;
    }
    
    // View operations
    public abstract DoubleBuffer duplicate();
    public abstract DoubleBuffer slice();
    public abstract DoubleBuffer slice(int index, int length);
    
    // Compact operation
    public abstract DoubleBuffer compact();
}

Implementation Details #

Double-Precision Data Processing #

DoubleBuffer provides efficient double operations with IEEE 754 semantics:

// Creating and populating DoubleBuffer
DoubleBuffer buffer = DoubleBuffer.allocate(100);
buffer.put(3.141592653589793);
buffer.put(Double.MAX_VALUE);
buffer.put(Double.NaN);
buffer.put(Double.POSITIVE_INFINITY);
buffer.flip();

// Reading double values
double pi = buffer.get();           // 3.141592653589793
double max = buffer.get();          // Double.MAX_VALUE
double nan = buffer.get(2);         // Double.NaN
double infinity = buffer.get(3);    // Double.POSITIVE_INFINITY

// Bulk operations with double arrays
double[] data = {1.0, 2.0, 3.0, 4.0, 5.0};
DoubleBuffer wrapped = DoubleBuffer.wrap(data);  // Shares array
double[] copy = new double[5];
buffer.get(copy);                  // Bulk copy

View from ByteBuffer #

DoubleBuffer can be created as a view of ByteBuffer (8 bytes per double):

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);

// Create DoubleBuffer view (8 bytes per double)
DoubleBuffer doubleBuffer = byteBuffer.asDoubleBuffer();

// The view shares underlying byte buffer
// Writing to doubleBuffer affects byteBuffer
doubleBuffer.put(0, 3.141592653589793);  // Writes 8 bytes
doubleBuffer.put(1, Double.NaN);         // Writes next 8 bytes

// Byte order affects double representation
byteBuffer.order(ByteOrder.BIG_ENDIAN);
DoubleBuffer bigEndianView = byteBuffer.asDoubleBuffer();
// Same bytes, different double interpretation

Floating-Point Special Values #

// Handling special double values
DoubleBuffer buffer = DoubleBuffer.allocate(10);

// Store special values
buffer.put(Double.NaN);
buffer.put(Double.POSITIVE_INFINITY);
buffer.put(Double.NEGATIVE_INFINITY);
buffer.put(Double.MIN_VALUE);
buffer.put(Double.MAX_VALUE);
buffer.put(Double.MIN_NORMAL);
buffer.flip();

// Check special values
while (buffer.hasRemaining()) {
    double value = buffer.get();
    
    if (Double.isNaN(value)) {
        // Handle NaN
    } else if (Double.isInfinite(value)) {
        // Handle infinity
    } else if (Double.isFinite(value)) {
        // Handle finite value
    }
}

Compact Operation for Double Data #

public DoubleBuffer compact() {
    int pos = position();
    int lim = limit();
    int rem = (pos <= lim ? lim - pos : 0);
    
    // Copy remaining doubles to beginning
    for (int i = 0; i < rem; i++) {
        put(i, get(pos + i));
    }
    
    position(rem);
    limit(capacity());
    discardMark();
    return this;
}

// Usage pattern for double stream processing
DoubleBuffer buffer = DoubleBuffer.allocate(1024);
while (hasMoreData()) {
    // Fill buffer with doubles
    while (buffer.hasRemaining() && hasMoreData()) {
        buffer.put(nextDouble());
    }
    
    buffer.flip();
    
    // Process complete batches
    while (buffer.remaining() >= BATCH_SIZE) {
        processBatch(buffer);
    }
    
    buffer.compact();  // Move remaining data to beginning
}

Performance Characteristics #

OperationHeapDoubleBufferDirectDoubleBuffer (via ByteBuffer)
AllocationO(1) fastO(n) slower (native allocation)
Double AccessO(1) direct array accessO(1) with bounds checking
Bulk OperationsFast (System.arraycopy)Slower (element-by-element)
Memory Usage8 bytes per double + overhead8 bytes per double + native overhead
Numerical PerformanceGood (JIT optimized)Slightly slower (JNI overhead)

Common Patterns #

High-Precision Scientific Computing #

// Matrix operations with double precision
DoubleBuffer matrixMultiply(DoubleBuffer a, DoubleBuffer b, 
                           int aRows, int aCols, int bCols) {
    if (a.remaining() != aRows * aCols || b.remaining() != aCols * bCols) {
        throw new IllegalArgumentException("Matrix dimension mismatch");
    }
    
    DoubleBuffer result = DoubleBuffer.allocate(aRows * bCols);
    
    for (int i = 0; i < aRows; i++) {
        for (int j = 0; j < bCols; j++) {
            double sum = 0.0;
            for (int k = 0; k < aCols; k++) {
                sum += a.get(i * aCols + k) * b.get(k * bCols + j);
            }
            result.put(sum);
        }
    }
    
    result.flip();
    return result;
}

// Numerical integration
double integrate(DoubleBuffer functionValues, double stepSize) {
    if (functionValues.remaining() < 2) {
        throw new IllegalArgumentException("Need at least 2 points");
    }
    
    // Simpson's rule
    double sum = functionValues.get(0) + functionValues.get(functionValues.limit() - 1);
    
    for (int i = 1; i < functionValues.remaining() - 1; i++) {
        double coefficient = (i % 2 == 0) ? 2.0 : 4.0;
        sum += coefficient * functionValues.get(i);
    }
    
    return sum * stepSize / 3.0;
}

Financial Calculations #

// Calculate compound interest
DoubleBuffer calculateCompoundInterest(DoubleBuffer principals, 
                                      double annualRate, 
                                      int periodsPerYear, 
                                      int years) {
    DoubleBuffer results = DoubleBuffer.allocate(principals.remaining());
    
    double ratePerPeriod = annualRate / periodsPerYear;
    int totalPeriods = periodsPerYear * years;
    
    for (int i = 0; i < principals.remaining(); i++) {
        double principal = principals.get(i);
        double amount = principal * Math.pow(1 + ratePerPeriod, totalPeriods);
        results.put(amount);
    }
    
    results.flip();
    return results;
}

// Moving average for stock prices
DoubleBuffer exponentialMovingAverage(DoubleBuffer prices, double alpha) {
    if (prices.remaining() == 0) {
        return DoubleBuffer.allocate(0);
    }
    
    DoubleBuffer ema = DoubleBuffer.allocate(prices.remaining());
    
    // First EMA is first price
    double currentEMA = prices.get(0);
    ema.put(currentEMA);
    
    // Calculate subsequent EMAs
    for (int i = 1; i < prices.remaining(); i++) {
        double price = prices.get(i);
        currentEMA = alpha * price + (1 - alpha) * currentEMA;
        ema.put(currentEMA);
    }
    
    ema.flip();
    return ema;
}

Signal Processing with High Precision #

// Fast Fourier Transform (simplified)
DoubleBuffer[] fft(DoubleBuffer real, DoubleBuffer imag) {
    if (real.remaining() != imag.remaining()) {
        throw new IllegalArgumentException("Real and imag parts must match");
    }
    
    int n = real.remaining();
    if ((n & (n - 1)) != 0) {
        throw new IllegalArgumentException("n must be power of 2");
    }
    
    DoubleBuffer realOut = DoubleBuffer.allocate(n);
    DoubleBuffer imagOut = DoubleBuffer.allocate(n);
    
    // Copy input to output (in-place FFT would modify input)
    for (int i = 0; i < n; i++) {
        realOut.put(real.get(i));
        imagOut.put(imag.get(i));
    }
    
    realOut.flip();
    imagOut.flip();
    
    // Cooley-Tukey FFT implementation would go here
    // ... (complex FFT algorithm)
    
    return new DoubleBuffer[]{realOut, imagOut};
}

// Digital filter design
DoubleBuffer firFilter(DoubleBuffer signal, DoubleBuffer coefficients) {
    int signalLen = signal.remaining();
    int coeffLen = coefficients.remaining();
    int outputLen = signalLen + coeffLen - 1;
    
    DoubleBuffer output = DoubleBuffer.allocate(outputLen);
    
    for (int n = 0; n < outputLen; n++) {
        double sum = 0.0;
        for (int k = 0; k < coeffLen; k++) {
            if (n - k >= 0 && n - k < signalLen) {
                sum += coefficients.get(k) * signal.get(n - k);
            }
        }
        output.put(sum);
    }
    
    output.flip();
    return output;
}

Geographic Coordinate Processing #

// Convert spherical coordinates to Cartesian
DoubleBuffer sphericalToCartesian(DoubleBuffer spherical) {
    // Expects [lat1, lon1, lat2, lon2, ...] in radians
    if (spherical.remaining() % 2 != 0) {
        throw new IllegalArgumentException("Need pairs of lat/lon");
    }
    
    int pointCount = spherical.remaining() / 2;
    DoubleBuffer cartesian = DoubleBuffer.allocate(pointCount * 3);
    
    for (int i = 0; i < pointCount; i++) {
        double lat = spherical.get(i * 2);
        double lon = spherical.get(i * 2 + 1);
        
        // Convert to Cartesian (assuming unit sphere)
        double x = Math.cos(lat) * Math.cos(lon);
        double y = Math.cos(lat) * Math.sin(lon);
        double z = Math.sin(lat);
        
        cartesian.put(x);
        cartesian.put(y);
        cartesian.put(z);
    }
    
    cartesian.flip();
    return cartesian;
}

// Calculate great-circle distances
DoubleBuffer greatCircleDistances(DoubleBuffer points1, DoubleBuffer points2) {
    if (points1.remaining() != points2.remaining() || points1.remaining() % 2 != 0) {
        throw new IllegalArgumentException("Need matching pairs of lat/lon");
    }
    
    int pairCount = points1.remaining() / 2;
    DoubleBuffer distances = DoubleBuffer.allocate(pairCount);
    
    for (int i = 0; i < pairCount; i++) {
        double lat1 = Math.toRadians(points1.get(i * 2));
        double lon1 = Math.toRadians(points1.get(i * 2 + 1));
        double lat2 = Math.toRadians(points2.get(i * 2));
        double lon2 = Math.toRadians(points2.get(i * 2 + 1));
        
        // Haversine formula
        double dLat = lat2 - lat1;
        double dLon = lon2 - lon1;
        double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
                   Math.cos(lat1) * Math.cos(lat2) *
                   Math.sin(dLon/2) * Math.sin(dLon/2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        double distance = 6371.0 * c;  // Earth radius in km
        
        distances.put(distance);
    }
    
    distances.flip();
    return distances;
}

Best Practices #

  1. Double-Precision Accuracy:

    // Be aware of floating-point accuracy limitations
    DoubleBuffer buffer = DoubleBuffer.allocate(100);
    
    // Use relative error for comparisons
    double a = buffer.get(0);
    double b = buffer.get(1);
    
    double relativeError = Math.abs(a - b) / Math.max(Math.abs(a), Math.abs(b));
    if (relativeError < 1e-12) {
        // Values are considered equal
    }
    
  2. NaN and Infinity Handling:

    // Always check for special values in scientific computing
    DoubleBuffer data = readScientificData();
    
    for (int i = 0; i < data.remaining(); i++) {
        double value = data.get(i);
    
        if (Double.isNaN(value)) {
            // Handle NaN (replace with default or use interpolation)
            data.put(i, interpolateNeighbors(data, i));
        } else if (Double.isInfinite(value)) {
            // Handle infinity (clamp to reasonable bounds)
            if (value > 0) {
                data.put(i, Double.MAX_VALUE);
            } else {
                data.put(i, -Double.MAX_VALUE);
            }
        }
    }
    
  3. Memory Alignment for Performance:

    // For native library integration, ensure proper alignment
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
    // Ensure buffer is aligned to double size (8 bytes)
    if (byteBuffer.position() % 8 != 0) {
        byteBuffer.position(byteBuffer.position() + (8 - byteBuffer.position() % 8));
    }
    DoubleBuffer doubleBuffer = byteBuffer.asDoubleBuffer();
    
  4. Bulk Operations for Performance:

    // Use bulk operations for better performance
    double[] batch = new double[100];
    buffer.get(batch);  // Single call vs 100 individual gets
    
    // For maximum performance with heap buffers:
    double[] data = new double[1000];
    DoubleBuffer buffer = DoubleBuffer.wrap(data);  // Zero overhead
    
  5. Byte Order Considerations:

    // Double representation depends on byte order
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
    byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // x86 native order
    DoubleBuffer doubleBuffer = byteBuffer.asDoubleBuffer();
    
    // Always specify byte order explicitly for portable code
    

Common Pitfalls #

  1. Floating-Point Precision: Direct equality comparisons of double values
  2. NaN Propagation: NaN values can silently propagate through calculations
  3. Byte Order Errors: Wrong byte order corrupts double values completely
  4. Memory Consumption: Double buffers consume 8× memory of equivalent byte buffers
  5. Performance Bottlenecks: Using single-element operations instead of bulk ops
  6. Alignment Issues: Unaligned access can cause performance penalties or crashes
  7. Special Value Handling: Not checking for NaN/infinity in scientific calculations

Internal Implementation #

HeapDoubleBuffer #

class HeapDoubleBuffer extends DoubleBuffer {
    final double[] hb;       // The underlying double array
    final int offset;        // The offset into the array
    
    HeapDoubleBuffer(int cap, int lim) {
        super(-1, 0, lim, cap, null);
        hb = new double[cap];
        offset = 0;
    }
    
    HeapDoubleBuffer(double[] buf, int off, int len) {
        super(-1, off, off + len, buf.length, null);
        hb = buf;
        offset = 0;
    }
    
    public double get() {
        return hb[ix(nextGetIndex())];
    }
    
    public DoubleBuffer put(double x) {
        hb[ix(nextPutIndex())] = x;
        return this;
    }
    
    int ix(int i) {
        return i + offset;
    }
}

DirectDoubleBuffer (via ByteBuffer view) #

// DoubleBuffer view of direct ByteBuffer
class ByteBufferAsDoubleBufferB extends DoubleBuffer {
    private final ByteBuffer bb;
    private final int offset;
    
    ByteBufferAsDoubleBufferB(ByteBuffer bb) {
        super(-1, 0, bb.remaining() >> 3, bb.remaining() >> 3);
        this.bb = bb.duplicate().order(ByteOrder.BIG_ENDIAN);
        this.offset = bb.position();
    }
    
    public double get() {
        return bb.getDouble(ix(nextGetIndex()));
    }
    
    public DoubleBuffer put(double x) {
        bb.putDouble(ix(nextPutIndex()), x);
        return this;
    }
    
    int ix(int i) {
        return (i << 3) + offset;
    }
}

Performance Optimization #

Vectorized Double Operations #

public class DoubleVectorProcessor {
    private DoubleBuffer buffer;
    private final int vectorSize;
    
    public DoubleVectorProcessor(int bufferSize, int vectorSize) {
        this.buffer = DoubleBuffer.allocate(bufferSize);
        this.vectorSize = vectorSize;
    }
    
    public void processVectors(DoubleSupplier supplier) {
        double[] vector = new double[vectorSize];
        
        while (true) {
            // Fill buffer with vectors
            while (buffer.hasRemaining() >= vectorSize) {
                for (int i = 0; i < vectorSize; i++) {
                    double value = supplier.getAsDouble();
                    if (Double.isNaN(value)) {  // End sentinel
                        flush();
                        return;
                    }
                    buffer.put(value);
                }
            }
            
            // Process full vectors
            buffer.flip();
            while (buffer.remaining() >= vectorSize) {
                buffer.get(vector);
                processVector(vector);
            }
            buffer.compact();
        }
    }
    
    private void processVector(double[] vector) {
        // Vectorized operation...
    }
}

Memory-Mapped Double File Processing #

// Process large double file with memory mapping
public void processDoubleFile(Path filePath) throws IOException {
    try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) {
        long fileSize = channel.size();
        
        // Ensure file size is multiple of 8 bytes
        if (fileSize % 8 != 0) {
            throw new IOException("File size not aligned to 8 bytes");
        }
        
        long position = 0;
        
        while (position < fileSize) {
            long size = Math.min(Integer.MAX_VALUE, fileSize - position);
            MappedByteBuffer mappedBuffer = channel.map(
                FileChannel.MapMode.READ_ONLY, position, size);
            
            mappedBuffer.order(ByteOrder.BIG_ENDIAN);
            DoubleBuffer doubleBuffer = mappedBuffer.asDoubleBuffer();
            
            // Process double buffer
            processDoubleBuffer(doubleBuffer);
            
            position += size;
        }
    }
}

Double Buffer Pool for High-Performance Computing #

public class DoubleBufferPool {
    private final ConcurrentMap<Integer, Queue<DoubleBuffer>> pools = 
        new ConcurrentHashMap<>();
    
    public DoubleBuffer acquire(int size) {
        Queue<DoubleBuffer> pool = pools.computeIfAbsent(size, 
            k -> new ConcurrentLinkedQueue<>());
        
        DoubleBuffer buffer = pool.poll();
        if (buffer == null) {
            buffer = DoubleBuffer.allocate(size);
        }
        buffer.clear();
        return buffer;
    }
    
    public DoubleBuffer acquireDirect(int size) {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(size * 8);
        byteBuffer.order(ByteOrder.nativeOrder());
        return byteBuffer.asDoubleBuffer();
    }
    
    public void release(DoubleBuffer buffer) {
        buffer.clear();
        Queue<DoubleBuffer> pool = pools.get(buffer.capacity());
        if (pool != null) {
            pool.offer(buffer);
        }
    }
}