United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-4804304 Direct{Double,Float}Buffers change values if buffer order != native order (x86)
JDK-4804304 : Direct{Double,Float}Buffers change values if buffer order != native order (x86)

Details
Type:
Bug
Submit Date:
2003-01-16
Status:
Resolved
Updated Date:
2003-04-28
Project Name:
JDK
Resolved Date:
2003-01-31
Component:
core-libs
OS:
linux,windows_2000
Sub-Component:
java.nio
CPU:
x86
Priority:
P2
Resolution:
Fixed
Affected Versions:
1.4.1
Fixed Versions:
1.4.2 (b16)

Related Reports
Backport:
Duplicate:
Relates:
Relates:

Sub Tasks

Description

Name: rmT116609			Date: 01/16/2003


FULL PRODUCT VERSION :
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)

FULL OPERATING SYSTEM VERSION :
Microsoft Windows 2000 [Version 5.00.2195]

ADDITIONAL OPERATING SYSTEMS :
Microsoft Windows XP [Version 5.1.2600]


A DESCRIPTION OF THE PROBLEM :
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.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Run attached program.


EXPECTED VERSUS ACTUAL BEHAVIOR :
Actual Results:
before = 0.5121609353879392 3fe0639f5478f57f
after  = 0.5121609353881665 3fe0639f5478fd7f
before ^ after = 800

Expected Results:
The same numbers before and after.

REPRODUCIBILITY :
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
(beforeBits));
        System.out.println("after  = " + after + " " + Long.toHexString
(afterBits));
        System.out.println("before ^ after = " + Long.toHexString(beforeBits ^
afterBits));
    }
}

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

                                    

Comments
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
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
                                     
2003-01-16
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


                                     
2004-06-14



Hardware and Software, Engineered to Work Together