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 :)
Related
I have a decimal in a string format in a locale that is not 'en_US'. I would like to convert it to a decimal in 'en_US' locale.
Eg: "123.345.123,45" -> "123345123.45" or "123,345,123.45"
"123 345,45" -> "123345.45" or "123,345.45"
"123.345" -> "123345" or "123,345"
It seems like NumberFormat will do what you're looking for.
Found this snippet in the oracle documentation:
static public void displayNumber(Locale currentLocale) {
Integer quantity = new Integer(123456);
Double amount = new Double(345987.246);
NumberFormat numberFormatter;
String quantityOut;
String amountOut;
numberFormatter = NumberFormat.getNumberInstance(currentLocale);
quantityOut = numberFormatter.format(quantity);
amountOut = numberFormatter.format(amount);
System.out.println(quantityOut + " " + currentLocale.toString());
System.out.println(amountOut + " " + currentLocale.toString());
}
This example prints the following; it shows how the format of the same number varies with Locale:
123 456 fr_FR
345 987,246 fr_FR
123.456 de_DE
345.987,246 de_DE
123,456 en_US 3
45,987.246 en_US
Here's the link that content is from:
https://docs.oracle.com/javase/tutorial/i18n/format/numberFormat.html
In order to convert a formatted number string from one Locale to another, you must know the Locale from which the string was generated. From there you can use NumberFormat.parse to convert the string back to a Number. Then use the target Locale to create another NumberFormat and use the .format method to change the string representation.
static public String displayNumber(String startValue, Locale startLocale, Locale endLocale) throws ParseException {
NumberFormat parser = NumberFormat.getNumberInstance(startLocale);
NumberFormat formatter = NumberFormat.getNumberInstance(endLocale);
Number rawNumber = parser.parse(startValue);
return formatter.format(rawNumber);
}
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.
I am trying to apply this format to a given int number #´###,### I tried with DecimalFormat class but it only allows to have one grouping separator symbol when I need to have two the accute accent for millions and commas for thousands.
So at the end I can format values like 1,000 or millions in this way 1´000,000
I always prefer to use String.format, but I am not sure if there is a Locale that would format numbers like that either. Here is some code that will do the job though.
// Not sure if you wanted to start with a number or a string. Adjust accordingly
String stringValue = "1000000";
float floatValue = Float.valueOf(stringValue);
// Format the string to a known format
String formattedValue = String.format(Locale.US, "%,.2f", floatValue);
// Split the string on the separator
String[] parts = formattedValue.split(",");
// Put the parts back together with the special separators
String specialFormattedString = "";
int partsRemaining = parts.length;
for(int i=0;i<parts.length;i++)
{
specialFormattedString += parts[i];
partsRemaining--;
if(partsRemaining > 1)
specialFormattedString += "`";
else if(partsRemaining == 1)
specialFormattedString += ",";
}
I found useful the link #Roshan provide in comments, this solution is using regex expression and replaceFirst method
public static String audienceFormat(int number) {
String value = String.valueOf(number);
if (value.length() > 6) {
value = value.replaceFirst("(\\d{1,3})(\\d{3})(\\d{3})", "$1\u00B4$2,$3");
} else if (value.length() >=5 && value.length() <= 6) {
value = value.replaceFirst("(\\d{2,3})(\\d{3})", "$1,$2");
} else {
value = value.replaceFirst("(\\d{1})(\\d+)", "$1,$2");
}
return value;
}
I don't know if this solution has a performance impact, also I am rockie with regex, so this code might be shorted.
Try this, these Locale formats in your required format.
List<Locale> locales = Arrays.asList(new Locale("it", "CH"), new Locale("fr", "CH"), new Locale("de", "CH"));
for (Locale locale : locales) {
DecimalFormat df = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
dfs.setCurrencySymbol("");
df.setDecimalFormatSymbols(dfs);
System.out.println(String.format("%5s %15s %15s", locale, format(df.format(1000)), format(df.format(1_000_000))));
}
util method
private static String format(String str) {
int index = str.lastIndexOf('\'');
if (index > 0) {
return new StringBuilder(str).replace(index, index + 1, ",").toString();
}
return str;
}
output
it_CH 1,000.00 1'000,000.00
fr_CH 1,000.00 1'000,000.00
de_CH 1,000.00 1'000,000.00
set df.setMaximumFractionDigits(0); to remove the fractions
output
it_CH 1,000 1'000,000
fr_CH 1,000 1'000,000
de_CH 1,000 1'000,000
Maybe try using this, the "#" in place with the units you want before the space or comma.
String num = "1000500000.574";
String newnew = new DecimalFormat("#,###.##").format(Double.parseDouble(number));
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();
}
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"));