ICU-1871 double division inaccurate, changed round() api to handle that.

X-SVN-Rev: 11135
This commit is contained in:
Syn Wee Quek 2003-02-21 01:49:23 +00:00
parent b3bf0e1583
commit bce4ce451a
2 changed files with 106 additions and 45 deletions

View file

@ -1,7 +1,7 @@
/*****************************************************************************************
* $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatAPI.java,v $
* $Date: 2002/08/01 20:27:21 $
* $Revision: 1.3 $
* $Date: 2003/02/21 01:49:23 $
* $Revision: 1.4 $
*
*****************************************************************************************
**/
@ -24,9 +24,8 @@
package com.ibm.icu.dev.test.format;
import com.ibm.icu.lang.*;
import com.ibm.icu.text.*;
import com.ibm.icu.util.*;
import com.ibm.icu.math.BigDecimal;
import java.util.Locale;
import java.text.ParsePosition;
import java.text.Format;
@ -37,8 +36,58 @@ public class IntlTestDecimalFormatAPI extends com.ibm.icu.dev.test.TestFmwk
public static void main(String[] args) throws Exception {
new IntlTestDecimalFormatAPI().run(args);
}
/**
* Problem 1: simply running
* decF4.setRoundingMode(java.math.BigDecimal.ROUND_HALF_UP) does not work
* as decF4.setRoundingIncrement(.0001) must also be run.
* Problem 2: decF4.format(8.88885) does not return 8.8889 as expected.
* You must run decF4.format(new BigDecimal(Double.valueOf(8.88885))) in
* order for this to work as expected.
* Problem 3: There seems to be no way to set half up to be the default
* rounding mode.
* We solved the problem with the code at the bottom of this page however
* this is not quite general purpose enough to include in icu4j. A static
* setDefaultRoundingMode function would solve the problem nicely. Also
* decimal places past 20 are not handled properly. A small ammount of work
* would make bring this up to snuff.
*/
public void testJB1871()
{
// problem 2
DecimalFormat dec = new DecimalFormat(",##0.0000");
double roundinginc = 0.0001;
double number = 8.88885;
String expected = "8.8889";
dec.setRoundingMode(BigDecimal.ROUND_HALF_UP);
dec.setRoundingIncrement(roundinginc);
if (!dec.format(number).equals(expected)) {
errln("Error formating " + number + ", expected " + expected);
}
dec = new DecimalFormat(",##0.0001");
dec.setRoundingMode(BigDecimal.ROUND_HALF_UP);
if (!dec.format(number).equals(expected)) {
errln("Error formating " + number + ", expected " + expected);
}
// testing 20 decimal places
dec = new DecimalFormat(",##0.00000000000000000001");
BigDecimal bignumber = new BigDecimal("8.888888888888888888885");
expected = "8.88888888888888888889";
dec.setRoundingMode(BigDecimal.ROUND_HALF_UP);
if (!dec.format(bignumber).equals(expected)) {
errln("Error formating " + bignumber + ", expected " + expected);
}
}
// This test checks various generic API methods in DecimalFormat to achieve 100% API coverage.
/**
* This test checks various generic API methods in DecimalFormat to achieve
* 100% API coverage.
*/
public void TestAPI()
{
logln("DecimalFormat API test---"); logln("");
@ -213,20 +262,7 @@ public class IntlTestDecimalFormatAPI extends com.ibm.icu.dev.test.TestFmwk
if( ! s3.equals(p2) ) {
errln("ERROR: toLocalizedPattern() result did not match pattern applied");
}
// ======= Test getStaticClassID()
// logln("Testing instanceof()");
// try {
// NumberFormat test = new DecimalFormat();
// if (! (test instanceof DecimalFormat)) {
// errln("ERROR: instanceof failed");
// }
// }
// catch (Exception e) {
// errln("ERROR: Couldn't create a DecimalFormat");
// }
}
}

View file

