/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.f1x.util.format; /** * This class formats floating point number to textual representation with given precision. * Thread safe. * For large numbers this method falls back to java.lang.Double.toString(double). * Note: formatter always uses '.' dot as decimal separator (regardless of current locale). * * NOTE: This is an alternative to DoubleFormatter that has static formatting method (thread safe). * TODO: Replace DoubleFormatter by this implementation after additional testing. */ public abstract class DoubleFormatter2 { /** * Bit 63 represents the sign of the floating-point number. * * @see java.lang.Double#doubleToLongBits(double) */ private static final long SIGN_MASK = 0x8000000000000000L; private static final int MIN_PRECISION = 0; private static final int MAX_PRECISION = 18; private static final long[] MULTIPLIER_TABLE = { 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L, 100000000L, 1000000000L, 10000000000L, 100000000000L, 1000000000000L, 10000000000000L, 100000000000000L, 1000000000000000L, 10000000000000000L, 100000000000000000L, 1000000000000000000L }; private DoubleFormatter2() { } /** * Formats given double number with given precision into byte buffer. * * @param value a double value. * @param precision maximum number of digits after decimal point (e.g. 3). Truncated part will be rounded. * @param buffer formatted double value will be written in this byte array. * @param offset start index in which formatted value will be written. * @return the next index in byte buffer after writing formatted value. * @throws IllegalArgumentException if precision < 0 or 18 < precision, if value is NaN or Infinite. */ public static int format(double value, int precision, byte[] buffer, int offset) { if (precision < MIN_PRECISION) throw new IllegalArgumentException("precision < " + MIN_PRECISION); if (precision > MAX_PRECISION) throw new IllegalArgumentException("precision > " + MAX_PRECISION); if (Double.isNaN(value)) throw new IllegalArgumentException("NaN"); if (Double.isInfinite(value)) throw new IllegalArgumentException("Infinity"); long bits = Double.doubleToRawLongBits(value); boolean isNegative = (bits & SIGN_MASK) != 0; if (isNegative) { // reset sign bit bits ^= SIGN_MASK; value = Double.longBitsToDouble(bits); buffer[offset++] = '-'; } if (bits == 0) { buffer[offset++] = '0'; return offset; } // check whether we can represent integer and fractional parts of double number as long numbers if (value < 1e-18 || 1e18 < value) return CharSequenceFormatter.format(toString(value), buffer, offset); if (precision == 0) return LongFormatter.format(Math.round(value), buffer, offset); long integerPart = (long) value; offset = LongFormatter.format(integerPart, buffer, offset); long multiplier = getMultiplier(precision); long fractionalPart = Math.round((value - integerPart) * multiplier); if (fractionalPart == 0) return offset; buffer[offset++] = '.'; offset = addLeadingZerosIfNeeded(fractionalPart, precision, buffer, offset); // get rid of trailing zeros while (fractionalPart % 10 == 0) fractionalPart /= 10; return LongFormatter.format(fractionalPart, buffer, offset); } private static long getMultiplier(int precision) { return MULTIPLIER_TABLE[precision - 1]; } private static int addLeadingZerosIfNeeded(long fractionalPart, int precision, byte[] output, int offset) { int leadingZeros = precision - LongFormatter.stringSize(fractionalPart); for (int i = 0; i < leadingZeros; i++) output[offset++] = '0'; return offset; } private static String toString(double value) { // All exceptional cases have been covered // TODO: this leads to garbage return Double.toString(value); } }