How to prevent rounding up decimal values using NumberFormat? - java

I have this function:
public void printCurrency(double currencyAmount, String lang, String country) {
Locale locale = new Locale(lang, country);
Currency currency = Currency.getInstance(locale);
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
numberFormat.setMaximumFractionDigits(2);
Log.d(TAG, currency.getDisplayName() + ": " + numberFormat.format(currencyAmount));
}
Everytime I call this with the following:
double amount = 123456789012345.67d;
printCurrency(amount, "jp", "JP");
printCurrency(amount, "en", "GB");
printCurrency(amount, "en", "US");
My amount is always rounded like so:
> Japanese Yen: ¥123,456,789,012,346
> British Pound Sterling: £123,456,789,012,346.00
> US Dollar: $123,456,789,012,346.00
I need a solution that always give a 2-decimal-place amount (if applicable) such as
> Japanese Yen: ¥123,456,789,012,346
> British Pound Sterling: £123,456,789,012,345.67
> US Dollar: $123,456,789,012,345.67
I tried the following but same issue:
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
numberFormat.setMaximumFractionDigits(2);
numberFormat.setRoundingMode(RoundingMode.UNNECESSARY);

I want to post my test result here because I don't think there is something wrong with my code.
Given the following values:
amount = 1234567890123.67;
amount = 12345678901234.67;
amount = 123456789012345.67;
my function prints the following amounts:
British Pound Sterling: £1,234,567,890,123.67
British Pound Sterling: £12,345,678,901,234.70
British Pound Sterling: £123,456,789,012,346.00
As you can see, there is no rounding of amount until length is more than 15 digits.
EDIT: Tried the amount = 12345678901234567890d, result is
British Pound Sterling: £12,345,678,901,234,600,000.00
So I guess, this approach is limited to amount length <= 15.

Related

Checking if a String has a fraction part in Java : Locale involved

I have a String value which can either hold a Long or a Double. The String may be Locale based.
So it may hold the following values:
11
11.00
15,25 (for Locale like Denmark where the decimal part is denoted by a comma instead of dot)
I want to do something only when it is a Double; in sense that it contains a fraction value. A fraction value of "00" is also a valid case.
if(string contains fraction){
// do something
}
Given above three examples, control should go inside if for 11.00 and 15,25 but not for 11.
How can I check this?
Please keep in mind that Locale is involved. So dot and comma may have different meaning for different Locale. So simple regex to find their occurrence won't work. For e.g. 11,00 is 1100 if Locale is Australia and thus is not a double. But 11,00 is a double if Locale is a European country like Denmark or Germany.
I need to find some solution using NumberFormat but not able to work it out.
I have Locale info. So I know if the String is of which Locale. Given that, how can I find if String has a fraction or not?
EDIT: Since you've edited your question stating you know the Locale, you can use it with NumberFormat.getNumberInstance(locale).parse(strValue) in combination with a regex for the comma and thousand separator. Here a test code:
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
class Main{
private static final Locale DUTCH = new Locale("nl","NL");
public static void main(String[] a){
test("11", Locale.ENGLISH);
test("11", DUTCH);
System.out.println();
test("11.00", Locale.ENGLISH);
test("11.00", DUTCH);
System.out.println();
test("11,00", Locale.ENGLISH);
test("11,00", DUTCH);
System.out.println();
test("15.123", Locale.ENGLISH);
test("15.123", DUTCH);
System.out.println();
test("15,123", Locale.ENGLISH);
test("15,123", DUTCH);
System.out.println();
test("something", Locale.ENGLISH);
test("something", DUTCH);
}
static void test(String val, Locale locale){
try{
DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
char decimalSep = symbols.getDecimalSeparator();
char thousandSep = symbols.getGroupingSeparator();
String escapedDecimalSep = decimalSep == '.' ? "\\." : decimalSep+"";
String escapedThousandSep = thousandSep == '.' ? "\\." : thousandSep+"";
String intRegex = "\\d+(" + escapedThousandSep + "\\d{3})*"; // Example ENGLISH: "\\d+(,\\d{3})*"
String doubleRegex = intRegex + escapedDecimalSep + "\\d+"; // Example ENGLISH: "\\d+(,\\d{3})*\\.\\d+"
NumberFormat format = NumberFormat.getInstance(locale);
Number number = format.parse(val);
if(val.matches(doubleRegex)){
double d = number.doubleValue();
System.out.println(val + " (in locale " + locale + ") is a double: " + d);
} else if(val.matches(intRegex)){
int i = number.intValue();
System.out.println(val + " (in locale " + locale + ") is an integer: " + i);
} else{
System.out.println("Unable to determine whether value " + val + " is an integer or double for locale " + locale);
}
} catch(ParseException ex){
System.out.println("Error occurred for value \"" + val + "\". Are you sure it's an integer or decimal?");
}
}
}
Try it online.
Here is the output:
11 (in locale en) is an integer: 11
11 (in locale nl_NL) is an integer: 11
11.00 (in locale en) is a double: 11.0
Unable to determine whether value 11.00 is an integer or double for locale nl_NL
Unable to determine whether value 11,00 is an integer or double for locale en
11,00 (in locale nl_NL) is a double: 11.0
15.123 (in locale en) is a double: 15.123
15.123 (in locale nl_NL) is an integer: 15123
15,123 (in locale en) is an integer: 15123
15,123 (in locale nl_NL) is a double: 15.123
Error occurred for value "something". Are you sure it's an integer or decimal?
Error occurred for value "something". Are you sure it's an integer or decimal?
With a regex you could do
Pattern decimalPattern = Pattern.compile("\\d+(,|\\.)\\d+{2}");
and then have
boolean isDecimal = decimalPattern.matcher(input).matches();
Regex:
\d+ one or more digits
(,|\\.) a decimal point or a comma
\d+ one or more digits again
Or you could do the splitting thing
String[] split = input.split("(,|\\.)");
boolean isDecimal = split.length > 1 && split[1].length() == 2;
You could use a loop to check if it have a comma or dot and then check with the if?
boolean IsADouble = false;
for (int i = 0; i < String.length(); i++) {
if (String.charAt(i) == ','|| String.charAt(i) == '.') {
IsADouble = true
}
}
And then you create the If to do something if its a double.
Hope it helped you :)