@ -5,24 +5,20 @@
*******************************************************************************
*
* $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/text/DecimalFormat.java,v $
* $Date: 2002/12/05 01:21:38 $
* $Revision: 1.20 $
* $Date: 2003/02/21 01:49:21 $
* $Revision: 1.21 $
*
*****************************************************************************************
*/
package com.ibm.icu.text;
import com.ibm.icu.util.Currency;
import java.text.Format;
import java.text.ParsePosition;
import java.text.FieldPosition;
import java.math.BigInteger;
import java.util.ResourceBundle;
import java.util.Locale;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Hashtable;
import java.io.InvalidObjectException; //Bug 4185761 [Richard/GCL]
/**
* <code>DecimalFormat</code> is a concrete subclass of
@ -496,8 +492,9 @@ public class DecimalFormat extends NumberFormat {
// Apply rounding after multiplier
if (roundingDouble > 0.0) {
number = roundingDouble
* round(number / roundingDouble, roundingMode, isNegative);
// number = roundingDouble
// * round(number / roundingDouble, roundingMode, isNegative);
number = round(number, roundingDouble, roundingMode, isNegative);
}
if (Double.isInfinite(number))
@ -529,46 +526,74 @@ public class DecimalFormat extends NumberFormat {
return subformat(result, fieldPosition, isNegative, false);
}
}
/**
* <strong><font face=helvetica color=red>NEW</font></strong>
* Round a double value to the nearest integer according to the
* given mode.
* @param a the absolute value of the number to be rounded
* Note this is changed from the version in 2.4, since division of doubles
* have inaccuracies. jitterbug 1871.
* @param number the absolute value of the number to be rounded
* @param roundingInc the rounding increment
* @param mode a BigDecimal rounding mode
* @param isNegative true if the number to be rounded is negative
* @return the absolute value of the rounded result
*/
private static double round(double a, int mode, boolean isNegative) {
private static double round(double number, double roundingInc,
int mode, boolean isNegative) {
double div = number / roundingInc;
switch (mode) {
case java.math.BigDecimal.ROUND_CEILING:
return isNegative ? Math.floor(a) : Math.ceil(a);
return (isNegative ? Math.floor(div) : Math.ceil(div))
* roundingInc;
case java.math.BigDecimal.ROUND_FLOOR:
return isNegative ? Math.ceil(a) : Math.floor(a);
return (isNegative ? Math.ceil(div) : Math.floor(div))
* roundingInc;
case java.math.BigDecimal.ROUND_DOWN:
return Math.floor(a);
return (Math.floor(div)) * roundingInc;
case java.math.BigDecimal.ROUND_UP:
return Math.ceil(a);
return (Math.ceil(div)) * roundingInc;
case java.math.BigDecimal.ROUND_HALF_EVEN:
// We should be able to just return Math.rint(a), but this
// doesn't work in some VMs.
{
double f = Math.floor(a);
if ((a - f) != 0.5) {
return Math.rint(a);
{ double ceildiff = (Math.ceil(div) * roundingInc) - number;
double floor = Math.floor(div);
double floordiff = number - (floor * roundingInc);
if (ceildiff != floordiff) {
return (Math.rint(div)) * roundingInc;
}
f /= 2.0;
return f == Math.floor(f) ? Math.floor(a) : (Math.floor(a) + 1.0);
floor /= 2.0;
return (floor == Math.floor(floor) ? Math.floor(div)
: (Math.floor(div) + 1.0))
* roundingInc;
}
case java.math.BigDecimal.ROUND_HALF_DOWN:
return ((a - Math.floor(a)) <= 0.5) ? Math.floor(a) : Math.ceil(a);
{
double ceil = Math.ceil(div);
double ceildiff = (ceil * roundingInc) - number;
double floor = Math.floor(div);
double floordiff = number - (floor * roundingInc);
if (ceildiff < floordiff) {
return ceil * roundingInc;
}
return floor * roundingInc;
}
case java.math.BigDecimal.ROUND_HALF_UP:
return ((a - Math.floor(a)) < 0.5) ? Math.floor(a) : Math.ceil(a);
{
double ceil = Math.ceil(div);
double ceildiff = (ceil * roundingInc) - number;
double floor = Math.floor(div);
double floordiff = number - (floor * roundingInc);
if (ceildiff <= floordiff) {
return ceil * roundingInc;
}
return floor * roundingInc;
}
case java.math.BigDecimal.ROUND_UNNECESSARY:
if (a != Math.floor(a)) {
if (div != Math.floor(div)) {
throw new ArithmeticException("Rounding necessary");
}
return a;
return number;
default:
throw new IllegalArgumentException("Invalid rounding mode: " + mode);
}