/
githubmirror
/
jdk22
Обзор
Документация
Войти
/
githubmirror
/
jdk22
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/java.base/share/classes/jdk/internal/math/FormattedFPDecimal.java
320 строк
10 KB
Raffaello Giulietti
8300869: Make use of the Double.toString(double) algorithm in java.util.Formatter
02 фев 2023, 22:10
02 фев 2023, 22:10
f696785
Код
Авторство
О чём код?
/* * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.internal.math; /* * This class provides support for the 'e', 'f' and 'g' conversions on double * values with sign bit 0. * It is worth noting that float values are converted to double values _before_ * control reaches code in this class. * * It delegates the conversion to decimal to class DoubleToDecimal to get * the decimal d selected by Double.toString(double) as a pair of integers * f and e meeting d = f 10^e. * It then rounds d to the appropriate number of digits, as per specification, * and extracts the digits of both the significand and, where required, the * exponent of the rounded value. * * Further processing like padding, sign, grouping, localization, etc., is the * responsibility of the caller. */ public final class FormattedFPDecimal { public static final char SCIENTIFIC = 'e'; public static final char PLAIN = 'f'; public static final char GENERAL = 'g'; private long f; private int e; // normalized to 0 when f = 0 private int n; private char[] digits; // ... and often the decimal separator as well private char[] exp; // [+-][e]ee, that is, sign and minimum 2 digits private FormattedFPDecimal() { } public static FormattedFPDecimal valueOf(double v, int prec, char form) { FormattedFPDecimal fd = new FormattedFPDecimal(); DoubleToDecimal.split(v, fd); return switch (form) { case SCIENTIFIC -> fd.scientific(prec); case PLAIN -> fd.plain(prec); case GENERAL -> fd.general(prec); default -> throw new IllegalArgumentException( String.format("unsupported form '%c'", form) ); }; } public void set(long f, int e, int n) { /* Initially, n = 0 if f = 0, and 10^{n-1} <= f < 10^n if f != 0 */ this.f = f; this.e = e; this.n = n; } public char[] getExponent() { return exp; } public char[] getMantissa() { return digits; } public int getExponentRounded() { return n + e - 1; } private FormattedFPDecimal plain(int prec) { /* * Rounding d = f 10^e to prec digits in plain mode means the same * as rounding it to the p = n + e + prec most significand digits of d, * with the understanding that p < 0 cuts off all its digits. */ round(n + e + (long) prec); // n + e is well inside the int range return plainChars(); } private FormattedFPDecimal plainChars() { if (e >= 0) { plainCharsPureInteger(); } else if (n + e > 0) { plainCharsMixed(); } else { plainCharsPureFraction(); } return this; } private void plainCharsPureInteger() { digits = new char[n + e]; fillWithZeros(n, n + e); fillWithDigits(f, 0, n); } private void plainCharsMixed() { digits = new char[n + 1]; long x = fillWithDigits(f, n + 1 + e, n + 1); digits[n + e] = '.'; fillWithDigits(x, 0, n + e); } private void plainCharsPureFraction() { digits = new char[2 - e]; long x = f; fillWithDigits(x, 2 - e - n, 2 - e); fillWithZeros(0, 2 - e - n); digits[1] = '.'; } private FormattedFPDecimal scientific(int prec) { /* * Rounding d = f 10^e to prec digits in scientific mode means the same * as rounding it to the p = prec + 1 most significand digits of d. */ round(prec + 1L); return scientificChars(prec); } private FormattedFPDecimal scientificChars(int prec) { if (prec != 0) { scientificCharsWithFraction(); } else { scientificCharsNoFraction(); } expChars(); return this; } private void scientificCharsWithFraction() { digits = new char[1 + n]; // room for leading digit and for '.' long x = fillWithDigits(f, 2, 1 + n); digits[1] = '.'; digits[0] = toDigit(x); } private void scientificCharsNoFraction() { digits = new char[1]; digits[0] = toDigit(f); } private FormattedFPDecimal general(int prec) { /* * Rounding d = f 10^e to prec digits in general mode means the same * as rounding it to the p = prec most significand digits of d, and then * deciding whether to format it in plain or scientific mode, depending * on the rounded value. */ round(prec); int er = getExponentRounded(); if (-4 <= er && er < prec) { plainChars(); } else { scientificChars(prec - 1); } return this; } private void expChars() { int er = getExponentRounded(); int aer = Math.abs(er); exp = new char[aer >= 100 ? 4 : 3]; int q; if (aer >= 100) { q = aer / 10; exp[3] = toDigit(aer - 10 * q); aer = q; } q = aer / 10; exp[2] = toDigit(aer - 10 * q); exp[1] = toDigit(q); exp[0] = er >= 0 ? '+' : '-'; } private void round(long pp) { /* * Let d = f 10^e, and let p shorten pp. * This method rounds d to the p most significant digits. * It does so by possibly modifying f, e and n. * When f becomes 0, e and n are normalized to 0 and 1, resp. * * For any real x let * r(x) = floor(x + 1/2) * which is rounding to the closest integer, with ties rounded toward * positive infinity. * * When f = 0 there's not much to say, except that this holds iff n = 0. * * Otherwise, since * 10^{n-1} <= f < 10^n * it follows that * 10^{e+n-1} <= d < 10^{e+n} * To round d to the most significant p digits, first scale d to the * range [10^{p-1}, 10^p), cutoff the fractional digits by applying r, * and finally scale back. * To this end, first define * ds = d 10^{p-e-n} * which ensures * 10^{p-1} <= ds < 10^p * * Now, if p < 0 (that is, if p <= -1) then * ds < 10^p <= 10^{-1} < 1/2 * so that * r(ds) = 0 * Thus, rounding d to p < 0 digits leads to 0. */ if (n == 0 || pp < 0) { f = 0; e = 0; n = 1; return; } /* * Further, if p >= n then * ds = f 10^e 10^{p-e-n} = f 10^{p-n} * which shows that ds is an integer, so r(ds) = ds. That is, * rounding to p >= n digits leads to a result equal to d. */ if (pp >= n) { // no rounding needed return; } /* * Finally, 0 <= p < n. When p = 0 it follows that * 10^{-1} <= ds < 1 * 0 <= f' = r(ds) <= 1 * that is, f' is either 0 or 1. * * Otherwise * 10^{p-1} <= ds < 10^p * 1 <= 10^{p-1} <= f' = r(ds) <= 10^p * Note that f' = 10^p is a possible outcome. * * Scale back, where e' = e + n - p * d' = f' 10^{e+n-p} = f' 10^e', with 10^{e+n-1} <= d' <= 10^{e+n} * * Since n > p, f' can be computed in integer arithmetic as follows, * where / denotes division in the real numbers: * f' = r(ds) = r(f 10^{p-n}) = r(f / 10^{n-p}) * = floor(f / 10^{n-p} + 1/2) * = floor((f + 10^{n-p}/2) / 10^{n-p}) */ int p = (int) pp; // 0 <= pp < n, safe cast e += n - p; // new e is well inside the int range long pow10 = MathUtils.pow10(n - p); f = (f + (pow10 >> 1)) / pow10; if (p == 0) { n = 1; if (f == 0) { e = 0; } return; } n = p; if (f == MathUtils.pow10(p)) { /* * f is n + 1 digits long. * Absorb one trailing zero into e and reduce f accordingly. */ f /= 10; e += 1; } } /* * Fills the digits section with indices in [from, to) with the lower * to - from digits of x (as chars), while stripping them away from x. * Returns the stripped x. */ private long fillWithDigits(long x, int from, int to) { while (to > from) { long q = x / 10; digits[--to] = toDigit(x - q * 10); x = q; } return x; } /* * Fills the digits section with indices in [from, to) with '0'. */ private void fillWithZeros(int from, int to) { while (to > from) { digits[--to] = '0'; } } private static char toDigit(long d) { return toDigit((int) d); } private static char toDigit(int d) { return (char) (d + '0'); } }