Is locale aware parsing for Numberformat possible in Android?

I have an Android application which does some basic mathematics.
Example
try {
NumberFormat nf = NumberFormat.getNumberInstance();
DecimalFormat df = (DecimalFormat) nf;
a = Float.parseFloat(vw3.getText().toString());
f = Float.parseFloat(vw5.getText().toString());
c = a / 100;
d = c * 1.036f;
e = f / 100;
g = e * 1.24f;
h = d + g;
String str1 = String.valueOf(df.format(h));
vw7.setText(str1);
} catch (NumberFormatException f) {
a = (0);
}
}
When the user is in the USA the calculations work fine and format fine. Well as you would expect. The 1,000.00 format where the grouping is by comma and separator is by decimal point. When a user is in France, the grouping is different and the separator is also different. Using the 1,000.00 example, in France the number would be formatted like this 1 000,00. A space is the grouping separator and the comma is the decimal separator. This causes a problem when you try and run a calculation and you will get a NumberFromatException (NFE). And I anticipated a NFE issue and catch it and replace the possible cause with the correct number. However, replacing any comma with a space and any period with a comma will also produce a NFE.
Example
try {
NumberFormat nf = NumberFormat.getNumberInstance();
DecimalFormat df = (DecimalFormat) nf;
a = Float.parseFloat(vw3.getText().toString().replace(",",""));
f = Float.parseFloat(vw5.getText().toString().replace(",",""));
c = a / 100;
d = c * 1.036f;
e = f / 100;
g = e * 1.24f;
h = d + g;
String str1 = String.valueOf(df.format(h));
vw7.setText(str1);
} catch (NumberFormatException f) {
a = (0);
}
}
EDIT - As suggested by Peter O. I have tried parsing the number with a locale aware means.
Example
NumberFormat.getNumberInstance().parse(string);
Or
NumberFormat df = NumberFormat.getNumberInstance();
String value = "10,40 €";
Number valueParsed = df.parse(value);
vw7.setText(valueParsed);
Will produce a "Bad Class" illegalargument.
I am looking for a solution to where I can do the calculations in an acceptable manner within the apps programming regardless of the locale and then later format the results to the locale. The question could be or is, do you force a locale for your calculations and then format the results for the locale?
If this is the code you are using and your strings will have the currency symbol. In this case € the EURO symbol.
Your example:
NumberFormat df = NumberFormat.getNumberInstance();
String value = "10,40 €";
Number valueParsed = df.parse(value);
vw7.setText(valueParsed);
so that value always has a currency symbol you need to use
NumberFormat df= NumberFormat.getCurrencyInstance();
instead of the .getNumberInstance(). This worked on my system which is currently set so that the € symbols is the default currency symbol. You will have to try it on a system that has the $ currency symbol in order to verify that it works there, also.

