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 #
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 #
| Operation | HeapDoubleBuffer | DirectDoubleBuffer (via ByteBuffer) |
|---|---|---|
| Allocation | O(1) fast | O(n) slower (native allocation) |
| Double Access | O(1) direct array access | O(1) with bounds checking |
| Bulk Operations | Fast (System.arraycopy) | Slower (element-by-element) |
| Memory Usage | 8 bytes per double + overhead | 8 bytes per double + native overhead |
| Numerical Performance | Good (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 #
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 }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); } } }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();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 overheadByte 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 #
- Floating-Point Precision: Direct equality comparisons of double values
- NaN Propagation: NaN values can silently propagate through calculations
- Byte Order Errors: Wrong byte order corrupts double values completely
- Memory Consumption: Double buffers consume 8× memory of equivalent byte buffers
- Performance Bottlenecks: Using single-element operations instead of bulk ops
- Alignment Issues: Unaligned access can cause performance penalties or crashes
- 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);
}
}
}
Related Classes #
- ByteBuffer: Byte buffer with double view support
- FloatBuffer: Single-precision floating-point buffer
- LongBuffer: 64-bit integer buffer
- MappedByteBuffer: Memory-mapped file buffer