Converting bytes, shorts, chars, and booleans to Strings Converting floats to Strings

- 104 - .appendcharForDigit[c10]; else if i 1000000 six digits ... Im sure you get the idea else if i 10000000 seven digits ... so just keep doing the same, but more else if i 100000000 eight digits ... because my editor doesnt like wasting all this space else if i 1000000000 nine digits ... on unnecessary repetitions else { ten digits s.appendcharForDigit[i1000000000]; s.appendcharForDigit[c=i1000000000100000000]; s.appendcharForDigit[c=10000000010000000]; s.appendcharForDigit[c=100000001000000]; s.appendcharForDigit[c=1000000100000]; s.appendcharForDigit[c=10000010000]; s.appendcharForDigit[c=100001000]; s.appendcharForDigit[c=1000100]; s.appendcharForDigit[c=10010]; s.appendcharForDigit[c10]; } } If you compare this implementation to executing StringBuffer.appendint , the algorithm listed here runs in less time for all except the latest VM, and creates two fewer objects [3] see Table 5-2 . This is faster than the JDK optimized version, has a smaller impact on garbage creation, and has all the other advantages previously listed for the long conversion i.e., it is easily generalized for other radix values, digits are iterated in order so you can write to a stream, and it is easier to alter for formatting without using temporary objects. Note that the long conversion method can also be improved using two of the three techniques we used for the int conversion method: inlining the magnitude method and unrolling the loop. [3] If the StringBuffer.appendint used the algorithm shown here, it would be faster for all JDK versions measured in this chapter, since the characters could be added directly to the char buffer without going through the StringBuffer.appendchar method. Table 5-2, Time Taken to Append an int to a StringBuffer VM 1.2 1.3 HotSpot 1.0 1.1.6 JDK int conversion 100 61 89 148 Optimized int conversion 84 60 81 111

5.3.3 Converting bytes, shorts, chars, and booleans to Strings

You can use the int conversion method for byte s and short s using overloading. You can make byte conversion even faster using a String array as a lookup table for the 256 byte values. The conversion of byte s and short s to String s in the JDK appears not to have been tuned to as high a standard as radix 10 int s up to JDK 1.3. This means that the int conversion algorithm shown previously, when applied to byte s and short s, is significantly faster than the JDK conversions and does not produce any temporary objects. When it comes to using the other data types, there is no need to handle boolean s in any special way: the Boolean.toString already uses canonical strings. And there is obviously nothing in particular that needs to be done for char s apart from making sure you add them to strings as characters, not numbers. - 105 -

5.3.4 Converting floats to Strings

