JDK-4860749 : REGRESSION: Wrong double calculation result for Pi (Windows only)
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 1.4.1_02,1.4.2
  • Priority: P1
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2003-05-08
  • Updated: 2003-07-14
  • Resolved: 2003-05-13
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 Availability Release.

To download the current JDK release, click here.
Other
1.4.2 b23Fixed
Related Reports
Relates :  
Relates :  
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
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
14-06-2004

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
12-05-2003

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
09-05-2003