United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-4860749 REGRESSION: Wrong double calculation result for Pi (Windows only)
JDK-4860749 : REGRESSION: Wrong double calculation result for Pi (Windows only)

Details
Type:
Bug
Submit Date:
2003-05-08
Status:
Resolved
Updated Date:
2003-07-14
Project Name:
JDK
Resolved Date:
2003-05-13
Component:
client-libs
OS:
windows_2000
Sub-Component:
2d
CPU:
x86
Priority:
P1
Resolution:
Fixed
Affected Versions:
1.4.1_02,1.4.2
Fixed Versions:
1.4.2 (b23)

Related Reports
Backport:
Relates:
Relates:

Sub Tasks

Description

Name: rmT116609			Date: 05/08/2003


FULL PRODUCT VERSION :
D:\Temp>"c:\Program Files\Java\j2re1.4.2\bin\java" -version
java version "1.4.2-beta"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2-beta-b19)
Java HotSpot(TM) Client VM (build 1.4.2-beta-b19, mixed mode)

FULL OS VERSION :
Microsoft Windows 2000 [Version 5.00.2195]

A DESCRIPTION OF THE PROBLEM :
On this small benchmark application, the Pi calculation gives a wrong result, when running on JDK 1.4.2 Beta.

This does not occur with other JDK versions or when extracting the Pi calculation code from the program. So I suppose it depends on the context (GC running before...). Using strictfp does not change the behavior.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the Zjb program

java -cp . Zjb

EXPECTED VERSUS ACTUAL BEHAVIOR :
With other JDK: Pi=3.141591698659554

With JDK 1.4.2 Beta: Pi=3.1413934230804443

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Label;
import java.awt.List;
import java.awt.Panel;
import java.awt.TextField;
import java.awt.Toolkit;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;


/**
 * Zjb: Zaurus Java Bechmark
 * @author GenePi
 */
