JDK-4804304 : Direct{Double,Float}Buffers change values if buffer order != native order (x86)
  • Type: Bug
  • Status: Resolved
  • Resolution: Fixed
  • Component: core-libs
  • Sub-Component: java.nio
  • Priority: P2
  • Affected Version: 1.4.1
  • OS: linux,windows_2000
  • CPU: x86
  • Submit Date: 2003-01-16
  • Updated Date: 2003-04-28
  • Resolved Date: 2003-01-31
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availabitlity Release.

To download the current JDK release, click here.
1.4.2 b16Resolved
Related Reports
Duplicate :  
Relates :  
Relates :  

Name: rmT116609			Date: 01/16/2003

java version "1.4.1_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_01-b01)
Java HotSpot(TM) Client VM (build 1.4.1_01-b01, mixed mode)

Microsoft Windows 2000 [Version 5.00.2195]

Microsoft Windows XP [Version 5.1.2600]

If you put double numbers into a direct DoubleBuffer and then get them back out, some specific numbers change slightly. The attached program has one example of such number.

One out of several thousand numbers generated with Math.random() produces this behavior. The difference between the "before" and "after" numbers converted to long bits is always in the same bit x800.

1. Run attached program.

Actual Results:
before = 0.5121609353879392 3fe0639f5478f57f
after  = 0.5121609353881665 3fe0639f5478fd7f
before ^ after = 800

Expected Results:
The same numbers before and after.

This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;

public class BugDemo {
    public static void main(String[] args) {
        double before = 0.5121609353879392;
        // put before into buffer and then get it back
        DoubleBuffer buffer = ByteBuffer.allocateDirect(8).asDoubleBuffer();
        buffer.put(0, before);
        double after = buffer.get(0);
        // print out results
        long beforeBits = Double.doubleToLongBits(before);
        long afterBits = Double.doubleToLongBits(after);
        System.out.println("before = " + before + " " + Long.toHexString
        System.out.println("after  = " + after + " " + Long.toHexString
        System.out.println("before ^ after = " + Long.toHexString(beforeBits ^

---------- END SOURCE ----------
(Review ID: 167220) 

CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: mantis-beta tiger FIXED IN: mantis-beta tiger INTEGRATED IN: mantis-b16 mantis-beta tiger tiger-b05

WORK AROUND The problem does not occur if the buffer's byte order matches the machine's native byte order. The default byte order for all direct buffers (regardless of platform) is ByteOrder.LITTLE_ENDIAN. This is not the native byte order for x86 processors. Use the following code to set the appropriate byte order. ByteBuffer bb = ByteBuffer.allocateDirect(X); bb.order(ByteOrder.nativeOrder(); Naturally, using native byte order is also ideal for performance reasons. The problem also does not occur (regardless of byte order) if you don't use direct buffers. ByteBuffer bb = ByteBuffer.allocate(X); -- iag@sfbay 2003-01-16

EVALUATION This problem is reproducible on winNT and linux. It is not reproducible on solaris-sparc. I have verified that the problem has existed since DoubleBuffers were introduced in jdk1.4. In the case where the native byte order does not match the buffer's byte order, this is the implementation of DirectDoubleBuffer.put: public DoubleBuffer put(double x) { unsafe.putDouble(ix(nextPutIndex()), Bits.swap(x)); return this; } Here is the implementation of DirectDoubleBuffer.put for matching byte orders: public DoubleBuffer put(double x) { unsafe.putDouble(ix(nextPutIndex()), (x)); return this; } The only difference between these two methods is the use of Bits.swap. We never use Bits.swap for heap buffers. -- iag@sfbay 2003-01-16 The problem here is that the DirectDoubleBuffer code is treating a swapped double as a double. This often works, but not when the swapped bits happen to represent a NaN. For the particular case described above we have x = 0.5121609353879392, y = Double.doubleToRawLongBits(x) = 0x3fe0639f5478f57f, z = swap(y) = 0x7ff578549f63e03f, which represents a NaN. At this point if we transform z back into a double and look at the resulting raw bits we get w = Double.doubleToRawLongBits(Double.longBitsToDouble(z)) = 0x7ffd78549f63e03f, which is a different NaN but is the value that's actually stored in the buffer. The long-to-double conversion, which is done in hardware, has set bit 0x0008000000000000 in the result. When this value is read from the buffer, re-swapped, and then converted back to a double we get the incorrect value Double.longBitsToDouble(swap(Double.doubleToRawLongBits(w))) = 0.5121609353881665 != x. The fix is to change the Direct{Float,Double}Buffer code to store and load swapped doubles or floats as longs or ints, respectively. This is also a slight performance improvement since there's no reason to convert swapped bits twice. -- ###@###.### 2003/1/17 Some more detail on the previous evaluation. IEEE 754 arithmetic defines two kinds of NaN, signaling and quiet. When signaling NaNs are used in an operation, possibly including a load or store, they are changed into quiet NaNs. The x86 and most other processors do this conversion by writing a 1 into the highest bit of the significand, i.e. 0x0008000000000000L, which is the change being observed. On the x86, this conversion happens when a float or double signaling NaN is loading onto the x87 stack. As discussed in bug 4660849, x86 calling conventions require functions which return doubles to return them on the top of the x87 stack. Therefore, as long as longBitsToDouble is implemented as a normal function, the method cannot return the bit pattern of a signaling NaN. Implementing the bitwise conversion functions as intrinsics with load and store instructions may be able to avoid this problem. SPARC does not perform a NaN conversion on floating-point load or store. Bit patterns this problem affects are 0xyyyyyyyy(7|f)ff([0-7])yyyy where y is any bit pattern. I agree with the fix proposed above. ###@###.### 2003-01-17 For the record, a quick-and-dirty test of 10^8 pseudorandom samples shows that this error affects about 1 in 1000 random float values and about 1 in 8200 random double values. -- ###@###.### 2003/1/20 The float bit patterns with this problem are 0xyyyy(7|f)f(8-B)y where y is any bit pattern. ###@###.### 2003-01-29
186-11-01 0