I'm developing an app where I use the Geocoder to get a place's coordinates.
The operative is this:
The user defines an address.
The geocoder finds that address and I get the coordinates from that address.
This coordinates are in decimal format and I need them in degrees-minutos so I format them.
To format the coordinates from decimal to degrees-minutes I use:
String frmtLatitude = Location.convert(Double.parseDouble(lat), Location.FORMAT_MINUTES);
So, if I have for example this latitude 43.249591 in decimal value, it returns it like this 43:14.97546.
After this, I have to make some operations to finally get the latitude with this appearance: 4314.975
When I do this operations, one of them is to split the value using the ".". I split 14.97546 to get in one hand the 14 and in the other 97546.
Until here, everything ok. It works fine when I have my phone's language selected to be in english. But if I select to be in spanish, the app crashes. I have followed the stacktrace and it points there. Is like that in english when using the first commented function to convert from decimal to degrees-minutes it separates the decimals with a "." but if I have it in spanish, it separates them with a ",".
Can this really happen or the cause could be another thing?
We can look at the source code of the convert method
public static String convert(double coordinate, int outputType) {
if (coordinate < -180.0 || coordinate > 180.0 ||
Double.isNaN(coordinate)) {
throw new IllegalArgumentException("coordinate=" + coordinate);
}
if ((outputType != FORMAT_DEGREES) &&
(outputType != FORMAT_MINUTES) &&
(outputType != FORMAT_SECONDS)) {
throw new IllegalArgumentException("outputType=" + outputType);
}
StringBuilder sb = new StringBuilder();
// Handle negative values
if (coordinate < 0) {
sb.append('-');
coordinate = -coordinate;
}
DecimalFormat df = new DecimalFormat("###.#####");
if (outputType == FORMAT_MINUTES || outputType == FORMAT_SECONDS) {
int degrees = (int) Math.floor(coordinate);
sb.append(degrees);
sb.append(':');
coordinate -= degrees;
coordinate *= 60.0;
if (outputType == FORMAT_SECONDS) {
int minutes = (int) Math.floor(coordinate);
sb.append(minutes);
sb.append(':');
coordinate -= minutes;
coordinate *= 60.0;
}
}
sb.append(df.format(coordinate));
return sb.toString();
}
We can see that it uses a DecimalFormat with a given pattern. So, if we look to the DecimalFormat constructor :
public DecimalFormat(String pattern) {
// Always applyPattern after the symbols are set
this.symbols = new DecimalFormatSymbols(Locale.getDefault());
applyPattern(pattern, false);
}
We can see here that even if we give a pattern, it uses the locale values. The javadoc also said :
Parameters:
pattern A non-localized pattern string.
To finish, we can go here to see the different local variant of numbers representation : http://docs.oracle.com/cd/E19455-01/806-0169/overview-9/index.html
So we can see that US-English use the "dot format" and that Spanish use "comma format".
To answer your question : the proflem you're facing is probably due to the Decimal format of your locale. I advice you to be REALLY CAREFUL when converting types of objects to make manipulation on them. Converting an int to a String should be only to display it.
I think you should seperate decimal part of your number when it stills a float (or any decimal type) and then convert your object to a String to display it. You can take a look at Math class or search SO to get some example on how to this ;)
Also, as #Dmitry said, you can get DecimalSeparator with DecimalFormatSymbols.getDecimalSeparator().
Sources
Location.convert(double,int) source code
DecimalFormat(String) source code
Java "Decimal and thousands separators"
You are right, decimal seperator depends on your locale. You can get it by something like this
DecimalFormat df = new DecimalFormat();
DecimalFormatSymbols formatSymbols = df.getDecimalFormatSymbols();
char separator = formatSymbols.getDecimalSeparator();
Related
I'm working on an app and facing an issue. I've tried a number of solutions but nothing solved my problem.
I need to round off two digits after decimal point.
For Example.
9.225 should be rounded off to 9.23
Thank you.
For Kotlin use "%.2f".format(number), for Java use String.format("%.2f", number)
Result:
You can use String.format("%.2f", d), this will rounded automatically. d is your value.
OR
You can use this
double d = 1.234567;
DecimalFormat df = new DecimalFormat("#.##");
Log.d(df.format(d));
You can get as a float value as well like below.
float value = Float.valueOf(df.format(d)); // Output will be 1.24
I would have gone with a probable over the top solution however this is what i came up with.
It uses regex to split the string value of the number passed and then rounds up/down depending on the leading digit after the decimal place. It will return a Double in the instance but you can change that if you like. It does throw IllegalArgumentException, but thats taste dependant.
/**
* #param value the value that is being transformed
* #param decimalPlace the decimal place you want to return to
* #return transformed value to the decimal place
* #throws IllegalArgumentException
*/
Double roundNumber(#NonNull Double value, #NonNull Integer decimalPlace) throws IllegalArgumentException {
String valueString = value.toString();
if(valueString.length()> decimalPlace+1){
throw new IllegalArgumentException(String.format("The string value of %s is not long enough to have %dplaces", valueString, decimalPlace));
}
Pattern pattern = Pattern.compile("(\\d)('.')(\\d)");
Matcher matcher = pattern.matcher(valueString);
if (matcher.groupCount() != 4) { //0 = entire pattern, so 4 should be the total ?
throw new IllegalArgumentException(String.format("The string value of %s does not contain three groups.", valueString));
}
String decimal = matcher.group(3);
int place = decimal.charAt(decimalPlace);
int afterDecimalPlace = decimal.charAt(decimalPlace + 1);
String newDecimal = decimal.substring(0, decimalPlace - 1);
newDecimal += afterDecimalPlace > 5 ? (place + 1) : place;
return Double.parseDouble(matcher.group(1) + "." + newDecimal);
}
When the user change the locale in device the numbers are also getting changed according to the selected locale. This is causing NumberFormatException while performing mathematical operations and app is getting crashed. The code snippet which is causing the crash is given below.
public static double ToDataUnitMB(double _dataBytes){
double dDataBytes;
dDataBytes = Double.parseDouble(getDecimalFormat().format(_dataBytes / 1048576));
return dDataBytes; }
This code snippet is causing NumberFormatException and the value in _dataBytes is shown as "७२.४१". Can anyone help me to prevent the number from changing when user change the locale.
Update
I am getting the value "७२.४१" after performing the below operation getDecimalFormat().format(_dataBytes / 1048576)
So while parsing to Double it is showing numberFormatException
Since you're starting with raw _dataBytes you have several options how to format number independent of the locale.
First Approach:
You can modify following snippet to your needs. It will give you the same output regardless of the user locale.
String patern = "###.##"; //your pattern as per need
Locale locale = new Locale("en", "US");
DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getNumberInstance(locale);
decimalFormat.applyPattern(patern);
double formatedDouble = Double.parseDouble(decimalFormat.format(_dataBytes/(1024*1024f)));
Keep in mind that this method also makes grouping and decimal separators to be fixed, so that comma and dot will alway be used as, respectively, grouping separator and decimal separator.
Second Approach:
If you do not strictly require Double you could generate formatted String with something similar to following method:
String generateFormatedFileSize(long _dataBytes) {
String formatedFileSize = "";
long bytes = _dataBytes;
short unit = 1024;
if (bytes < unit)
formatedFileSize = bytes + " B";
else {
int exp = (int) (Math.log(bytes) / Math.log(unit));
formatedFileSize = String.format("%.1f %sB", bytes / Math.pow(unit, exp), "KMGT".charAt(exp - 1));
}
return formatedFileSize;
}
This formatting will be sensitive to grouping separator and decimal separator, but otherwise insensitive to Locale.
For Local that uses "US" numbering format, this will give you following output:
12.5 KB
5.3 B
8.0 MB
And for Local using "European" numbering format:
12,5 KB
5,3 B
8,0 MB
Off course, these two methods are not exclusive and you could use some mix of these approaches at different parts of the App.
I am 99% sure that this cannot be done, however I thought I would ask to be certain.
I am attempting to create an application that calculates the required dice roll for an action in a popular tabletop war game.
The following is this calculation in Java
int x = ((WSattacker * 2) - WSdefender);
int y = (WSattacker - WSdefender);
String result;
// Calculation for a +5
if (x <= -1) {
result = "5+";
}
// Calculation for a +4
else if (x >= 0 && y <= 0) {
result = "4+";
}
// Calculation for a +3
else if (y > 0) {
result = "3+";
} else {
result = "Error";
}
return result;
Now my issue is that to avoid copywriter infringement I cannot mention the name of the game in my application, and probably cannot hard code the above calculation in the app.
This means that it is difficult to tell a potential user what the app will do.
The only solution I can think of is to make the application generic and allow the user to input the calculation required in the form of an equation.
An equation that I can place anonymously on a public board or similar.
Therefore my questions are as follows.
Is there another way of going about this?
If no, is it possible to condense the above code into a single expression/ equationi.e. one that removes the if and else statements
To answer question 2:
result = test_condition_1 ? result2_if_true : (test_condition_2 ? result2_if_true : test3_or_result2);
You can then build up 'compound' test conditions this way, and it's based upon ternary operators.
EDIT
Ternary operators are a short-hand way of writing if..then..else statments, and more information can be found in the wiki-link above. An example of its use is below, which you can compile and run:
public class TernaryTest {
public static void main(String [] args){
int x = 14;
int y = 5;
String result = ( x <= 10 ) ? "Less than 10" : "More than 10";
System.out.println("Result is: " + result);
}
}
Try running it and see the result as you change the value of x to understand how it works. Then it's possible to extend it to include and else by replacing the "more than 10" string.
I have double amount. Let amount to be 500000.12. I want to set value of the amount to TextView like this format 500,000.12 (where 12 is cents , 500,000 is dollars).
I wrote this function and it works
private String getAmountAsString(double amount) {
double integralPart = amount % 1;
int fractionalPart = (int) (amount - integralPart);
int integral = (int) integralPart * 100;
String strFractional = String.format("%,d", fractionalPart);
String strAmount = (strFractional + "." + String.valueOf(integral));
return strAmount;
}
But I think that there can be some easy and good way of doing this with java native functions. Can anybody help to find functions or some better way?
various Locale can be used to format float, double etc. You can use:
String.format(Locale.<Your Locale>, "%1$,.2f", myDouble);
Here .2f represents how many digits you want after decimal. If you are not specifying any locale it will use the default locale.
In String class this method is overloaded as:
format(String format, Object... args)
&
format(Locale l, String format, Object... args)
For more info have a look: Link1 , Link2 and Link3
Therefore NumberFormats are used. They are good to handle local differents for different countries.
//define a local, gets automated if a point or comma is correct in this Country.
NumberFormat anotherFormat = NumberFormat.getNumberInstance(Locale.US);
DecimalFormat anotherDFormat = (DecimalFormat) anotherFormat;
anotherDFormat.applyPattern("#.00");//set the number of digits afer the point
anotherDFormat.setGroupingUsed(true);// set grouping
anotherDFormat.setGroupingSize(3);//and size of grouping
double myDouble = 123456.78;
String numberWithSeparators = anotherDFormat.format(myDouble);//convert it
I think you have to take a look at this one
http://docs.oracle.com/javase/tutorial/i18n/format/decimalFormat.html
I'm calling Google Maps Intent from my activity with this code found on StackOverflow:
final String uriContent = String.format(Locale.ENGLISH, "http://maps.google.com/maps?q=loc:%s", pCoordinate);
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uriContent));
pContext.startActivity(intent);
where pCooriante contains entirely address such as 1.23456,7.8901.
It works well when my phone is using English as its language, but when I change it to French or Vietnamese (which use comma , as its number decimal seperator), it can't work anymore, because the query proceeded by Google Maps look like: 1,000,2,000 (it is shown in the search bar, and after that, a message like Cannot find 1,0000,2,0000 appears), although the exact URI I sent to the intent is 1.000,2.000 (the coordinate is converted to String to prevent Locale problems, and therefore the Locale.ENGLISH in String.format is more or less just abundant).
In short, Uri.parse(uriContent) return exactly the request with the query is 1.000,2.000, but Google Maps itself changed it. However, the code for direction works well with either Locale:
final String uriContent = String.format(Locale.ENGLISH, "google.navigation:q=%s", pCoordinate);
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uriContent));
pContext.startActivity(intent);
Is there anyway to prevent the conversion of Google Maps? If I use geo:<coordinate>, it's fine, but I need a marker at that position.
Addional information:
This code final String uriContent = String.format("geo:0,0?q=%s&z=19", pCoordinate); doesn't work too, the periods are converted into commas.
This code final String uriContent = String.format("geo:%s?q=%s&z=19", pCoordinate, pCoordinate); can bring the map center to the coordinate, but still cannot put the marker there, and with the error "Cannot find 'coordinate with periods replaced by commas'"
I am using a temporary solution to this problem, by converting the decimal form of coordinates to degree one. (For example, instead of sending 10.768717,106.651488, I send 10° 46' 7.3812",106° 39' 5.3568"). The conversion is just simple mathematics operation.
However, there was a problem with Java float and double precision, and that was a lot of distance when sending to Google Maps. Therefore I change my input data, convert data using C#'s decimal and my Android app just use it without manupilating anything. Here is the convesion (C#) code:
protected String convertDecimalToDegree(decimal pDecimal)
{
int degree = (int)Math.Floor(pDecimal);
pDecimal -= degree;
pDecimal *= 60;
int minute = (int)Math.Floor(pDecimal);
pDecimal -= minute;
pDecimal *= 60;
return degree + "° " + minute + "\' " + pDecimal + "\"";
}
Usage:
String[] coordinates = shop.MapCoordination.Split(',');
decimal n1 = Decimal.Parse(coordinates[0]);
decimal n2 = Decimal.Parse(coordinates[1]);
shop.MapCoordination = this.convertDecimalToDegree(n1) + "," + this.convertDecimalToDegree(n2);
I will mark this as answer for now, but I appreciate any solution without having to convert to this form.
You can use the following snippet to solve the problem
public String getCoordinates(String coordinates){
if(Locale.FRANCE == Locale.getDefault()){
Pattern p = Pattern.compile(",.*?(,)");
Matcher m = p.matcher(coordinates);
if (m.find( )) {
int index = m.end(); //gets the second comma position
String str1 = coordinates.substring(0,index-1);
String str2 = coordinates.substring(index,coordinates.length());
NumberFormat nf = NumberFormat.getInstance(Locale.FRANCE);
try {
str1 = nf.parse(str1).toString();
str2 = nf.parse(str2).toString();
} catch (ParseException e) {
e.printStackTrace();
}
return str1+","+str2;
}
}
return coordinates;
}
Updating the Google Maps app to the latest version (7.2.0) seems to fix the issue.
in Xamarin.Android:
using System.Globalization;
Android.Net.Uri.Parse("http://maps.google.com/maps?daddr=" + myLatitude.ToString(CultureInfo.InvariantCulture) + "," + myLongitude.ToString(CultureInfo.InvariantCulture)));