class Zjb
	extends Frame
{
	static Zjb _mainWindow;
	
	
	/**
	 * Number of benchmark runs.
	 */
	private final TextField _runs;
	
	
	/**
	 * Results list
	 */
	private final List _results;
	
	
	/**
	 * Wait, program is thinking...
	 */
	private final Label _wait;
	
	
	/**
	 * Start button
	 */
	private final Button _start;
	
	
	/**
	 * Benchmark running
	 */
	private volatile boolean _running = false;
	
	
	/**
	 * Layout the main window.
	 **/
	Zjb()
	{
		super("Zaurus java benchmark 1.0");

		setLayout(new BorderLayout());

		// Input fields
		Panel top = new Panel(new GridLayout(1, 0));
		top.add(new Label("Number of runs"));
		_runs = new TextField("1");
		top.add(_runs);

		add(top, BorderLayout.NORTH);

		// Results list
		_results = new List();
		add(_results, BorderLayout.CENTER);

		// Start button
		final Panel bottom = new Panel(new FlowLayout(FlowLayout.RIGHT));
		_wait = new Label();
		bottom.add(_wait);
		_start = new Button("Start");
		_start.addActionListener(new ActionListener()
		{
			public void actionPerformed(final ActionEvent evt)
			{
				if (!_running)
				{
					// Clear previous results and start benchmark.
					_results.clear();
					_start.setLabel("Stop");
					_wait.setText("Running...          ");
					bottom.validate();
					_running = true;
				}
				else
				{
					_start.setLabel("Start");
					_wait.setText("");
					_running = false;
				}
			}
		});
		bottom.add(_start);

		// Quit button
		final Button quit = new Button("Quit");
		quit.addActionListener(new ActionListener()
		{
			public void actionPerformed(final ActionEvent evt)
			{
				System.exit(0);
			}
		});
		bottom.add(quit);

		add(bottom, BorderLayout.SOUTH);

		// Exit when main window closes
		addWindowListener(new WindowAdapter()
		{
			public void windowClosing(final WindowEvent evt)
			{
				System.exit(0);
			}
		});

		Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
		setSize(dim);
		validate();
	}
	
	
	
	/**
	 * The benchmarks
	 *
	 * @param runs Number of runs
	 */
	private static void runBenchmarks(final int runs)
	{
		long start;
		long end;
		long totalStart;
		long totalEnd;
		
		// Integer arithmetic
		start = System.currentTimeMillis();
		totalStart = start;
		int resultInt = 0;
		for (int i = 0; i < runs; i++)
		{
			resultInt = ackerman(3, 9);
			// resultInt = ackerman(3, 7);
		}
		end = System.currentTimeMillis();
		_mainWindow._results.add("Integer arithmetic: " + ((end - start) / 1000.0) + " s [Ack(3,9)=" + resultInt + "]");
		
		if (!_mainWindow._running)
		{
			return;
		}
		
		
		// Float and double
		start = System.currentTimeMillis();
		double resultDouble = 0.0;
		for (int i = 0; i < runs; i++)
		{
			resultDouble = 0.0;
			for (int j = 1; j < 1000000; j++)
			{
				resultDouble += 1.0 / ((double) j * (double) j);
			}
			resultDouble = Math.sqrt(resultDouble * 6.0);
		}
		end = System.currentTimeMillis();
		_mainWindow._results.add("Double arithmetic: " + ((end - start) / 1000.0) + " s [Pi=" + resultDouble + "]");
		
		if (!_mainWindow._running)
		{
			return;
		}
		
		
		// Big operations
		start = System.currentTimeMillis();
		BigInteger resultBig = new BigInteger("1");
		for (int i = 0; i < runs; i++)
		{
			resultBig = fact(3000);
		}
		end = System.currentTimeMillis();
		_mainWindow._results.add("Infinite arithmetic: " + ((end - start) / 1000.0) + " s [3000!=" + resultBig.toString().substring(1, 20) + "...]");
		
		if (!_mainWindow._running)
		{
			return;
		}
		
		
		// Strings
		start = System.currentTimeMillis();
		String resultString = null;
		for (int i = 0; i < runs; i++)
		{
			final String alphabet = " qwertyuioplkjhgfdsazxcvbnm0789456123./*";
			StringBuffer buf = new StringBuffer();
			for (int j = 0; j < 100000; j++)
			{
				int pos = j % alphabet.length();
				buf.append(alphabet.substring(pos, pos + 1));
			}
			resultString = buf.toString();
		}
		end = System.currentTimeMillis();
		_mainWindow._results.add("Strings: " + ((end - start) / 1000.0) + " s [" + resultString.substring(1, 20) + "...]");
		
		if (!_mainWindow._running)
		{
			return;
		}
		

		// Drawing
		start = System.currentTimeMillis();
		for (int i = 0; i < runs; i++)
		{
			final int size = 200;
			Dialog dialog = new Dialog(_mainWindow, "Drawing...", true);
			dialog.add(new TestPanel(dialog));
			dialog.setSize(size, size);
			dialog.show();
		}
		end = System.currentTimeMillis();
		_mainWindow._results.add("Drawing: " + ((end - start) / 1000.0) + " s");
		
		if (!_mainWindow._running)
		{
			return;
		}
		

		// File copy
		start = System.currentTimeMillis();
		String resultIO = "OK";
		loopIO:
		for (int i = 0; i < runs; i++)
		{
			final String tempName = "/tmp/Zjb.tmp";
			try
			{
				BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(tempName));
				for (int j = 0; j < 1000000; j++)
				{
					out.write((byte) j);
				}
				out.close();
				
				BufferedInputStream in = new BufferedInputStream(new FileInputStream(tempName));
				for (int j = 0; j < 1000000; j++)
				{
					int r = in.read();
					if ((byte) r != (byte) j)
					{
						resultIO = "Failed";
						System.err.println("Content mismatch at " + j);
						break loopIO;
					}
				}
				in.close();
				
				new File(tempName).delete();
			}
			catch(IOException ioe)
			{
				resultIO = "Failed";
				System.err.println(ioe);
				break loopIO;
			}
		}
		end = System.currentTimeMillis();
		_mainWindow._results.add("Files I/O: " + ((end - start) / 1000.0) + " s [1MB written/read/deleted: " + resultIO + "]");
		
		totalEnd = end;
		_mainWindow._results.add("");
		_mainWindow._results.add("Total: " + ((totalEnd - totalStart) / 1000.0) + " s");
	}
	
	
	/**
	 * Utility functions: Ackerman function
	 *
	 * @param m
	 * @param n
	 */
	private static int ackerman(final int m, final int n)
	{
		if (m == 0)
		{
			return (n + 1);
		}
		else if (n == 0)
		{
			return (ackerman(m - 1, 1));
		}
		else
		{
			return ackerman(m - 1, ackerman(m, (n - 1)));
		}
	}
	
	
	/**
	 * Factorial of big numbers.
	 *
	 * @param n
	 * @return
	 */
	private static BigInteger fact(final int n)
	{
		final BigInteger one = new BigInteger("1");
		BigInteger num = new BigInteger("1");
		BigInteger fact = new BigInteger("1");
		for (int i = 2; i <= n; i++)
		{
			num = num.add(one);
			fact = fact.multiply(num);
		}
		
		return fact;
	}
	
	
	/**
	 * Benchmark entry point.
	 *
	 * @param args Command line arguments
	 */
	public static void main(String[] args)
	{
		_mainWindow = new Zjb();
		_mainWindow.show();
		
		synchronized (Zjb.class)
		{
			while (true)
			{
				try
				{
					Zjb.class.wait(500L);
				}
				catch (InterruptedException ie)
				{
					// Wake
				}
				if (_mainWindow._running)
				{
					try
					{
						runBenchmarks(Integer.parseInt(_mainWindow._runs.getText()));
					}
					catch (NumberFormatException nfe)
					{
						_mainWindow._runs.setText("1");
						runBenchmarks(1);
					}
					_mainWindow._running = false;
					_mainWindow._start.setLabel("Start");
					_mainWindow._wait.setText("");
				}
			}
		}
	}
}