java.util.IllegalFormatConversionException: f != java.lang.String Error

import javax.swing.JOptionPane;
public class Minutes {
public static void main(String[] args) {
double BasePlanCost = 20;
final double BaseCostPerMinute=0.15;
double MinutesUsed = Double.parseDouble(JOptionPane.showInputDialog("Please enter the amount of minutes Used: "));
double CostForMinutes = BaseCostPerMinute * MinutesUsed;
double GrandTotal = BasePlanCost + CostForMinutes;
JOptionPane.showMessageDialog(null, String.format("$%.2f","**IST Wireless Receipt**","\n","Base Plan Cost:" +BasePlanCost,"/n","Cost For Minutes Used: "+ CostForMinutes,"/n","Grand Total :" +GrandTotal));
}
}
This program inputs the amount of minutes the user enters and calculates the grand total by adding the CostForMinutes and BasePlanCost.
CostForMinutes is calculated by multiplying the minutes the user enters and the BaseCostPerMinute. The out is all the numbers outputted by two decimal places and outputted as a receipt.
When I compile the program it lets me input the amount of minutes but the code collapses and gives me this error
exception in thread "main" java.util.IllegalFormatConversionException: f != java.lang.String
can anyone help me out?
EDIT this is what I want the output to look like
http://i.stack.imgur.com/CubfC.png
You have
String.format("$%.2f","**IST Wireless Receipt**",
This means you want to format the second argument which is a String using %.2f which is a float format which won't work.
You need to re-organize your format to be first and the values you want to format after it.
String.format("**IST Wireless Receipt**%n" +
"Base Plan Cost: $%.2f%n" +
"Cost For Minutes Used: $%.2f%n" +
"Grand Total: $%.2f%n",
BasePlanCost, CostForMinutes, GrandTotal)
Try to organize your message as:
String message = String.format(
"**IST Wireless Receipt** \n" +
" Base Plan Cost:$ %.2f \n" +
" Cost For Minutes Used: $ %.2f \n" +
" Grand Total : $ %.2f", BasePlanCost, CostForMinutes, GrandTotal);
JOptionPane.showMessageDialog(null, message);
I recommended you to read the code conventions of the java language
http://www.oracle.com/technetwork/java/codeconventions-135099.html

Java Decimal Format parsing issue

public class NumFormatTest
{
public static void main(String[] args) throws ParseException
{
String num = "1 201";
DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.FRANCE);
System.out.println("Number Before parse: "+num);
double dm = df.parse(num).doubleValue();
System.out.println("Number After parse: "+dm);
}
}
Output:
Number Before parse: 1 201
Number After parse: 1.0
Expected Output:
Number Before parse: 1 201
Number After parse: **1201**
Can any please help me understand why parse is not able to convert a FRENCH locale formatted string (1 201) to normal double value (1201.0)?
There are two kinds of spaces. The "normal" space character (No. 32 - HEX 0x20) and the non-breaking space (NBSP) (No. 160 - HEX 0xA0).
The French locale expects the whitespace character between the digits to be the non breaking space! You can help yourself with this line of code:
String num = "1 201";
num = num.replaceAll(" ", "\u00A0"); // '\u00A0' is the non breaking whitespace character!
This way your code will work like expected. Please note that if you format a double into a String with French locale the resulting whitespace character will be the NBSP too!!!
DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.FRENCH);
System.out.println(df.format(1201.1));
// This will print "1 202,1" But the space character will be '\u00A0'!
You can use
String num = "1 201";
DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.FRANCE);
System.out.println("Number Before parse: "+num);
DecimalFormatSymbols symbols = df.getDecimalFormatSymbols();
symbols.setGroupingSeparator(' ');
df.setDecimalFormatSymbols(symbols);
double dm = df.parse(num).doubleValue();
System.out.println("Number After parse: "+dm);
Expected Output:
Number Before parse: 1 201
Number After parse: 1201.0
Actually, Java is using the character unbreakable space (\u00a0) to parse French numbers.
Thus, the following code actually works:
String num = "1\u00a0201";
double dm = df.parse(num).doubleValue();
System.out.println("Number After parse: " + dm);
See #ParkerHalo answer which provide more details.
This seems to work.
public double parse(String decimalAsText) {
NumberFormat decimalFormat = NumberFormat.getNumberInstance(Locale.FRANCE);
decimalAsText = decimalAsText.replace(' ', '\u00a0');
ParsePosition pp = new ParsePosition(0);
Number n = decimalFormat.parse(decimalAsText, pp);
return n.doubleValue();
}

