You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@xmlgraphics.apache.org by ga...@apache.org on 2012/04/29 18:16:54 UTC
svn commit: r1331933 - in /xmlgraphics/commons/trunk:
src/java/org/apache/xmlgraphics/util/DoubleFormatUtil.java status.xml
test/java/org/apache/xmlgraphics/util/DoubleFormatUtilTest.java
Author: gadams
Date: Sun Apr 29 16:16:53 2012
New Revision: 1331933
URL: http://svn.apache.org/viewvc?rev=1331933&view=rev
Log:
Bugzilla #43940: Add double format utility to improve thread safety and performance. Submitted by Julien Aymé.
Added:
xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/util/DoubleFormatUtil.java
xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/util/DoubleFormatUtilTest.java
Modified:
xmlgraphics/commons/trunk/status.xml
Added: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/util/DoubleFormatUtil.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/util/DoubleFormatUtil.java?rev=1331933&view=auto
==============================================================================
--- xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/util/DoubleFormatUtil.java (added)
+++ xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/util/DoubleFormatUtil.java Sun Apr 29 16:16:53 2012
@@ -0,0 +1,344 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/* $Id$ */
+
+package org.apache.xmlgraphics.util;
+
+/**
+ * This class implements fast, thread-safe format of a double value
+ * with a given number of decimal digits.
+ * <p>
+ * The contract for the format methods is this one:
+ * if the source is greater than or equal to 1 (in absolute value),
+ * use the decimals parameter to define the number of decimal digits; else,
+ * use the precision parameter to define the number of decimal digits.
+ * <p>
+ * A few examples (consider decimals being 4 and precision being 8):
+ * <ul>
+ * <li>0.0 should be rendered as "0"
+ * <li>0.1 should be rendered as "0.1"
+ * <li>1234.1 should be rendered as "1234.1"
+ * <li>1234.1234567 should be rendered as "1234.1235" (note the trailing 5! Rounding!)
+ * <li>1234.00001 should be rendered as "1234"
+ * <li>0.00001 should be rendered as "0.00001" (here you see the effect of the "precision" parameter)
+ * <li>0.00000001 should be rendered as "0.00000001"
+ * <li>0.000000001 should be rendered as "0"
+ * </ul>
+ *
+ * Originally authored by Julien Aymé.
+ */
+public class DoubleFormatUtil {
+
+ /**
+ * Rounds the given source value at the given precision
+ * and writes the rounded value into the given target
+ *
+ * @param source the source value to round
+ * @param decimals the decimals to round at (use if abs(source) ≥ 1.0)
+ * @param precision the precision to round at (use if abs(source) < 1.0)
+ * @param target the buffer to write to
+ */
+ public static void formatDouble(double source, int decimals, int precision, StringBuffer target) {
+ int scale = (Math.abs(source) >= 1.0) ? decimals : precision;
+ if (tooManyDigitsUsed(source, scale) || tooCloseToRound(source, scale)) {
+ formatDoublePrecise(source, decimals, precision, target);
+ } else {
+ formatDoubleFast(source, decimals, precision, target);
+ }
+ }
+
+ /**
+ * Rounds the given source value at the given precision
+ * and writes the rounded value into the given target
+ * <p>
+ * This method internally uses the String representation of the source value,
+ * in order to avoid any double precision computation error.
+ *
+ * @param source the source value to round
+ * @param decimals the decimals to round at (use if abs(source) ≥ 1.0)
+ * @param precision the precision to round at (use if abs(source) < 1.0)
+ * @param target the buffer to write to
+ */
+ public static void formatDoublePrecise(double source, int decimals, int precision, StringBuffer target) {
+ if (isRoundedToZero(source, decimals, precision)) {
+ // Will always be rounded to 0
+ target.append('0');
+ return;
+ }
+
+ boolean negative = source < 0.0;
+ if (negative) {
+ source = -source;
+ // Done once and for all
+ target.append('-');
+ }
+ int scale = (source >= 1.0) ? decimals : precision;
+
+ // The only way to format precisely the double is to use the String
+ // representation of the double, and then to do mathematical integer operation on it.
+ String s = Double.toString(source);
+ if (source >= 10e-3 && source < 1e7) {
+ // Plain representation of double: "intPart.decimalPart"
+ int dot = s.indexOf('.');
+ String decS = s.substring(dot + 1);
+ int decLength = decS.length();
+ if (scale >= decLength) {
+ if ("0".equals(decS)) {
+ // source is a mathematical integer
+ target.append(s.substring(0, dot));
+ } else {
+ target.append(s);
+ }
+ return;
+ } else if (scale + 1 < decLength) {
+ // ignore unnecessary digits
+ decLength = scale + 1;
+ decS = decS.substring(0, decLength);
+ }
+ long intP = Long.parseLong(s.substring(0, dot));
+ long decP = Long.parseLong(decS);
+ format(target, scale, intP, decP);
+ } else {
+ // Scientific representation of double: "x.xxxxxEyyy"
+ int dot = s.indexOf('.');
+ int exp = s.indexOf('E');
+ int exposant = Integer.parseInt(s.substring(exp + 1));
+ String intS = s.substring(0, dot);
+ String decS = s.substring(dot + 1, exp);
+ int decLength = decS.length();
+ if (exposant >= 0) {
+ int digits = decLength - exposant;
+ if (digits <= 0) {
+ // no decimal part,
+ // no rounding involved
+ target.append(intS);
+ target.append(decS);
+ for (int i = -digits; i > 0; i--) {
+ target.append('0');
+ }
+ } else if (digits <= scale) {
+ // decimal part precision is lower than scale,
+ // no rounding involved
+ target.append(intS);
+ target.append(decS.substring(0, exposant));
+ target.append('.');
+ target.append(decS.substring(exposant));
+ } else {
+ // decimalDigits > scale,
+ // Rounding involved
+ long intP = Long.parseLong(intS) * tenPow(exposant) + Long.parseLong(decS.substring(0, exposant));
+ long decP = Long.parseLong(decS.substring(exposant, exposant + scale + 1));
+ format(target, scale, intP, decP);
+ }
+ } else {
+ // Only a decimal part is supplied
+ exposant = -exposant;
+ int digits = scale - exposant + 1;
+ if (digits < 0) {
+ target.append('0');
+ } else if (digits == 0) {
+ long decP = Long.parseLong(intS);
+ format(target, scale, 0L, decP);
+ } else {
+ long decP = Long.parseLong(intS) * tenPow(digits) + Long.parseLong(decS.substring(0, Math.min(decLength, digits)));
+ format(target, scale, 0L, decP);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the given source value will be rounded to zero
+ *
+ * @param source the source value to round
+ * @param decimals the decimals to round at (use if abs(source) ≥ 1.0)
+ * @param precision the precision to round at (use if abs(source) < 1.0)
+ * @return true if the source value will be rounded to zero
+ */
+ private static boolean isRoundedToZero(double source, int decimals, int precision) {
+ return Math.abs(source) < 5.0 / tenPow(Math.max(decimals, precision) + 1);
+ }
+
+ /**
+ * Most used power of ten (to avoid the cost of Math.pow(10, n)
+ */
+ private static final long[] tenPows = new long[20];
+ static {
+ tenPows[0] = 1L;
+ for (int i = 1; i < tenPows.length; i++) {
+ tenPows[i] = tenPows[i - 1] * 10L;
+ }
+ }
+
+ /**
+ * Returns ten to the power of n
+ *
+ * @param n the nth power of ten to get
+ * @return ten to the power of n
+ */
+ public static long tenPow(int n) {
+ assert n >= 0;
+ return n < tenPows.length ? tenPows[n] : (long) Math.pow(10, n);
+ }
+
+ /**
+ * Helper method to do the custom rounding used within formatDoublePrecise
+ *
+ * @param target the buffer to write to
+ * @param scale the expected rounding scale
+ * @param intP the source integer part
+ * @param decP the source decimal part, truncated to scale + 1 digit
+ */
+ private static void format(StringBuffer target, int scale, long intP, long decP) {
+ long scaleTen = tenPow(scale);
+ if (decP != 0L) {
+ // decP is the decimal part of source, truncated to scale + 1 digit.
+ // Custom rounding: add 5
+ decP += 5L;
+ decP /= 10L;
+ if (decP >= scaleTen) {
+ intP++;
+ decP -= scaleTen;
+ }
+ if (decP != 0L) {
+ // Remove trailing zeroes
+ while (decP % 10L == 0L) {
+ decP = decP / 10L;
+ scale--;
+ }
+ }
+ }
+ target.append(intP);
+ if (decP != 0L) {
+ target.append('.');
+ while (scale > 0 && decP < tenPow(--scale)) {
+ // Insert leading zeroes
+ target.append('0');
+ }
+ target.append(decP);
+ }
+ }
+
+ /**
+ * Rounds the given source value at the given precision
+ * and writes the rounded value into the given target
+ * <p>
+ * This method internally uses double precision computation and rounding,
+ * so the result may not be accurate (see formatDouble method for conditions).
+ *
+ * @param source the source value to round
+ * @param decimals the decimals to round at (use if abs(source) ≥ 1.0)
+ * @param precision the precision to round at (use if abs(source) < 1.0)
+ * @param target the buffer to write to
+ */
+ public static void formatDoubleFast(double source, int decimals, int precision, StringBuffer target) {
+ if (isRoundedToZero(source, decimals, precision)) {
+ // Will always be rounded to 0
+ target.append('0');
+ return;
+ }
+
+ boolean isPositive = source >= 0.0;
+ source = Math.abs(source);
+ int scale = (source >= 1.0) ? decimals : precision;
+
+ long intPart = (long) Math.floor(source);
+ long tenScale = tenPow(scale);
+ double fracUnroundedPart = (source - intPart) * tenScale;
+ long fracPart = Math.round(fracUnroundedPart);
+ if (fracPart >= tenScale) {
+ intPart++;
+ fracPart -= tenScale;
+ }
+ if (fracPart != 0L) {
+ // Remove trailing zeroes
+ while (fracPart % 10L == 0L) {
+ fracPart = fracPart / 10L;
+ scale--;
+ }
+ }
+
+ if (intPart != 0L || fracPart != 0L) {
+ // non-zero value
+ if (!isPositive) {
+ // negative value, insert sign
+ target.append('-');
+ }
+ // append integer part
+ target.append(intPart);
+ if (fracPart != 0L) {
+ // append fractional part
+ target.append('.');
+ // insert leading zeroes
+ while (scale > 0 && fracPart < tenPow(--scale)) {
+ target.append('0');
+ }
+ target.append(fracPart);
+ }
+ } else {
+ target.append('0');
+ }
+ }
+
+ /**
+ * Returns the exponent of the given value
+ *
+ * @param value the value to get the exponent from
+ * @return the value's exponent
+ */
+ public static int getExponant(double value) {
+ // See Double.doubleToRawLongBits javadoc or IEEE-754 spec
+ // to have this algorithm
+ long exp = Double.doubleToRawLongBits(value) & 0x7ff0000000000000L;
+ exp = exp >> 52;
+ return (int) (exp - 1023L);
+ }
+
+ /**
+ * Returns true if the rounding is considered to use too many digits
+ * of the double for a fast rounding
+ *
+ * @param source the source to round
+ * @param scale the scale to round at
+ * @return true if the rounding will potentially use too many digits
+ */
+ private static boolean tooManyDigitsUsed(double source, int scale) {
+ return getExponant(source) + scale >= 14;
+ }
+
+ /**
+ * Returns true if the given source is considered to be too close
+ * of a rounding value for the given scale.
+ *
+ * @param source the source to round
+ * @param scale the scale to round at
+ * @return true if the source will be potentially rounded at the scale
+ */
+ private static boolean tooCloseToRound(double source, int scale) {
+ source = Math.abs(source);
+ long intPart = (long) Math.floor(source);
+ double fracPart = (source - intPart) * tenPow(scale);
+ double distanceToRound = Math.abs(fracPart - Math.floor(fracPart) - 0.5);
+ // For huge scale, we have to reduce the range.
+ double range = scale > 12 ? .1 : .001;
+ return distanceToRound <= range;
+ // .001 range: Totally arbitrary range,
+ // I never had a failure in 10e7 random tests with this value
+ // May be JVM dependent or architecture dependent
+ }
+}
Modified: xmlgraphics/commons/trunk/status.xml
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/status.xml?rev=1331933&r1=1331932&r2=1331933&view=diff
==============================================================================
--- xmlgraphics/commons/trunk/status.xml (original)
+++ xmlgraphics/commons/trunk/status.xml Sun Apr 29 16:16:53 2012
@@ -41,6 +41,9 @@
</contexts>
<changes>
<release version="Trunk" date="n/a">
+ <action context="Code" dev="GA" type="add" fixes-bug="43940" due-to="Julien Aymé">
+ Add double format utility to improve thread safety and performance.
+ </action>
<action context="Code" dev="GA" type="fix" fixes-bug="50497" due-to="Brian Carlson">
Support rdf:resource attributes in XMP.
</action>
Added: xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/util/DoubleFormatUtilTest.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/util/DoubleFormatUtilTest.java?rev=1331933&view=auto
==============================================================================
--- xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/util/DoubleFormatUtilTest.java (added)
+++ xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/util/DoubleFormatUtilTest.java Sun Apr 29 16:16:53 2012
@@ -0,0 +1,422 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/* $Id$ */
+
+package org.apache.xmlgraphics.util;
+
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+/**
+ * Test class of DoubleFormatUtil
+ */
+public class DoubleFormatUtilTest extends TestCase {
+
+ /**
+ * Test simple values as specified in the format contract.
+ * <p>
+ * Note: Some of these tests will fail if formatFast is used.
+ */
+ public void testSimple() {
+ int decimals = 4;
+ int precision = 8;
+
+ double value = 0.0;
+ String expected = "0";
+ String actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ value = 0.1;
+ expected = "0.1";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ value = 1234.1;
+ expected = "1234.1";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ // rounding
+ value = 1234.1234567;
+ expected = "1234.1235";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ value = 1234.99995;
+ expected = "1235";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ value = -1234.99995;
+ expected = "-1235";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ value = 1234.99994999;
+ expected = "1234.9999";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ // decimals / precision switch
+ value = 0.00000001;
+ expected = "0.00000001";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ value = -0.00000001;
+ expected = "-0.00000001";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ value = 72.00001234;
+ expected = "72";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ // limit precision
+ value = 0.000000001;
+ expected = "0";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ value = 5.0e-9;
+ expected = "0.00000001";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ value = 4.9999999999e-9;
+ expected = "0";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ value = 2.0005e-5;
+ expected = "0.00002001";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ value = 2.00049999999999e-5;
+ expected = "0.00002";
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+ }
+
+ /**
+ * AssertEquals with a more detailed message
+ */
+ private static void assertEquals(double value, int decimals, int precision, String expected, String actual) {
+ assertEquals("value: " + value + ", decimals: " + decimals + ", precision: " + precision, expected, actual);
+ }
+
+ /**
+ * The buffer used to format
+ */
+ private StringBuffer buf = new StringBuffer();
+
+ /**
+ * Formats using FormatUtil#formatDouble method
+ */
+ private String format(double value, int decimals, int precision) {
+ buf.setLength(0);
+ DoubleFormatUtil.formatDouble(value, decimals, precision, buf);
+ return buf.toString();
+ }
+
+ /**
+ * Formats using FormatUtil#formatDoublePrecise method
+ */
+ private String formatPrecise(double value, int decimals, int precision) {
+ buf.setLength(0);
+ DoubleFormatUtil.formatDoublePrecise(value, decimals, precision, buf);
+ return buf.toString();
+ }
+
+ /**
+ * Formats using FormatUtil#formatDoubleFast method
+ */
+ private String formatFast(double value, int decimals, int precision) {
+ buf.setLength(0);
+ DoubleFormatUtil.formatDoubleFast(value, decimals, precision, buf);
+ return buf.toString();
+ }
+
+ /**
+ * Formats using a BigDecimal. This is the reference (always returns the correct format)
+ * whereas DecimalFormat may have some formating errors regarding the last digit.
+ */
+ private String refFormat(double value, int decimals, int precision) {
+ buf.setLength(0);
+ BigDecimal bg = new BigDecimal(Double.toString(value));
+ int scale = Math.abs(value) < 1.0 ? precision : decimals;
+ bg = bg.setScale(scale, BigDecimal.ROUND_HALF_UP);
+ //buf.append(bg.toString()); // Java 1.4
+ buf.append(bg.toPlainString()); // Java 1.5 and more !
+ if (buf.indexOf(".") >= 0) {
+ for (int i = buf.length() - 1; i > 1 && buf.charAt(i) == '0'; i--) {
+ buf.setLength(i);
+ }
+ if (buf.charAt(buf.length() - 1) == '.') {
+ buf.setLength(buf.length() - 1);
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * The decimal format used within formatDf method
+ */
+ private DecimalFormat df = new DecimalFormat("0", new DecimalFormatSymbols(Locale.US));
+
+ /**
+ * Formats using DecimalFormat#format method
+ */
+ private String formatDf(double value, int decimals, int precision) {
+ int scale = Math.abs(value) < 1.0 ? precision : decimals;
+ df.setMaximumFractionDigits(scale);
+ return df.format(value);
+ }
+
+ /**
+ * The maximum power of ten to use when testing high values double
+ */
+ private static final int maxPow = 12;
+
+ /**
+ * Tests the formatPrecise method against the reference, with random values
+ */
+ public void testPrecise() {
+ long seed = System.currentTimeMillis();
+ Random r = new Random();
+ r.setSeed(seed);
+
+ double value, highValue, lowValue;
+ int nbTest = 10000;
+ int maxDecimals = 12;
+
+ String actual, expected;
+ for (int i = nbTest; i > 0; i--) {
+ int decimals = r.nextInt(maxDecimals);
+ int precision = decimals + 3;
+ value = 1 + r.nextDouble(); // Use decimals and not precision
+ expected = refFormat(value, decimals, precision);
+ actual = formatPrecise(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ highValue = value * DoubleFormatUtil.tenPow(r.nextInt(maxPow));
+ expected = refFormat(highValue, decimals, precision);
+ actual = formatPrecise(highValue, decimals, precision);
+ assertEquals(highValue, decimals, precision, expected, actual);
+
+ lowValue = (value - 1) / 1000;
+ expected = refFormat(lowValue, decimals, precision);
+ actual = formatPrecise(lowValue, decimals, precision);
+ assertEquals(lowValue, decimals, precision, expected, actual);
+ }
+ }
+
+ /**
+ * Tests the format method against the reference, with random values
+ */
+ public void testFormat() {
+ long seed = System.currentTimeMillis();
+ Random r = new Random();
+ r.setSeed(seed);
+
+ double value, highValue, lowValue;
+ int nbTest = 10000;
+ int maxDecimals = 12;
+
+ String actual, expected;
+ for (int i = nbTest; i > 0; i--) {
+ int decimals = r.nextInt(maxDecimals);
+ int precision = decimals + 3;
+ value = 1 + r.nextDouble(); // Use decimals and not precision
+ expected = refFormat(value, decimals, precision);
+ actual = format(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ highValue = value * DoubleFormatUtil.tenPow(r.nextInt(maxPow));
+ expected = refFormat(highValue, decimals, precision);
+ actual = format(highValue, decimals, precision);
+ assertEquals(highValue, decimals, precision, expected, actual);
+
+ lowValue = (value - 1) / 1000;
+ expected = refFormat(lowValue, decimals, precision);
+ actual = format(lowValue, decimals, precision);
+ assertEquals(lowValue, decimals, precision, expected, actual);
+ }
+ }
+
+ /**
+ * Tests the formatFast method against the reference, with random values.
+ * Disabled since the formatFast method is not accurate.
+ */
+ public void fast() {
+ long seed = System.currentTimeMillis();
+ Random r = new Random();
+ r.setSeed(seed);
+
+ double value, highValue, lowValue;
+ int nbTest = 10000;
+ int maxDecimals = 12;
+
+ String actual, expected;
+ for (int i = nbTest; i > 0; i--) {
+ int decimals = r.nextInt(maxDecimals);
+ int precision = decimals + 3;
+ value = 1 + r.nextDouble(); // Use decimals and not precision
+ expected = refFormat(value, decimals, precision);
+ actual = formatFast(value, decimals, precision);
+ assertEquals(value, decimals, precision, expected, actual);
+
+ highValue = value * DoubleFormatUtil.tenPow(r.nextInt(maxPow));
+ expected = refFormat(highValue, decimals, precision);
+ actual = formatFast(highValue, decimals, precision);
+ assertEquals(highValue, decimals, precision, expected, actual);
+
+ lowValue = (value - 1) / 1000;
+ expected = refFormat(lowValue, decimals, precision);
+ actual = formatFast(lowValue, decimals, precision);
+ assertEquals(lowValue, decimals, precision, expected, actual);
+ }
+ }
+
+ /**
+ * Performance comparison of the differents formatXXX methods,
+ * to see which one is the fastest in the same conditions.
+ */
+ public void performanceCompare() {
+ // Rename this method in testPerformanceCompare to run it within JUnit tests
+ // This method is quite long (depends of the value of nbTest).
+ long seed = System.currentTimeMillis();
+ Random r = new Random();
+ r.setSeed(seed);
+
+ double value, highValue, lowValue;
+ long start = System.currentTimeMillis();
+ int nbTest = 100000;
+ int maxDecimals = 10;
+
+ r.setSeed(seed);
+ start = System.currentTimeMillis();
+ for (int i = nbTest; i > 0; i--) {
+ int decimals = r.nextInt(maxDecimals);
+ int precision = decimals + 3;
+ value = 1 + r.nextDouble(); // Use decimals and not precision
+ format(value, decimals, precision);
+
+ highValue = value * DoubleFormatUtil.tenPow(r.nextInt(maxPow));
+ format(highValue, decimals, precision);
+
+ lowValue = (value - 1) / 1000;
+ format(lowValue, decimals, precision);
+ }
+ long formatDuration = System.currentTimeMillis() - start;
+ System.out.println("Format duration: " + formatDuration + "ms to format " + (3 * nbTest) + " doubles");
+
+ r.setSeed(seed);
+ start = System.currentTimeMillis();
+ for (int i = nbTest; i > 0; i--) {
+ int decimals = r.nextInt(maxDecimals);
+ int precision = decimals + 3;
+ value = 1 + r.nextDouble(); // Use decimals and not precision
+ formatPrecise(value, decimals, precision);
+
+ highValue = value * DoubleFormatUtil.tenPow(r.nextInt(maxPow));
+ formatPrecise(highValue, decimals, precision);
+
+ lowValue = (value - 1) / 1000;
+ formatPrecise(lowValue, decimals, precision);
+ }
+ long preciseFormatDuration = System.currentTimeMillis() - start;
+ System.out.println("Format Precise duration: " + preciseFormatDuration + "ms to format " + (3 * nbTest) + " doubles");
+
+ r.setSeed(seed);
+ start = System.currentTimeMillis();
+ for (int i = nbTest; i > 0; i--) {
+ int decimals = r.nextInt(maxDecimals);
+ int precision = decimals + 3;
+ value = 1 + r.nextDouble(); // Use decimals and not precision
+ formatFast(value, decimals, precision);
+
+ highValue = value * DoubleFormatUtil.tenPow(r.nextInt(maxPow));
+ formatFast(highValue, decimals, precision);
+
+ lowValue = (value - 1) / 1000;
+ formatFast(lowValue, decimals, precision);
+ }
+ long fastFormatDuration = System.currentTimeMillis() - start;
+ System.out.println("Fast Format duration: " + fastFormatDuration + "ms to format " + (3 * nbTest) + " doubles");
+
+ r.setSeed(seed);
+ start = System.currentTimeMillis();
+ for (int i = nbTest; i > 0; i--) {
+ int decimals = r.nextInt(maxDecimals);
+ int precision = decimals + 3;
+ value = 1 + r.nextDouble(); // Use decimals and not precision
+ refFormat(value, decimals, precision);
+
+ highValue = value * DoubleFormatUtil.tenPow(r.nextInt(maxPow));
+ refFormat(highValue, decimals, precision);
+
+ lowValue = (value - 1) / 1000;
+ refFormat(lowValue, decimals, precision);
+ }
+ long bgDuration = System.currentTimeMillis() - start;
+ System.out.println("BigDecimal format duration: " + bgDuration + "ms to format " + (3 * nbTest) + " doubles");
+
+ r.setSeed(seed);
+ start = System.currentTimeMillis();
+ for (int i = nbTest; i > 0; i--) {
+ int decimals = r.nextInt(maxDecimals);
+ int precision = decimals + 3;
+ value = 1 + r.nextDouble(); // Use decimals and not precision
+ formatDf(value, decimals, precision);
+
+ highValue = value * DoubleFormatUtil.tenPow(r.nextInt(maxPow));
+ formatDf(highValue, decimals, precision);
+
+ lowValue = (value - 1) / 1000;
+ formatDf(lowValue, decimals, precision);
+ }
+ long dfDuration = System.currentTimeMillis() - start;
+ System.out.println("DecimalFormat duration: " + dfDuration + "ms to format " + (3 * nbTest) + " doubles");
+
+ r.setSeed(seed);
+ start = System.currentTimeMillis();
+ for (int i = nbTest; i > 0; i--) {
+ int decimals = r.nextInt(maxDecimals);
+ int precision = decimals + 3;
+ precision++; // Avoid warning unused local variable
+ value = 1 + r.nextDouble(); // Use decimals and not precision
+ Double.toString(value);
+
+ highValue = value * DoubleFormatUtil.tenPow(r.nextInt(maxPow));
+ Double.toString(highValue);
+
+ lowValue = (value - 1) / 1000;
+ Double.toString(lowValue);
+ }
+ long toStringDuration = System.currentTimeMillis() - start;
+ System.out.println("toString duration: " + toStringDuration + "ms to format " + (3 * nbTest) + " doubles");
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: commits-help@xmlgraphics.apache.org