class TestPanel
	extends Panel
{
	/**
	 * The dialog containing the panel.
	 */
	private final Dialog _dialog;
	
	
	TestPanel(final Dialog dialog)
	{
		_dialog = dialog;
	}
	
	
	public void paint(final Graphics g)
	{
		Dimension dim = getSize();
		g.setColor(Color.white);
		g.fillRect(0, 0, dim.width, dim.height);
		for (int i = 0; i < 1000; i++)
		{
			Color color = new Color((int) (Math.random() * Integer.MAX_VALUE));
			int x = (int) (Math.random() * dim.width);
			int y = (int) (Math.random() * dim.height);
			int width = (int) (Math.random() * dim.width);
			int height = (int) (Math.random() * dim.height);
			g.setColor(color);
			g.fillRect(x, y, width, height);
		}
		
		g.setColor(Color.white);
		g.fillRect(0, 0, dim.width, dim.height);
		for (int i = 0; i < 1000; i++)
		{
			Color color = new Color((int) (Math.random() * Integer.MAX_VALUE));
			int x = (int) (Math.random() * dim.width);
			int y = (int) (Math.random() * dim.height);
			int width = (int) (Math.random() * dim.width);
			int height = (int) (Math.random() * dim.height);
			g.setColor(color);
			g.fillOval(x, y, width, height);
		}
		
		
		// Hide dialog when finished
		_dialog.hide();
	}
}
---------- END SOURCE ----------

(Review ID: 184686) 
======================================================================

                                    

Comments
WORK AROUND

Run the app with d3d disabled.  You can do this in 2 ways:
	java -Dsun.java2d.d3d=false <class>
and
	set J2D_D3D=false
	java <class>
Note that you can also set this environment variable in the system
control panel (on win2k/XP).

###@###.### 2003-05-09
                                     
2003-05-09
EVALUATION

After running a few tests, this appears only in -Xint mode on Windows/x86.
It doesn't appear with -client -Xcomp or -server -Xcomp on Windows, or on
Solaris/x86 in -Xint mode.

It also doesn't appear to be a VM bug. Dropping the 1.4.1 VM into a 1.4.2 JDK
exhibits the same problem. Dropping a 1.4.2 VM into a 1.4.1 JDK does not.

I recall seeing a recent putback to the AWT or 2D code which mucked with the
FPU control word, but seem to recall this code was only being executed on
some consumer versions of Windows (e.g. 98) and not on NT/2000, which is the
platform tested. Adding some relevant engineers to the interest list.

###@###.### 2003-05-09


Note that in C1 we only touch the FPU control word for methods that do long
sequences of single-precision floating point, and I'm pretty sure the same
holds true for C2. This test does only double-precision floating-point.
Running with -Xcomp -XX:CompileOnly=Zjb fails in the same way -Xint does.
This strongly indicates that the reason it works for both C1 and C2 in -Xcomp
mode is that we're compiling and running some other method which causes the
FPU control word to be reset.

###@###.### 2003-05-09


The attached boiled-down test case clearly indicates that the control word
change is occurring in native code called by the constructor for java.awt.Frame.
Interestingly, if one uses a javax.swing.JFrame for this purpose the bug doesn't
occur. Run the attached Zjb5.java with no command line arguments to reproduce
the bug, and with "-swing" to observe that the bug doesn't occur.

A stopgap measure would be to put in SAVE_CONTROLWORD and RESTORE_CONTROLWORD
in the native methods invoked by the Frame constructor. Better would be to
understand the new (since 1.4.1) API calls being used that are causing this
problem and bracket only those API calls.

###@###.### 2003-05-09

I think the bug Ken is referring to is 4351747 which was fixed by 2D in Mantis. 
###@###.### 2003-05-09


The affected code path is clearly the native code for the constructor of
java.awt.Frame. Please see the above evaluation and the test case.

###@###.### 2003-05-09
====================================

As noted by Ken this is nothing to do with 4351747 which was implemented
as suggested by the JDK's floating point engineer, and in any case doesn't
even get executed unless you run on Windows 95 AND you print. Neither applies
to this test case.

###@###.### 2003-05-09
============================

This is in fact a d3d-related problem; reassigning to 2D...