Converting floating-point numbers to strings turns out to be hideously underoptimized in every version of the JDK up to 1.3 and maybe beyond. Looking at the JDK code and comments, it seems that no one has yet got around to tuning these conversions. Floating-point numbers can be converted using similar optimizations to the number conversions previously addressed. You need to check for and handle the special cases separately. You then scale the floats into an integer value and use the previously defined int conversion algorithm to convert to characters in order, ensuring that you format the decimal point at the correct position. The case of values between .001 and 10,000,000 are handled differently, because these are printed without exponent values; all other floats are printed with exponents. Finally, it would be possible to overload the float and double case, but it turns out that if you do this, the float does not convert as well in correctness or speed, so it is necessary to duplicate the algorithms for the float and double cases. Note that the printed values of float s and double s are, in general, only representative of the underlying value. This is true both for the JDK algorithms and the conversions here. There are times when the string representation comes out differently for the two implementations, and neither is actually more accurate. The algorithm used by the JDK prints the minimum number of digits possible, while maintaining uniqueness of the printed value with respect to the other floating-point values adjacent to the value being printed. The algorithm presented here prints the maximum number of digits not including trailing zeros regardless of whether some digits are not needed to distinguish the number from other numbers. For example, the Float.MIN_VALUE is printed by the JDK as 1.4E-45, whereas the algorithm here prints it as 1.4285714E-45. Because of the limitations in the accuracy of numbers, neither printed representation is more or less accurate compared to the underlying floating-point number actually held in Float.MIN_VALUE e.g., assigning both 1.46e-45F and 1.45e-45F to a float results in Float.MIN_VALUE being assigned. Note that the code that follows shortly uses the previously defined append method for appending long s to StringBuffers . Also note that the dot character has been hardcoded as the decimal separator character here for clarity, but it is straightforward to change for internationalization. This method of converting floats to strings has the same advantages as those mentioned previously for integral types, i.e., it is printed in digit order, no temporary objects are generated, etc. The double conversion see the next section is similar to the float conversion, with all the same advantages. In addition, both algorithms are several times faster than the JDK conversions. Normally, when you print out floating-point numbers, you print in a defined format with a specified number of digits. The default floating-point toString methods cannot format floating-point numbers; you must first create the string, then format it afterwards. The algorithm presented here could easily be altered to handle formatting floating-point numbers without using any intermediate strings. This algorithm is also easily adapted to handle rounding up or down; it already detects which side of the half value the number is on: public static final char[] NEGATIVE_INFINITY = {-,I,n,f,i,n,i,t,y}; public static final char[] POSITIVE_INFINITY = {I,n,f,i,n,i,t,y}; public static final char[] NaN = {N,a,N}; private static final int floatSignMask = 0x80000000; private static final int floatExpMask = 0x7f800000; private static final int floatFractMask= ~floatSignMask|floatExpMask; private static final int floatExpShift = 23; private static final int floatExpBias = 127; change dot to international character where this is used below - 106 - public static final char[] DOUBLE_ZERO = {0,.,0}; public static final char[] DOUBLE_ZERO2 = {0,.,0,0}; public static final char[] DOUBLE_ZERO0 = {0,.}; public static final char[] DOT_ZERO = {.,0}; private static final float[] f_magnitudes = { 1e-44F, 1e-43F, 1e-42F, 1e-41F, 1e-40F, 1e-39F, 1e-38F, 1e-37F, 1e-36F, 1e-35F, 1e-34F, 1e-33F, 1e-32F, 1e-31F, 1e-30F, 1e-29F, 1e-28F, 1e-27F, 1e-26F, 1e-25F, 1e-24F, 1e-23F, 1e-22F, 1e-21F, 1e-20F, 1e-19F, 1e-18F, 1e-17F, 1e-16F, 1e-15F, 1e-14F, 1e-13F, 1e-12F, 1e-11F, 1e-10F, 1e-9F, 1e-8F, 1e-7F, 1e-6F, 1e-5F, 1e-4F, 1e-3F, 1e-2F, 1e-1F, 1e0F, 1e1F, 1e2F, 1e3F, 1e4F, 1e5F, 1e6F, 1e7F, 1e8F, 1e9F, 1e10F, 1e11F, 1e12F, 1e13F, 1e14F, 1e15F, 1e16F, 1e17F, 1e18F, 1e19F, 1e20F, 1e21F, 1e22F, 1e23F, 1e24F, 1e25F, 1e26F, 1e27F, 1e28F, 1e29F, 1e30F, 1e31F, 1e32F, 1e33F, 1e34F, 1e35F, 1e36F, 1e37F, 1e38F }; public static void appendStringBuffer s, float d { handle the various special cases if d == Float.NEGATIVE_INFINITY s.appendNEGATIVE_INFINITY; else if d == Float.POSITIVE_INFINITY s.appendPOSITIVE_INFINITY; else if d = d s.appendNaN; else if d == 0.0 { can be -0.0, which is stored differently if Float.floatToIntBitsd floatSignMask = 0 s.append-; s.appendDOUBLE_ZERO; } else { convert negative numbers to positive if d 0 { s.append-; d = -d; } handle 0.001 up to 10000000 separately, without exponents if d = 0.001F d 0.01F { long i = long d 1E12F; i = i100 = 50 ? i100 + 1 : i100; s.appendDOUBLE_ZERO2; appendFractDigitss, i,-1; } else if d = 0.01F d 0.1F { long i = long d 1E11F; i = i100 = 50 ? i100 + 1 : i100; s.appendDOUBLE_ZERO; appendFractDigitss, i,-1; } else if d = 0.1F d 1F { long i = long d 1E10F; i = i100 = 50 ? i100 + 1 : i100; s.appendDOUBLE_ZERO0; appendFractDigitss, i,-1; } else if d = 1F d 10F { - 107 - long i = long d 1E9F; i = i100 = 50 ? i100 + 1 : i100; appendFractDigitss, i,1; } else if d = 10F d 100F { long i = long d 1E8F; i = i100 = 50 ? i100 + 1 : i100; appendFractDigitss, i,2; } else if d = 100F d 1000F { long i = long d 1E7F; i = i100 = 50 ? i100 + 1 : i100; appendFractDigitss, i,3; } else if d = 1000F d 10000F { long i = long d 1E6F; i = i100 = 50 ? i100 + 1 : i100; appendFractDigitss, i,4; } else if d = 10000F d 100000F { long i = long d 1E5F; i = i100 = 50 ? i100 + 1 : i100; appendFractDigitss, i,5; } else if d = 100000F d 1000000F { long i = long d 1E4F; i = i100 = 50 ? i100 + 1 : i100; appendFractDigitss, i,6; } else if d = 1000000F d 10000000F { long i = long d 1E3F; i = i100 = 50 ? i100 + 1 : i100; appendFractDigitss, i,7; } else { Otherwise the number has an exponent int magnitude = magnituded; long i; if magnitude -35 i = long d1E10F f_magnitudes[magnitude + 45]; else i = long d f_magnitudes[magnitude + 44 - 9]; i = i100 = 50 ? i100 + 1 : i100; appendFractDigitss, i, 1; s.appendE; appends,magnitude; } } return this; } private static int magnitudefloat d { return magnituded,Float.floatToIntBitsd; } private static int magnitudefloat d, int floatToIntBits - 108 - { int magnitude = int floatToIntBits floatExpMask floatExpShift - floatExpBias 0.301029995663981; if magnitude -44 magnitude = -44; else if magnitude 38 magnitude = 38; if d = f_magnitudes[magnitude+44] { whilemagnitude 39 d = f_magnitudes[magnitude+44] magnitude++; magnitude--; return magnitude; } else { whilemagnitude -45 d f_magnitudes[magnitude+44] magnitude--; return magnitude; } } private static void appendFractDigitsStringBuffer s, long i, int decimalOffset { long mag = magnitudei; long c; while i 0 { c = imag; s.appendcharForDigit[int c]; decimalOffset--; if decimalOffset == 0 s.append.; change to use international character c = mag; if c = i i -= c; mag = mag10; } if i = 0 s.appendcharForDigit[int i]; else if decimalOffset 0 { s.appendZEROS[decimalOffset]; ZEROS[n] is a char array of n 0s decimalOffset = 1; } decimalOffset--; if decimalOffset == 0 s.appendDOT_ZERO; else if decimalOffset == -1 s.append0; } The conversion times compared to the JDK conversions are shown in Table 5-3 . Note that if you are formatting floats, the JDK conversion requires additional steps and so takes longer. However, the method shown here is likely to take even less time, as you normally print fewer digits that require fewer loop iterations. Table 5-3, Time Taken to Append a float to a StringBuffer VM 1.2 1.3 HotSpot 1.0 1.1.6 - 109 - JDK float conversion 100 85 270 128 Optimized float conversion 26 30 95 33

5.3.5 Converting doubles to Strings