Changing the pattern for CHF currency in Java using icu package?

I wrote a java function that shows the locale pattern for each currency. See the function below. What I am interested to know is that why when the currency is CHF, the 2nd decimal is hardcoded to 5?
Note that I am using icu package and this issue doesn't exist with java.util.Currency package. I am using the default locale of en_US.
Here is the output of the function which is related to USD and CHF currencies:
Analyzing currency: [USD] localePattern: [¤#,##0.00;(¤#,##0.00)] Currency symbol [$]
Analyzing currency: [CHF] localePattern: [¤#,##0.05;(¤#,##0.05)] Currency symbol [SwF]
Here is the java function I wrote:
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.util.Currency;
public static void main(String[] args)
{
Currency configuredCurrency = null;
NumberFormat formatter = NumberFormat.getCurrencyInstance();
DecimalFormat localeCurrencyFormatter = (DecimalFormat)formatter;
String localePattern = "";
String symbol = "";
String currencies = "AED,AFN,ALL,AMD,ARS,AUD,BGN,BHD,BND,BOB,BRL,BWP,BYR,CAD,CHF,CLP,CNY,COP,CRC,CZK,DJF,DKK,DOP,DZD,EEK,EGP,ERN,ETB,EUR,GBP,GTQ,HKD,HNL,HRK,HUF,IDR,ILS,INR,IQD,IRR,ISK,JOD,JPY,KES,KPW,KRW,KWD,KZT,LBP,LTL,LVL,LYD,MAD,MKD,MTL,MXN,MYR,NIO,NOK,NZD,OMR,PAB,PEN,PHP,PKR,PLN,PYG,QAR,RON,RUB,SAR,SDD,SEK,SGD,SKK,SOS,SVC,SYP,SwF,THB,TND,TRY,TZS,UAH,USD,UYU,VEB,VND,YER,ZAR,ZWD";
String[] currenciesArray = currencies.split(",");
for (int i = 0; i < currenciesArray.length; i++)
{
String currency = currenciesArray[i];
configuredCurrency = Currency.getInstance(currency);
localeCurrencyFormatter.setCurrency(configuredCurrency);
localePattern = localeCurrencyFormatter.toPattern();
symbol = localeCurrencyFormatter.getCurrency().getSymbol();
System.out.println("Analyzing currency: [" + currency + "] localePattern: [" + localePattern + "] Currency symbol [" + symbol + "]");
}
}
The 5 there is the rounding increment (there is no 0.01 of Swiss franc, 0.05 is the least valuable coin (Swiss franc wikipedia)).
Also from the icu4j DecimalFormat javadoc:
"In place of '0', the digits '1' through '9' may be used to indicate a rounding increment."
The '5' tells the ICU package that there are special rules about how to round the number to the nearest 5/100ths when converting to a string form.
"In Switzerland, five centimes are the smallest currency unit for payment transactions. For Swiss company codes and the currency Swiss franc, you therefore enter 5 ."
SAP help web site
Thank you all for the help. I was finally able to find an answer. I wrote this piece of code and did the trick:
localeCurrencyFormatter.setRoundingIncrement(new BigDecimal("0"));

Categories

Resources