JDK-5015891 : Byte.parseByte("80",16) throws NumberFormatException
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 1.4.2
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2004-03-18
  • Updated: 2004-03-19
  • Resolved: 2004-03-19
Related Reports
Relates :  
Description

Name: gm110360			Date: 03/18/2004


FULL PRODUCT VERSION :
java version "1.4.2_04"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_04-b05)
Java HotSpot(TM) Client VM (build 1.4.2_04-b05, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows 2000/XP [Version 5.00.2195]

A DESCRIPTION OF THE PROBLEM :
received a NumberFormatException with the following line of code:

Byte.parseByte("80",16);

REGRESSION.  Last worked in version tiger-beta

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
call Byte.parseByte("80",16);

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I expected the numeric value of 128 to be returned. If you convert 0x80 to decimal using a calculator you get 128.
ACTUAL -
Got a NumberFormatException


ERROR MESSAGES/STACK TRACES THAT OCCUR :
NumberFormatException

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
public class Test {
	public static void main(String[] args) {
		byte myByte = Byte.parseByte("80",16);
		System.out.println("Output: " + myByte);
	}
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
I changed the line of code to

byte myByte = (byte) Integer.parseInt("80",16) and got a result of -128

This is exactly what Byte.parseByte() does but it throws an exception.
I can't figure out why because -128 is within the MIN / MAX range check inside parseByte that causes the number format exception.
(Incident Review ID: 243967) 
======================================================================

Name: gm110360			Date: 03/18/2004


A DESCRIPTION OF THE REQUEST :
Currently when using Byte.parseByte(String, int) to convert a number that is signed by setting the most significant bit ON is not supported, instead a NumberFormatException is thrown.

This does not follow traditional conversion expectations as is demonstrated by the number of bugs reported by Java users who are confused by such behavior of the parseByte method.

JUSTIFICATION :
Ex. A signed one byte number (using all 8 bits) such as -100 can be validly written as a two character HEX string using two different methods: Method 1: -64  Method 2: 9C

Since Java treats bytes as signed you must interept 9C as decimal -100 and not 156.  The (byte) casting operater demonstrates this because if you convert an int 156 to a byte using (byte) it will properly convert the number to -100.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The parseByte method should support both string formats for indicating that a number is negative for numbers with a radix that is based on the power of 2, such as 2,8,and 16.

It would also be great if the method threw exceptions that indicated the converted number did not fit into the target data type instead of just throwing a NumberFormatException.

See sample code for a revised implementation of the parseByte method.

This same suggestion would also need to be applied to Integer.parseInt and Long.parseLong.

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

public class Test
{

   public static int MIN_VALUE = -128;
   public static int MAX_VALUE = 127;
   public static int MAX_VALUE_UNSIGNED = 255;

public static void main(String[] args)
{

   try
   {
      byte b = parseByte("-80", 16);

      testParseByte();

   }
   catch(Exception e)
   {
      System.err.println(e.getMessage());
   }

   try
   {
      byte b = parseByte("-98", 16);
   }
   catch(Exception e)
   {
      System.err.println("Exception for parseByte(-98,16)");
      e.printStackTrace();
   }

   try
   {
      byte b = parseByte("98CA", 16);
   }
   catch(Exception e)
   {
      System.err.println("Exception for parseByte(98CA,16)");
      e.printStackTrace();
   }

   try
   {
      byte b = parseByte("3G", 16);
   }
   catch(Exception e)
   {
      System.err.println("Exception for parseByte(3G,16)");
      e.printStackTrace();
   }
}

public static void testParseByte()
{
   int   intVal;
   byte  byteVal1, byteVal2;

   String[] hArray1 =
   {
   "00","01","02","03","04","05","06","07","08","09","0A","0B","0C","0D","0E","0F",
   "10","11","12","13","14","15","16","17","18","19","1A","1B","1C","1D","1E","1F",
   "20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F",
   "30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F",
   "40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F",
   "50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F",
   "60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F",
   "70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E","7F",
   "80","81","82","83","84","85","86","87","88","89","8A","8B","8C","8D","8E","8F",
   "90","91","92","93","94","95","96","97","98","99","9A","9B","9C","9D","9E","9F",
   "A0","A1","A2","A3","A4","A5","A6","A7","A8","A9","AA","AB","AC","AD","AE","AF",
   "B0","B1","B2","B3","B4","B5","B6","B7","B8","B9","BA","BB","BC","BD","BE","BF",
   "C0","C1","C2","C3","C4","C5","C6","C7","C8","C9","CA","CB","CC","CD","CE","CF",
   "D0","D1","D2","D3","D4","D5","D6","D7","D8","D9","DA","DB","DC","DD","DE","DF",
   "E0","E1","E2","E3","E4","E5","E6","E7","E8","E9","EA","EB","EC","ED","EE","EF",
   "F0","F1","F2","F3","F4","F5","F6","F7","F8","F9","FA","FB","FC","FD","FE","FF"
   };

   String[] hArray2 =
   {
   "00","01","02","03","04","05","06","07","08","09","0A","0B","0C","0D","0E","0F",
   "10","11","12","13","14","15","16","17","18","19","1A","1B","1C","1D","1E","1F",
   "20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F",
   "30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F",
   "40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F",
   "50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F",
   "60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F",
   "70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E","7F",
   "-80", "-7F","-7E","-7D","-7C","-7B","-7A","-79","-78","-77","-76","-75","-74","-73","-72","-71","-70",
   "-6F","-6E","-6D","-6C","-6B","-6A","-69","-68","-67","-66","-65","-64","-63","-62","-61","-60",
   "-5F","-5E","-5D","-5C","-5B","-5A","-59","-58","-57","-56","-55","-54","-53","-52","-51","-50",
   "-4F","-4E","-4D","-4C","-4B","-4A","-49","-48","-47","-46","-45","-44","-43","-42","-41","-40",
   "-3F","-3E","-3D","-3C","-3B","-3A","-39","-38","-37","-36","-35","-34","-33","-32","-31","-30",
   "-2F","-2E","-2D","-2C","-2B","-2A","-29","-28","-27","-26","-25","-24","-23","-22","-21","-20",
   "-1F","-1E","-1D","-1C","-1B","-1A","-19","-18","-17","-16","-15","-14","-13","-12","-11","-10",
   "-0F","-0E","-0D","-0C","-0B","-0A","-09","-08","-07","-06","-05","-04","-03","-02","-01"
   };

   for (int i=0; i < hArray1.length; i++)
   {
      byteVal1 = parseByte(hArray1[i], 16);
      byteVal2 = parseByte(hArray2[i], 16);

      System.out.println(hArray1[i]+"="+byteVal1+" : "+hArray2[i]+"="+byteVal2);
   }
}

// Revised implementation for Byte.parseByte()

public static byte parseByte(String s, int radix) throws NumberFormatException {

   int i = Integer.parseInt(s, radix);

   if (i < MIN_VALUE)
       throw new NumberUnderflowException();

   if (i > MAX_VALUE_UNSIGNED)
       throw new NumberOverflowException();

   return (byte) i;
}

private static class NumberOverflowException extends java.lang.RuntimeException
{

public NumberOverflowException() {
   super();
}

}

private static class NumberUnderflowException extends java.lang.RuntimeException
{

public NumberUnderflowException() {
   super();
}

}


}
---------- END SOURCE ----------
(Review ID: 244073)
======================================================================

Comments
EVALUATION I was not able to reproduce the "expected" behavior with Tiger beta (or 1.4.2 or 1.3.1). Throwing an exception is the correct, documented behavior for this situation: "Parses the string argument as a *signed* byte in the radix specified by the second argument." That is, a negative value is indicated with a "-" character and not be a leading 1. Therefore, "80" is converted to decimal 128 and 128 is outside of byte's range from -128 to 127. However, the integer value 128 is converted to a byte value of -128 since the low-order 8 bits of the integer are used to create the byte. Closing as not a bug. ###@###.### 2004-03-18
18-03-2004