The problem appears to be d3d's desire to set the FPU into single-precision 
mode for the duration of the process.  This happens whenever we create a d3d
device.  There is a way to disable this (by forcing d3d to go into and out
of single-precision mode with every d3d call) by using the DDSCL_FPUPRESERVE
flag when we set the cooperative level for ddraw.  (We are obviously not
doing this now, thus the bug).  

You can work around the problem now using the d3d-disabling flags documented 
in the WorkAround field.  The fix is as simple as adding this FPU flag to
SetCooperativeLevel(), but we need to test with this flag to make sure that
it has no serious performance impact.

By the way, this bug should also be present in 1.4.1_02, since that code
is fairly similar to the current 1.4.2 code, with respect to the d3d code.
The bug may also be present in 1.4.1 and 1.4.1_01, although it looks like
(from the DirectX docs) this may be specific to the DX7 d3d interface, which
we only started using in 1.4.1_02 and 1.4.2.

###@###.### 2003-05-09

I've regressed the bug and found:
- The bug occurs in 1.4.1_02, but not 1.4.1 or 1.4.1_01.  This must mean that
the bug is related to the new use of the DX7 interfaces as of 1.4.2_02.
- The bug started occuring in 1.4.2 as of build 7 (which was the build that
integrated our 1.4.1_02 changes into 1.4.2).
- The -Dsun.java2d.d3d=false workaround works universally, regardless of
the release.  The environment variable workaround (J2D_D3D=false) has
no effect in 1.4.1_02 and has no effect in 1.4.2 until build 12.  This must
be the build where we first introduced that variable, to help with some
new d3d functionality.

The trick now is to validate that this fix is correct and to test for
quality and performance regressions.

###@###.### 2003-05-09

More information about the situations that can affect the appearance of this bug:
The D3D docs state that the behavior of having the FPU set to single-precision mode by default in SetCooperativeLevel() is new in DX7.  Prior to DX7, the default behavior was that which you now must request by using the new DDSCL_FPUPRESERVE flag in SetCooperativeLevel; you could request that d3d set the FPU into single-precision mode at creation time by using the DDSCL_FPUSETUP flag, but this flag was not enabled by default.  Now it is.  This maps to what we are seeing, where the bug first surfaced when we started using the DX7 interfaces in 1.4.1_02 and 1.4.2 b7.  I also note that disabling our use of DX7 internally makes the bug go away (although this is only a debugging option, not a real fix to the problem obviously).

Also, I traced some of the behavior in Swing vs AWT.  Although I did not chase the behavior into the exact call(s) that cause Swing to work, I did notice that the behavior difference comes in JRootPane, when we create the glass pane; if we disable that creation step things break in Swing just as they do in Awt.  Also, I can see d3d is, in fact, setting the FPU to single-precision mode in both cases; it's just that something after that fact causes the FPU to be reset to the default double-precision mode.  This could be through some call to LoadLibrary, which the D3D docs call out specifically as an event that may reset the FPU to double-precision.  It would be interesting to find out exactly what we are doing that causes this reset to happen, although it is somewhat of an academic issue at this point; it does not change the bug or the fix.

Another interesting point from the d3d docs is that the state of the FPU is thread-specific.  This means that creating the d3d device on one thread may set the FPU to single-precision mode on that thread, but another thread may still use double-precision.  For example, one test app I wrote showed that PI
was incorrectly calculated (using the sample code in the Description field) on the main thread after the creation of the Frame, but that a new thread calculated PI correctly after that.  This behavior implies a couple of interesting things: 
- Apps that perform calculations in threads other than that used to initialize d3d (usually the main thread or another thread that creates the first
onscreen or offscreen windows or images) will probably not see this bug because the FPU is still in double-precision mode in that other thread.
- Our current approach to multi-threaded rendering (where we allow calls into
DirectX from arbitrary threads) means that d3d may or may not be using 
single-precision in its calculations, based on what thread it is called from.
This is allowable behavior (according to the d3d docs); it just means that
d3d will suffer performance penalties from doing double-precision calculations
where single-precision is considered good enough.

note on the fix: we not only have to fix the call to SetCooperativeLevel() in 
ddrawObject.cpp's CreateDDrawObject() method, but also in ddrawObject.h's
dx7 implementation of SetCooperativeLevel().  This is because we may call
SetCooperativeLevel() at any time, based on whether we are going into or
out of fullscreen mode.

###@###.### 2003-05-12
                                     
2003-05-12
CONVERTED DATA

BugTraq+ Release Management Values

COMMIT TO FIX:
mantis-rc
tiger

FIXED IN:
mantis-rc
tiger

INTEGRATED IN:
mantis-b23
mantis-rc
tiger
tiger-b08


                                     
2004-06-14



Hardware and Software, Engineered to Work Together