Special cases:

*
- *
• If {@code accuracy} is 0, {@link Double#NaN NaN} or infinity, - * then this method returns 0.
• + *
• If {@code accuracy} is {@link Double#NaN NaN} or infinity, then this method returns 0 + * since those values are not represented by decimal digits.
• + * + *
• If {@code accuracy} is 0, then this method returns 324 since 10-324 is the + * first power of 10 smaller than {@link Double#MIN_VALUE}. + * + * {@note The above value can be understood in an other way: if the first 324 fraction digits are zero, + * then the IEEE `double` value is guaranteed to be rounded to exactly 0 no matter + * what the next fraction digits are.}
• + * *
• If {@code accuracy} is greater than 1, then this method returns * the number of "unnecessary" trailing zeros as a negative number. - * For example {@code fractionDigitsForDelta(100, …)} returns -2.
• + * + * {@example `fractionDigitsForDelta(100, …)` returns -2.} + * *
• If the first non-zero digits of {@code accuracy} are equal or greater than 95 * (e.g. 0.00099) and the {@code strict} argument is {@code true}, then this method * increases the number of needed fraction digits in order to prevent the rounded * number to be collapsed into the next integer value. * * {@example - * If {@code accuracy} is 0.95, then a return value of 1 is not sufficient since the - * rounded value of 0.95 with 1 fraction digit would be 1.0. Such value would be a + * If `accuracy` is 0.95, then a return value of 1 is not sufficient since + * the rounded value of 0.95 with 1 fraction digit would be 1.0. Such value would be a * violation of this method contract since the difference between 0 and that formatted * value would be greater than the accuracy. Note that this is not an artificial rule; * this is related to the fact that 0.9999… is mathematically strictly equals to 1.}
• @@ -274,20 +305,124 @@ public final class MathFunctions extends } else { // 'x' is out of range or NaN. final double y = Math.log10(accuracy); if (Double.isInfinite(y)) { - return 0; + return (accuracy == 0) ? -E10_FOR_ZERO : 0; } i = -((int) Math.floor(y)); scale = pow10(i); } - while ((accuracy *= scale) >= 9.5) { - i++; // The 0.…95 special case. - accuracy -= Math.floor(accuracy); - scale = 10; + if (strict) { + while ((accuracy *= scale) >= 9.5) { + i++; // The 0.…95 special case. + accuracy -= Math.floor(accuracy); + scale = 10; + } } return i; } /** + * Returns the number of significant fraction digits when formatting the given number in base 10. + * This method does not ignore trailing zeros. + * For example {@code fractionDigitsForValue(1.0)} returns 16, + * because the {@code double} format can store almost 16 decimal digits after 1. + * + * {@note We said almost because the very last digit may be able to store only a subset of the + * [0 … 9] digits.} + * + * Invoking this method is equivalent to invoking ```{@linkplain #fractionDigitsForDelta(double, boolean) + * fractionDigitsForDelta}(Math.{@linkplain Math#ulp(double) ulp}(value), false)```, except that it is + * potentially faster. + * + *

Special cases:

+ *
+ *
• If {@code value} is {@link Double#NaN NaN} or infinity, then this method returns 0 + * since those values are not represented by decimal digits.
• + * + *
• If {@code value} is 0, then this method returns 324 since + * {@code Math.ulp(0)} = {@value java.lang.Double#MIN_VALUE}.
• + *
+ * + * {@example This method is useful with `NumberFormat` for formatting all significant digits + * of a `double` value, padding with trailing zeros if necessary, but no more than + * necessary.} + * + * @param value The value for which to get the number of significant digits. + * @return The number of significant digits (may be negative), or 0 if {@code value} is NaN or infinity. + * + * @see java.text.NumberFormat#setMinimumFractionDigits(int) + * + * @since 0.4 + */ + public static int fractionDigitsForValue(final double value) { + /* + * We really need Math.getExponent(value) here rather than MathFunctions.getExponent(value). + * What we actually want is MathFunctions.getExponent(Math.ulp(value)), but we get the same + * result more efficiently if we replace the call to Math.ulp(double) by a SIGNIFICAND_SIZE + * subtraction in the exponent, provided that the exponent has NOT been corrected for sub- + * normal numbers (in order to reproduce the Math.ulp behavior). + */ + final int exponent = Math.getExponent(value); + if (exponent <= Double.MAX_EXPONENT) { // Exclude NaN and ±∞ cases. + return (int) -Math.floor(LOG10_2 * (exponent - SIGNIFICAND_SIZE)); + } + return 0; + } + + /** + * Returns the unbiased exponent used in the representation of a {@code double}, with correction for + * sub-normal numbers. This method is related to {@link Math#getExponent(double)} in the following ways: + * + *
+ *
• For NaN and all values equal or greater than {@link Double#MIN_NORMAL} in magnitude (including + * infinities), this method returns results that are identical to {@code Math.getExponent(double)}.
• + *
• For values smaller than {@link Double#MIN_NORMAL} in magnitude (including zero), the correction + * for sub-normal numbers results in return values smaller than what {@code Math.getExponent(double)} + * would return.
• + *
+ * + * Special cases: + *
+ *
• If the argument is NaN or infinite, then the result is {@link Double#MAX_EXPONENT} + 1.
• + *
• If the argument is {@link Double#MAX_VALUE}, then the result is {@value java.lang.Double#MAX_EXPONENT}.
• + *
• If the argument is {@link Double#MIN_NORMAL}, then the result is {@value java.lang.Double#MIN_EXPONENT}.
• + *
• If the argument is {@link Double#MIN_VALUE}, then the result is -1074.
• + *
• If the argument is zero, then the result is -1075.
• + *
+ * + * {@section Identities} + * For any p values in the [-1075 … 1024] range and value = 2p: + *
+ *
• `getExponent(Math.scalb(1.0, p)) == p`
• + *
• `Math.scalb(1.0, getExponent(value)) == value`
• + *
• `Math.floor({@linkplain #LOG10_2} * getExponent(value)) == Math.floor(Math.log10(value))`
• + *
+ * + * @param value The value for which to get the exponent. + * @return The unbiased exponent, corrected for sub-normal numbers if needed. + * Values will be in the [-1075 … 1024] range, inclusive. + * + * @see Math#getExponent(double) + * @see Math#scalb(double, int) + * + * @since 0.4 + */ + public static int getExponent(final double value) { + final long bits = doubleToRawLongBits(value); + int exponent = (int) ((bits >>> SIGNIFICAND_SIZE) & 0x7FFL); + if (exponent == 0) { + /* + * Number is sub-normal: there is no implicit 1 bit before the significand. + * We need to search for the position of the first real 1 bit, and fix the + * exponent accordingly. Note that numberOfLeadingZeros(…) is relative to + * 64 bits while the significand size is only 52 bits. The last term below + * is for fixing this difference. + */ + exponent -= Long.numberOfLeadingZeros(bits & ((1L << SIGNIFICAND_SIZE) - 1)) - (Long.SIZE - SIGNIFICAND_SIZE); + } + return exponent - Double.MAX_EXPONENT; + } + + /** * Computes 10 raised to the power of x. This method delegates to * `{@linkplain #pow10(int) pow10}((int) x)` if x is an * integer, or to `{@linkplain Math#pow(double, double) Math.pow}(10, x)` @@ -529,9 +664,9 @@ public final class MathFunctions extends * @return {@code +1} if x is positive, {@code -1} if negative, or 0 otherwise. */ public static short sgn(short x) { - if (x > 0) return (short) +1; - if (x < 0) return (short) -1; - else return (short) 0; + if (x > 0) return +1; + if (x < 0) return -1; + else return 0; } /** @@ -544,9 +679,9 @@ public final class MathFunctions extends * @return {@code +1} if x is positive, {@code -1} if negative, or 0 otherwise. */ public static byte sgn(byte x) { - if (x > 0) return (byte) +1; - if (x < 0) return (byte) -1; - else return (byte) 0; + if (x > 0) return +1; + if (x < 0) return -1; + else return 0; } /** Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java?rev=1534788&r1=1534787&r2=1534788&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java [UTF-8] Tue Oct 22 21:07:24 2013 @@ -39,7 +39,7 @@ import static java.lang.Double.isInfinit import static org.apache.sis.math.MathFunctions.pow10; import static org.apache.sis.math.MathFunctions.truncate; import static org.apache.sis.math.MathFunctions.isNegative; -import static org.apache.sis.math.MathFunctions.fractionDigitsForDelta; +import static org.apache.sis.math.MathFunctions.fractionDigitsForValue; // Related to JDK7 import java.util.Objects; @@ -956,7 +956,7 @@ public class AngleFormat extends Format if (maximumFractionDigits != minimumFractionDigits) { if (secondsFieldWidth != 0) angle *= 3600; else if (minutesFieldWidth != 0) angle *= 60; - final int n = fractionDigitsForDelta(Math.ulp(angle), false) - 1; + final int n = fractionDigitsForValue(angle) - 1; if (n < maximumFractionDigits) { maximumFractionDigits = Math.max(minimumFractionDigits, n); } @@ -1039,12 +1039,16 @@ public class AngleFormat extends Format if (suffix != null) { toAppendTo.append(suffix); } - it.addFieldLimit(Field.forCode(field), hasMore - ? (Number) Integer.valueOf((int) Math.round(value)) - : (Number) Float.valueOf((float) value), startPosition); - // The 'valueOf(…)' was for information purpose only. We use Float instead of Double - // because we don't want to give a false impression of accuracy (when formatting the - // seconds field, at least the 10 last bits of the double value are non-significant). + final Number userObject; + if (hasMore) { + userObject = (int) Math.round(value); + } else { + // Use Float instead of Double because we don't want to give a false impression of accuracy + // (when formatting the seconds field, at least the 10 last bits of the 'double' value are + // non-significant). + userObject = (float) value; + } + it.addFieldLimit(Field.forCode(field), userObject, startPosition); } else { toAppendTo = numberFormat.format(value, toAppendTo, dummyFieldPosition()); if (suffix != null) { @@ -1746,6 +1750,8 @@ BigBoss: switch (skipSuffix(source, p /** * Returns a clone of this {@code AngleFormat}. + * + * @return A clone of this format. */ @Override public AngleFormat clone() { Modified: sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java?rev=1534788&r1=1534787&r2=1534788&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java [UTF-8] Tue Oct 22 21:07:24 2013 @@ -252,6 +252,42 @@ public final strictfp class DoubleDouble } /** + * Tests {@link DoubleDouble#sqrt()} first with the square root of 2, then with random values. + * In the {@code sqrt(2)} case: + * + *
+ *
• The error using {@code double} arithmetic is approximatively 1E-16.
• + *
• The error using double-double arithmetic is expected to be slightly less that 1E-32.
• + *
+ */ + @Test + @DependsOnMethod({"testMultiply", "testDivide"}) + public void testSqrt() { + final BigDecimal SQRT2 = new BigDecimal("1.414213562373095048801688724209698"); + final DoubleDouble dd = new DoubleDouble(2, 0); + dd.sqrt(); + assertNormalizedAndEquals(sqrt(2), dd); + assertEquals(0, SQRT2.subtract(toBigDecimal(dd)).doubleValue(), 1E-32); + /* + * If we have been able to compute √2, now test with random values. + * Since the range of values is approximatively [-1000 … 1000], use + * a tolerance value 1000 time the one that we used for √2. + */ + for (int i=0; i - *
• The error using {@code double} arithmetic is approximatively 1E-16.
• - *
• The error using double-double arithmetic is expected to be slightly less that 1E-32.
• - *
+ * Tests the {@code DoubleDouble.createFoo()} methods. */ @Test - @DependsOnMethod({"testMultiply", "testDivide"}) - public void testSqrt() { - final BigDecimal SQRT2 = new BigDecimal("1.414213562373095048801688724209698"); - final DoubleDouble dd = new DoubleDouble(); - dd.value = 2; - dd.sqrt(); - assertNormalizedAndEquals(sqrt(2), dd); - assertEquals(0, SQRT2.subtract(toBigDecimal(dd)).doubleValue(), 1E-32); - /* - * If we have been able to compute √2, now test with random values. - * Since the range of values is approximatively [-1000 … 1000], use - * a tolerance value 1000 time the one that we used for √2. - */ - for (int i=0; i parseDouble("1E" + MAX_E10)); + assertEquals(POSITIVE_INFINITY, parseDouble("1E" + (MAX_E10 + 1)), 0); + } + + /** * Tests {@link MathFunctions#truncate(double)}. */ @Test @@ -60,18 +85,10 @@ public final strictfp class MathFunction assertEquals(-4.0, truncate(-4.9), 0); assertEquals(+0.0, truncate(+0.1), 0); assertEquals(-0.0, truncate(-0.1), 0); - assertEquals("Positive zero", - Double.doubleToLongBits(+0.0), - Double.doubleToLongBits(truncate(+0.5))); - assertEquals("Negative zero", - Double.doubleToLongBits(-0.0), - Double.doubleToLongBits(truncate(-0.5))); - assertEquals("Positive zero", - Double.doubleToLongBits(+0.0), - Double.doubleToLongBits(truncate(+0.0))); - assertEquals("Negative zero", - Double.doubleToLongBits(-0.0), - Double.doubleToLongBits(truncate(-0.0))); + assertEquals("Positive zero", doubleToLongBits(+0.0), doubleToLongBits(truncate(+0.5))); + assertEquals("Negative zero", doubleToLongBits(-0.0), doubleToLongBits(truncate(-0.5))); + assertEquals("Positive zero", doubleToLongBits(+0.0), doubleToLongBits(truncate(+0.0))); + assertEquals("Negative zero", doubleToLongBits(-0.0), doubleToLongBits(truncate(-0.0))); } /** @@ -126,11 +143,91 @@ public final strictfp class MathFunction assertEquals(-22, fractionDigitsForDelta(9.6E+23, true)); // Special case assertEquals(-300, fractionDigitsForDelta(1.1E+300, true)); - // Special cases. - assertEquals(0, fractionDigitsForDelta(0, true)); - assertEquals(0, fractionDigitsForDelta(Double.NaN, true)); - assertEquals(0, fractionDigitsForDelta(Double.POSITIVE_INFINITY, true)); - assertEquals(0, fractionDigitsForDelta(Double.NEGATIVE_INFINITY, true)); + // Other cases. + assertEquals(-E10_FOR_ZERO, fractionDigitsForDelta(0, false)); + assertEquals(-E10_FOR_ZERO, fractionDigitsForDelta(MIN_VALUE, false)); + assertEquals( 16, fractionDigitsForDelta(Math.ulp(1.0), false)); + assertEquals( 15, fractionDigitsForDelta(Math.ulp(45.0), false)); + assertEquals( 0, fractionDigitsForDelta(NaN, false)); + assertEquals( 0, fractionDigitsForDelta(POSITIVE_INFINITY, false)); + assertEquals( 0, fractionDigitsForDelta(NEGATIVE_INFINITY, false)); + } + + /** + * Tests {@link MathFunctions#fractionDigitsForValue(double)}. + */ + @Test + public void testFractionDigitsForValue() { + assertEquals(-E10_FOR_ZERO, fractionDigitsForValue(0)); + assertEquals(-E10_FOR_ZERO, fractionDigitsForValue(MIN_VALUE)); + assertEquals( 16, fractionDigitsForValue(1)); + assertEquals( 15, fractionDigitsForValue(45)); + assertEquals( 0, fractionDigitsForValue(NaN)); + assertEquals( 0, fractionDigitsForValue(POSITIVE_INFINITY)); + assertEquals( 0, fractionDigitsForValue(NEGATIVE_INFINITY)); + for (int i=E10_FOR_ZERO; i<=MAX_E10; i++) { + final double value = pow10(i); + final double accuracy = pow10(-fractionDigitsForValue(value)); + assertEquals("Shall not be greater than ULP", 0, accuracy, StrictMath.ulp(value)); + } + for (int i=MIN_EXPONENT; i<=MAX_EXPONENT; i++) { + final double value = StrictMath.scalb(1, i); + final double accuracy = pow10(-fractionDigitsForValue(value)); + assertEquals("Shall not be greater than ULP", 0, accuracy, StrictMath.ulp(value)); + } + } + + /** + * Tests the {@link MathFunctions#getExponent(double)} method. + * This method performs two tests: + * + *
+ *
• First, tests with a few normal (non-subnormal) numbers. + * The result shall be identical to {@link StrictMath#getExponent(double)}.
• + *
• Then, test with a few sub-normal numbers.
• + *
+ */ + @Test + public void testGetExponent() { + final double[] normalValues = { + 1E+300, 1E+200, 1E+100, 1E+10, 50, 20, 1, 1E-10, 1E-100, 1E-200, 1E-300, + POSITIVE_INFINITY, + NEGATIVE_INFINITY, + NaN, + MAX_VALUE, + MIN_NORMAL + }; + for (final double value : normalValues) { + assertEquals(StrictMath.getExponent(value), getExponent(value)); + } + /* + * Tests sub-normal values. We expect: + * + * getExponent(MIN_NORMAL ) == MIN_EXPONENT + * getExponent(MIN_NORMAL / 2) == MIN_EXPONENT - 1 + * getExponent(MIN_NORMAL / 4) == MIN_EXPONENT - 2 + * getExponent(MIN_NORMAL / 8) == MIN_EXPONENT - 3 + * etc. + */ + for (int i=0; i<=SIGNIFICAND_SIZE; i++) { + assertEquals(MIN_EXPONENT - i, getExponent(MIN_NORMAL / (1L << i))); + } + assertEquals(MIN_EXPONENT - 1, getExponent(JDK8.nextDown(MIN_NORMAL))); + assertEquals(MIN_EXPONENT - SIGNIFICAND_SIZE, getExponent(MIN_VALUE)); + assertEquals(MIN_EXPONENT - SIGNIFICAND_SIZE - 1, getExponent(0)); + /* + * Tests consistency with scalb, as documented in MathFunctions.getExponent(double) javadoc. + */ + for (int i = MIN_EXPONENT - SIGNIFICAND_SIZE - 1; i <= MAX_EXPONENT + 1; i++) { + assertEquals(i, getExponent(StrictMath.scalb(1.0, i))); + } + /* + * Tests consistency with log10, as documented in MathFunctions.getExponent(double) javadoc. + */ + for (int i = MIN_EXPONENT - SIGNIFICAND_SIZE; i <= MAX_EXPONENT; i++) { + assertEquals(StrictMath.floor(StrictMath.log10(StrictMath.scalb(1.0, i))), + StrictMath.floor(LOG10_2 * i /* i = getExponent(value) */), 0); + } } /** @@ -140,8 +237,8 @@ public final strictfp class MathFunction */ @Test public void testPow10() { - for (int i=-304; i<=304; i++) { // Range of allowed exponents in base 10. - assertEquals(Double.parseDouble("1E"+i), pow10((double) i), 0); + for (int i=E10_FOR_ZERO; i<=MAX_E10; i++) { // Range of allowed exponents in base 10. + assertEquals(parseDouble("1E"+i), pow10((double) i), 0); } } @@ -154,9 +251,9 @@ public final strictfp class MathFunction final double x = 0.1 * i; final double y = atanh(x); switch (i) { - case -10: assertEquals(Double.NEGATIVE_INFINITY, y, EPS); break; - default: assertEquals(x, StrictMath.tanh(y), EPS); break; - case +10: assertEquals(Double.POSITIVE_INFINITY, y, EPS); break; + case -10: assertEquals(NEGATIVE_INFINITY, y, EPS); break; + default: assertEquals(x, StrictMath.tanh(y), EPS); break; + case +10: assertEquals(POSITIVE_INFINITY, y, EPS); break; } } }