Seprating the digits in the textfiled while entering the numbers (Javafx) - java

I created a simple javafx program. I want to seprate the digits three by three when I entering the digits in the textfiled. I used two solution which are given in the stackoverflow links(How to format text of TextField? JavaFX , Java 8 U40 TextFormatter (JavaFX) to restrict user input only for decimal number)
but none them are working for me. the first solution(set textformatter) was useless for me(or maybe I couldn't work with it in a right way) but the second one was working but only accept 4 digits and the other numbers that I enterd in the textfield are the in the same style that I enterd them without comma.
I want to seprate every three digits like this: 12,564,546,554
if anyone know the solution please help me to overcome this problem.
thanks.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.text.DecimalFormat;
import java.text.ParsePosition;
public class DelimiterExample extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField();
HBox hBox = new HBox();
//solution one
DecimalFormat format = new DecimalFormat( "#,###" );
textField.setTextFormatter( new TextFormatter<>(c ->
{
if ( c.getControlNewText().isEmpty() )
{
return c;
}
ParsePosition parsePosition = new ParsePosition( 0 );
Object object = format.parse( c.getControlNewText(), parsePosition );
if ( object == null || parsePosition.getIndex() < c.getControlNewText().length() )
{
return null;
}
else
{
return c;
}
}));
// solution two
textField.textProperty().addListener((obs , oldVal , newVal)-> {
if (newVal.matches("\\d*")) {
DecimalFormat formatter = new DecimalFormat("#,###");
String newvalstr = formatter.format(Float.parseFloat(newVal));
//System.out.println(newvalstr);
textField.setText(newvalstr);
}
});
hBox.getChildren().add(textField);
Scene scene = new Scene(hBox , 100 , 100);
primaryStage.setScene(scene);
primaryStage.show();
}
}

Any modification that is not a selection change can be fixed by modifying the chars before the range:
final char seperatorChar = ',';
final Pattern p = Pattern.compile("[0-9" + seperatorChar + "]*");
textField.setTextFormatter(new TextFormatter<>(c -> {
if (!c.isContentChange()) {
return c; // no need for modification, if only the selection changes
}
String newText = c.getControlNewText();
if (newText.isEmpty()) {
return c;
}
if (!p.matcher(newText).matches()) {
return null; // invalid change
}
// invert everything before the range
int suffixCount = c.getControlText().length() - c.getRangeEnd();
int digits = suffixCount - suffixCount / 4;
StringBuilder sb = new StringBuilder();
// insert seperator just before caret, if necessary
if (digits % 3 == 0 && digits > 0 && suffixCount % 4 != 0) {
sb.append(seperatorChar);
}
// add the rest of the digits in reversed order
for (int i = c.getRangeStart() + c.getText().length() - 1; i >= 0; i--) {
char letter = newText.charAt(i);
if (Character.isDigit(letter)) {
sb.append(letter);
digits++;
if (digits % 3 == 0) {
sb.append(seperatorChar);
}
}
}
// remove seperator char, if added as last char
if (digits % 3 == 0) {
sb.deleteCharAt(sb.length() - 1);
}
sb.reverse();
int length = sb.length();
// replace with modified text
c.setRange(0, c.getRangeEnd());
c.setText(sb.toString());
c.setCaretPosition(length);
c.setAnchor(length);
return c;
}));

Please guys don't reinvent the wheel. The class javafx.util.converter.NumberStringConverter do all the needed work for us:
TextField numberField = new TextField();
TextFormatter<Number> textFormatter = new TextFormatter<>(new NumberStringConverter());
numberField.setTextFormatter(textFormatter);
NumberStringConverter is your friend ;), if you want to listen the correct number changes on the textfield, simply use the next textFormatter property (forgive direct method invocations over the textField object):
textFormatter.valueProperty(); //Returns ObjectProperty<Number>
Then you can retrieve any kind of number subclass (number.intValue(), number.floatValue(), etc), and the ChangeListener only will be triggered when the user writes a valid numeric value.
If you want to manually set a number value to the textfield:
float value = 1000f; //or any type of Number subclass
textFormatter.setValue(value);
P.D: NumberStringConverter has a Locale attribute that can be changed to apply the appropriate format of the desired Country.

Thanks again #fabian.I stucked in this problem for two days. I also found that I can have the currency style that I want with this code.Now I have two perfect solution.
textField.setOnKeyTyped(event -> {
String typedCharacter = event.getCharacter();
event.consume();
if (typedCharacter.matches("\\d*")) {
String currentText =
textField.getText().replaceAll("\\.","").replace(",", "");
long longVal = Long.parseLong(currentText.concat(typedCharacter));
textField.setText(new DecimalFormat("#,##0").format(longVal));
}
});

Related

Java 8 Search ArrayList with Streams algorithm failing

We are using a Stream to search an ArrayList of strings the Dictionary file is sorted & contains 307107 words all in lower case
We are using the findFirst to look for a match from the text in a TextArea
As long as the word is misspelled beyond the 3 character the search has favoriable results
If the misspelled word is like this "Charriage" the results are nothing close to a match
The obvious goal is to get as close to correct without the need to look at an enormous number of words
Here is the text we are tesing
Tak acheive it hommaker and aparent as Chariage NOT ME Charriag add missing vowel to Cjarroage
We have made some major changes to the stream search filters with reasonable improvements
We will edit the posted code to include ONLY the part of the code where the search is failing
And below that the code changes made to the stream filters
Before the code change if the searchString had a misspelled char at position 1 no results were found in the dictionary the new search filters fixed that
We also added more search information by increasing the number of char for endsWith
So what is still failing! If the searchString(misspelled word) is missing a char at the end of the word and if the word has an incorrect char from position 1 to 4 the search fails
We are working on adding & removing char but we are not sure this is a workable solution
Comments or code will be greatly appreciated if you would like the complete project we will post on GitHub Just ask in the comments
The question is still how to fix this search filter when multiple char are missing from the misspelled word?
After multiple hours of searching for a FREE txt Dictionary this is one of the best
A side bar fact it has 115726 words that are > 5 in length and have a vowel at the end of the word. That means it has 252234 words with no vowel at the end
Does that mean we have a 32% chance of fixing the issue by adding a vowel to the end of the searchString? NOT a question just an odd fact!
HERE is a link to the dictionary download and place the words_alpha.txt file on C drive at C:/A_WORDS/words_alpha.txt");
words_alpha.txt
Code Before Changes
}if(found != true){
lvListView.setStyle("-fx-font-size:18.0;-fx-background-color: white;-fx-font-weight:bold;");
for(int indexSC = 0; indexSC < simpleArray.length;indexSC++){
String NewSS = txtMonitor.getText().toLowerCase();
if(NewSS.contains(" ")||(NewSS.matches("[%&/0-9]"))){
String NOT = txtMonitor.getText().toLowerCase();
txtTest.setText(NOT+" Not in Dictionary");
txaML.appendText(NOT+" Not in Dictionary");
onCheckSpelling();
return;
}
int a = NewSS.length();
int Z;
if(a == 0){// manage CR test with two CR's
Z = 0;
}else if(a == 3){
Z = 3;
}else if(a > 3 && a < 5){
Z = 4;
}else if(a >= 5 && a < 8){
Z = 4;
}else{
Z = 5;
}
System.out.println("!!!! NewSS "+NewSS+" a "+a+" ZZ "+Z);
if(Z == 0){// Manage CR in TextArea
noClose = true;
strSF = "AA";
String NOT = txtMonitor.getText().toLowerCase();
//txtTo.setText("Word NOT in Dictionary");// DO NO SEARCH
//txtTest.setText("Word NOT in Dictionaary");
txtTest.setText("Just a Space");
onCheckSpelling();
}else{
txtTest.setText("");
txaML.clear();
txtTest.setText("Word NOT in Dictionaary");
txaML.appendText("Word NOT in Dictionaary");
String strS = searchString.substring(0,Z).toLowerCase();
strSF = strS;
}
// array & list use in stream to add results to ComboBox
List<String> cs = Arrays.asList(simpleArray);
ArrayList<String> list = new ArrayList<>();
cs.stream().filter(s -> s.startsWith(strSF))
//.forEach(System.out::println);
.forEach(list :: add);
for(int X = 0; X < list.size();X++){
String A = (String) list.get(X);
Improved New Code
}if(found != true){
for(int indexSC = 0; indexSC < simpleArray.length;indexSC++){
String NewSS = txtMonitor.getText().toLowerCase();
if(NewSS.contains(" ")||(NewSS.matches("[%&/0-9]"))){
String NOT = txtMonitor.getText().toLowerCase();
txtTest.setText(NOT+" Not in Dictionary");
onCheckSpelling();
return;
}
int a = NewSS.length();
int Z;
if(a == 0){// manage CR test with two CR's
Z = 0;
}else if(a == 3){
Z = 3;
}else if(a > 3 && a < 5){
Z = 4;
}else if(a >= 5 && a < 8){
Z = 4;
}else{
Z = 5;
}
if(Z == 0){// Manage CR
noClose = true;
strSF = "AA";
String NOT = txtMonitor.getText().toLowerCase();
txtTest.setText("Just a Space");
onCheckSpelling();
}else{
txtTest.setText("");
txtTest.setText("Word NOT in Dictionaary");
String strS = searchString.substring(0,Z).toLowerCase();
strSF = strS;
}
ArrayList list = new ArrayList<>();
List<String> cs = Arrays.asList(simpleArray);
// array list & list used in stream foreach filter results added to ComboBox
// Code below provides variables for refined search
int W = txtMonitor.getText().length();
String nF = txtMonitor.getText().substring(0, 1).toLowerCase();
String nE = txtMonitor.getText().substring(W - 2, W);
if(W > 7){
nM = txtMonitor.getText().substring(W-5, W);
System.out.println("%%%%%%%% nE "+nE+" nF "+nF+" nM = "+nM);
}else{
nM = txtMonitor.getText().substring(W-1, W);
System.out.println("%%%%%%%% nE "+nE+" nF "+nF+" nM = "+nM);
}
cs.stream().filter(s -> s.startsWith(strSF)
|| s.startsWith(nF, 0)
&& s.length()<= W+2
&& s.endsWith(nE)
&& s.startsWith(nF)
&& s.contains(nM))
.forEach(list :: add);
for(int X = 0; X < list.size();X++){
String A = (String) list.get(X);
sort(list);
cboSelect.setStyle("-fx-font-weight:bold;-fx-font-size:18.0;");
cboSelect.getItems().add(A);
}// Add search results to cboSelect
break;
Here is a screen shot of the FXML file the controls are named the same as the names used in our code with the exception of the ComboBox
I am adding a JavaFX answer. This app uses Levenshtein Distance. You have to click on Check Spelling to start. You can select a word from the list to replace the current word being checked. I notice Levenshtein Distance returns lots of words so you might want to find other ways to reduce the list down even more.
Main
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application
{
public static void main(String[] args)
{
launch(args);
}
TextArea taWords = new TextArea("Tak Carrage thiss on hoemaker answe");
TextField tfCurrentWordBeingChecked = new TextField();
//TextField tfMisspelledWord = new TextField();
ListView<String> lvReplacementWords = new ListView();
TextField tfReplacementWord = new TextField();
Button btnCheckSpelling = new Button("Check Spelling");
Button btnReplaceWord = new Button("Replace Word");
List<String> wordList = new ArrayList();
List<String> returnList = new ArrayList();
HandleLevenshteinDistance handleLevenshteinDistance = new HandleLevenshteinDistance();
ObservableList<String> listViewData = FXCollections.observableArrayList();
#Override
public void start(Stage primaryStage)
{
setupListView();
handleBtnCheckSpelling();
handleBtnReplaceWord();
VBox root = new VBox(taWords, tfCurrentWordBeingChecked, lvReplacementWords, tfReplacementWord, btnCheckSpelling, btnReplaceWord);
root.setSpacing(5);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public void handleBtnCheckSpelling()
{
btnCheckSpelling.setOnAction(actionEvent -> {
if (btnCheckSpelling.getText().equals("Check Spelling")) {
wordList = new ArrayList(Arrays.asList(taWords.getText().split(" ")));
returnList = new ArrayList(Arrays.asList(taWords.getText().split(" ")));
loadWord();
btnCheckSpelling.setText("Check Next Word");
}
else if (btnCheckSpelling.getText().equals("Check Next Word")) {
loadWord();
}
});
}
public void handleBtnReplaceWord()
{
btnReplaceWord.setOnAction(actionEvent -> {
int indexOfWordToReplace = returnList.indexOf(tfCurrentWordBeingChecked.getText());
returnList.set(indexOfWordToReplace, tfReplacementWord.getText());
taWords.setText(String.join(" ", returnList));
btnCheckSpelling.fire();
});
}
public void setupListView()
{
lvReplacementWords.setItems(listViewData);
lvReplacementWords.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
tfReplacementWord.setText(newSelection);
});
}
private void loadWord()
{
if (wordList.size() > 0) {
tfCurrentWordBeingChecked.setText(wordList.get(0));
wordList.remove(0);
showPotentialCorrectSpellings();
}
}
private void showPotentialCorrectSpellings()
{
List<String> potentialCorrentSpellings = handleLevenshteinDistance.getPotentialCorretSpellings(tfCurrentWordBeingChecked.getText().trim());
listViewData.setAll(potentialCorrentSpellings);
}
}
CustomWord Class
/**
*
* #author blj0011
*/
public class CustomWord
{
private int distance;
private String word;
public CustomWord(int distance, String word)
{
this.distance = distance;
this.word = word;
}
public String getWord()
{
return word;
}
public void setWord(String word)
{
this.word = word;
}
public int getDistance()
{
return distance;
}
public void setDistance(int distance)
{
this.distance = distance;
}
#Override
public String toString()
{
return "CustomWord{" + "distance=" + distance + ", word=" + word + '}';
}
}
HandleLevenshteinDistance Class
/**
*
* #author blj0011
*/
public class HandleLevenshteinDistance
{
private List<String> dictionary = new ArrayList<>();
public HandleLevenshteinDistance()
{
try {
//Load DictionaryFrom file
//See if the dictionary file exists. If it don't download it from Github.
File file = new File("alpha.txt");
if (!file.exists()) {
FileUtils.copyURLToFile(
new URL("https://raw.githubusercontent.com/dwyl/english-words/master/words_alpha.txt"),
new File("alpha.txt"),
5000,
5000);
}
//Load file content to a List of Strings
dictionary = FileUtils.readLines(file, Charset.forName("UTF8"));
}
catch (IOException ex) {
ex.printStackTrace();
}
}
public List<String> getPotentialCorretSpellings(String misspelledWord)
{
LevenshteinDistance levenshteinDistance = new LevenshteinDistance();
List<CustomWord> customWords = new ArrayList();
dictionary.stream().forEach((wordInDictionary) -> {
int distance = levenshteinDistance.apply(misspelledWord, wordInDictionary);
if (distance <= 2) {
customWords.add(new CustomWord(distance, wordInDictionary));
}
});
Collections.sort(customWords, (CustomWord o1, CustomWord o2) -> o1.getDistance() - o2.getDistance());
List<String> returnList = new ArrayList();
customWords.forEach((item) -> {
System.out.println(item.getDistance() + " - " + item.getWord());
returnList.add(item.getWord());
});
return returnList;
}
}
You just needed to go a little further out into the Dictionary
We are sure you were getting a lot of suggested words from the Dictionary?
We tested your code and sometimes it found 3000 or more possible matches WOW
So here is the BIG improvement. It still needs a lot of testing we used this line for our tests with 100% favorable results.
Tske Charriage to hommaker and hommake as hommaer
Our fear is if the speller really butchers the word this improvement might solve that degree of misspelling
We are sure you know that if the first letter is wrong this will not work
Like zenophobe for xenophobe
Here is the BIG improvement tada
cs.stream().filter(s -> s.startsWith(strSF)
|| s.startsWith(nF, 0)
&& s.length() > 1 && s.length() <= W+3 // <== HERE
&& s.endsWith(nE)
&& s.startsWith(nF)
&& s.contains(nM))
.forEach(list :: add);
You can send the check to my address 55 48 196 195
This question is a possible duplicate: Search suggestion in strings
I think you should be using something similar to Levenshtein Distance or Jaro Winkler Distance. If you can use Apache's Commons. I would suggest using Apache Commons Lang. It has an implementation of Levenshtein Distance. The example demos this implementation. If you set the distance to (distance <= 2), you will potentially get more results.
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
/**
*
* #author blj0011
*/
public class Main
{
public static void main(String[] args)
{
try {
System.out.println("Hello World!");
File file = new File("alpha.txt");
if (!file.exists()) {
FileUtils.copyURLToFile(
new URL("https://raw.githubusercontent.com/dwyl/english-words/master/words_alpha.txt"),
new File("alpha.txt"),
5000,
5000);
}
List<String> lines = FileUtils.readLines(file, Charset.forName("UTF8"));
//lines.forEach(System.out::println);
lines.stream().forEach(line -> {
int distance = StringUtils.getLevenshteinDistance(line, "zorilta");
//System.out.println(line + ": " + distance);
if (distance <= 1) {
System.out.println("Did you mean: " + line);
}
});
}
catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Output distance <= 1
Building JavaTestingGround 1.0
------------------------------------------------------------------------
--- exec-maven-plugin:1.5.0:exec (default-cli) # JavaTestingGround ---
Hello World!
Did you mean: zorilla
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 1.329 s
Finished at: 2019-11-01T11:02:48-05:00
Final Memory: 7M/30M
Distance <= 2
Hello World!
Did you mean: corita
Did you mean: gorilla
Did you mean: zoril
Did you mean: zorilla
Did you mean: zorillas
Did you mean: zorille
Did you mean: zorillo
Did you mean: zorils
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 1.501 s
Finished at: 2019-11-01T14:03:33-05:00
Final Memory: 7M/34M
See the possible duplicate for more details about Levenshtein Distance.

I want make a textfield limit 3 number

I want let the user can only input 3 digit。
I try to use [0,9]{0,3} to make sure the input can not be more than 3 digit, however, the [0,9] part work, i can not input sth like abcd, but the {0,3}part doesn't work, i can input more than 3 number such as 123456
TextField textArea1 = new TextField();
textArea1.setTextFormatter(new TextFormatter<String>(new UnaryOperator<TextFormatter.Change>() {
#Override
public TextFormatter.Change apply(TextFormatter.Change change) {
String value = change.getText();
if(value.matches("[0-9]{0,3}"))
{
return change;
}
return null;
}
}));
Return the change if the change is zero or more digits and the control's text length is less than or equal to 3.
TextField textArea1 = new TextField();
UnaryOperator<Change> integerFilter = change -> {
System.out.println(change);
if (change.getText().matches("\\d*") && change.getControlNewText().length() <= 3) {
return change;
}
return null;
};
If you want to use Matches only, you need to use getControlNewText().
TextField textArea1 = new TextField();
UnaryOperator<Change> integerFilter = change -> {
System.out.println(change);
if (change.getControlNewText().matches("[0-9]{0,3}")) {
return change;
}
return null;
};

JavaFX IllegalArgumentException: The start must be <= the end

I am trying to format string in TextField which includes: remove all characters that are not numbers, format numbers using DecimalFormatter and limit number of character in the TextField:
private void IntegerInputChecker() {
ChangeListener<String> integerChecker = new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue,
String newValue) {
String pureValue = newValue;
String formatedText = newValue;
if (pureValue.length() <= 15) {
// Limit the characters in TextField
if (!newValue.matches("\\d*")) {
// Replace everything excepts number
pureValue = newValue.replaceAll("[^\\d]", "");
formatedText = pureValue;
}
if (pureValue.length() > 3) {
// Format number
DecimalFormat formatter = new DecimalFormat( "#,###" );
formatedText = formatter.format(Double.parseDouble(pureValue));
}
else if(pureValue.length() == 3) {
formatedText = pureValue.replaceAll(",", "");
}
}
else {
// Limit the characters in TextField
formatedText = formatedText.substring(0, formatedText.length() - 1);
}
fieldPrice.setText(formatedText);
}
};
fieldPrice.textProperty().addListener(integerChecker);
}
Everything works fine when I type in number but when I try to delete them in order, JavaFX throws an exception (the program still works fine):
java.lang.IllegalArgumentException: The start must be <= the end
For example, If I have 123,456,789 when I delete the number to 123,456 then it throws the above exception.
Thanks in advance.

How can I verify that a swt text widget is a decimal value

In swt, text widget allow any string.
But What is the most appropriate SWT widget in which to enter a Decimal value?
I found two answers :
First, implement the VerifyKeyListener and VerifyListener, work for french decimal notation, but simple and easy to implement :
package test.actions;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.widgets.Text;
public final class AmountVerifyKeyListener implements VerifyListener, VerifyKeyListener {
private static final String REGEX = "^[-+]?[0-9]*[,]?[0-9]{0,2}+$";
private static final Pattern pattern = Pattern.compile(REGEX);
public void verifyText(VerifyEvent verifyevent) {
verify(verifyevent);
}
public void verifyKey(VerifyEvent verifyevent) {
verify(verifyevent);
}
private void verify (VerifyEvent e) {
String string = e.text;
char[] chars = new char[string.length()];
string.getChars(0, chars.length, chars, 0);
Text text = (Text)e.getSource();
if ( ( ",".equals(string) || ".".equals(string) ) && text.getText().indexOf(',') >= 0 ) {
e.doit = false;
return;
}
for (int i = 0; i < chars.length; i++) {
if (!(('0' <= chars[i] && chars[i] <= '9') || chars[i] == '.' || chars[i] == ',' || chars[i] == '-')) {
e.doit = false;
return;
}
if ( chars[i] == '.' ) {
chars[i] = ',';
}
}
e.text = new String(chars);
final String oldS = text.getText();
String newS = oldS.substring(0, e.start) + e.text + oldS.substring(e.end);
Matcher matcher = pattern.matcher(newS);
if ( !matcher.matches() ) {
e.doit = false;
return;
}
}
}
And the main class associated to the verifyKeyListener :
package test.actions;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
public class TestMain {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout(2, false));
final Text text = new Text(shell, SWT.NONE);
text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
text.addVerifyListener(new AmountVerifyKeyListener() ) ;
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
display.dispose();
}
}
Use the FormattedText from the nebula project : http://eclipse.org/nebula/
Somebody see another solution ?
A cleaner and easier way would be to use Double.parseString() and catch the NumberFormatException's to figure out if the text in it can be converted to a double.
For the validation of Double values you have to consider that characters like E may be included in Doubles and that partial input like "4e-" should be valid while typing to allow to finally enter "4e-3". The partial expressions might give a NumberFormatException for Double.parseDouble(partialExpression)
Also see my answer at following related question:
How to set a Mask to a SWT Text to only allow Decimals
(Further note: If one only wants to allow Integer values a Spinner can be used instead of a text field.)
Why not a simple regex on the widget like the following:
if(Text.getText().matches("^[0-9]+$")) {
show an error dialog or similar
}

Safe String to BigDecimal conversion

I'm trying to read some BigDecimal values from the string. Let's say I have this String: "1,000,000,000.999999999999999" and I want to get a BigDecimal out of it. What is the way to do it?
First of all, I don't like the solutions using string replaces (replacing commas etc.). I think there should be some neat formatter to do that job for me.
I've found a DecimalFormatter class, however as it operates through double - huge amounts of precision are lost.
So, how can I do it?
Check out setParseBigDecimal in DecimalFormat. With this setter, parse will return a BigDecimal for you.
String value = "1,000,000,000.999999999999999";
BigDecimal money = new BigDecimal(value.replaceAll(",", ""));
System.out.println(money);
Full code to prove that no NumberFormatException is thrown:
import java.math.BigDecimal;
public class Tester {
public static void main(String[] args) {
// TODO Auto-generated method stub
String value = "1,000,000,000.999999999999999";
BigDecimal money = new BigDecimal(value.replaceAll(",", ""));
System.out.println(money);
}
}
Output
1000000000.999999999999999
The following sample code works well (locale need to be obtained dynamically)
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import java.util.Locale;
class TestBigDecimal {
public static void main(String[] args) {
String str = "0,00";
Locale in_ID = new Locale("in","ID");
//Locale in_ID = new Locale("en","US");
DecimalFormat nf = (DecimalFormat)NumberFormat.getInstance(in_ID);
nf.setParseBigDecimal(true);
BigDecimal bd = (BigDecimal)nf.parse(str, new ParsePosition(0));
System.out.println("bd value : " + bd);
}
}
The code could be cleaner, but this seems to do the trick for different locales.
import java.math.BigDecimal;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
public class Main
{
public static void main(String[] args)
{
final BigDecimal numberA;
final BigDecimal numberB;
numberA = stringToBigDecimal("1,000,000,000.999999999999999", Locale.CANADA);
numberB = stringToBigDecimal("1.000.000.000,999999999999999", Locale.GERMANY);
System.out.println(numberA);
System.out.println(numberB);
}
private static BigDecimal stringToBigDecimal(final String formattedString,
final Locale locale)
{
final DecimalFormatSymbols symbols;
final char groupSeparatorChar;
final String groupSeparator;
final char decimalSeparatorChar;
final String decimalSeparator;
String fixedString;
final BigDecimal number;
symbols = new DecimalFormatSymbols(locale);
groupSeparatorChar = symbols.getGroupingSeparator();
decimalSeparatorChar = symbols.getDecimalSeparator();
if(groupSeparatorChar == '.')
{
groupSeparator = "\\" + groupSeparatorChar;
}
else
{
groupSeparator = Character.toString(groupSeparatorChar);
}
if(decimalSeparatorChar == '.')
{
decimalSeparator = "\\" + decimalSeparatorChar;
}
else
{
decimalSeparator = Character.toString(decimalSeparatorChar);
}
fixedString = formattedString.replaceAll(groupSeparator , "");
fixedString = fixedString.replaceAll(decimalSeparator , ".");
number = new BigDecimal(fixedString);
return (number);
}
}
I needed a solution to convert a String to a BigDecimal without knowing the locale and being locale-independent. I couldn't find any standard solution for this problem so i wrote my own helper method. May be it helps anybody else too:
Update: Warning! This helper method works only for decimal numbers, so numbers which always have a decimal point! Otherwise the helper method could deliver a wrong result for numbers between 1000 and 999999 (plus/minus). Thanks to bezmax for his great input!
static final String EMPTY = "";
static final String POINT = '.';
static final String COMMA = ',';
static final String POINT_AS_STRING = ".";
static final String COMMA_AS_STRING = ",";
/**
* Converts a String to a BigDecimal.
* if there is more than 1 '.', the points are interpreted as thousand-separator and will be removed for conversion
* if there is more than 1 ',', the commas are interpreted as thousand-separator and will be removed for conversion
* the last '.' or ',' will be interpreted as the separator for the decimal places
* () or - in front or in the end will be interpreted as negative number
*
* #param value
* #return The BigDecimal expression of the given string
*/
public static BigDecimal toBigDecimal(final String value) {
if (value != null){
boolean negativeNumber = false;
if (value.containts("(") && value.contains(")"))
negativeNumber = true;
if (value.endsWith("-") || value.startsWith("-"))
negativeNumber = true;
String parsedValue = value.replaceAll("[^0-9\\,\\.]", EMPTY);
if (negativeNumber)
parsedValue = "-" + parsedValue;
int lastPointPosition = parsedValue.lastIndexOf(POINT);
int lastCommaPosition = parsedValue.lastIndexOf(COMMA);
//handle '1423' case, just a simple number
if (lastPointPosition == -1 && lastCommaPosition == -1)
return new BigDecimal(parsedValue);
//handle '45.3' and '4.550.000' case, only points are in the given String
if (lastPointPosition > -1 && lastCommaPosition == -1){
int firstPointPosition = parsedValue.indexOf(POINT);
if (firstPointPosition != lastPointPosition)
return new BigDecimal(parsedValue.replace(POINT_AS_STRING, EMPTY));
else
return new BigDecimal(parsedValue);
}
//handle '45,3' and '4,550,000' case, only commas are in the given String
if (lastPointPosition == -1 && lastCommaPosition > -1){
int firstCommaPosition = parsedValue.indexOf(COMMA);
if (firstCommaPosition != lastCommaPosition)
return new BigDecimal(parsedValue.replace(COMMA_AS_STRING, EMPTY));
else
return new BigDecimal(parsedValue.replace(COMMA, POINT));
}
//handle '2.345,04' case, points are in front of commas
if (lastPointPosition < lastCommaPosition){
parsedValue = parsedValue.replace(POINT_AS_STRING, EMPTY);
return new BigDecimal(parsedValue.replace(COMMA, POINT));
}
//handle '2,345.04' case, commas are in front of points
if (lastCommaPosition < lastPointPosition){
parsedValue = parsedValue.replace(COMMA_AS_STRING, EMPTY);
return new BigDecimal(parsedValue);
}
throw new NumberFormatException("Unexpected number format. Cannot convert '" + value + "' to BigDecimal.");
}
return null;
}
Of course i've tested the method:
#Test(dataProvider = "testBigDecimals")
public void toBigDecimal_defaultLocaleTest(String stringValue, BigDecimal bigDecimalValue){
BigDecimal convertedBigDecimal = DecimalHelper.toBigDecimal(stringValue);
Assert.assertEquals(convertedBigDecimal, bigDecimalValue);
}
#DataProvider(name = "testBigDecimals")
public static Object[][] bigDecimalConvertionTestValues() {
return new Object[][] {
{"5", new BigDecimal(5)},
{"5,3", new BigDecimal("5.3")},
{"5.3", new BigDecimal("5.3")},
{"5.000,3", new BigDecimal("5000.3")},
{"5.000.000,3", new BigDecimal("5000000.3")},
{"5.000.000", new BigDecimal("5000000")},
{"5,000.3", new BigDecimal("5000.3")},
{"5,000,000.3", new BigDecimal("5000000.3")},
{"5,000,000", new BigDecimal("5000000")},
{"+5", new BigDecimal("5")},
{"+5,3", new BigDecimal("5.3")},
{"+5.3", new BigDecimal("5.3")},
{"+5.000,3", new BigDecimal("5000.3")},
{"+5.000.000,3", new BigDecimal("5000000.3")},
{"+5.000.000", new BigDecimal("5000000")},
{"+5,000.3", new BigDecimal("5000.3")},
{"+5,000,000.3", new BigDecimal("5000000.3")},
{"+5,000,000", new BigDecimal("5000000")},
{"-5", new BigDecimal("-5")},
{"-5,3", new BigDecimal("-5.3")},
{"-5.3", new BigDecimal("-5.3")},
{"-5.000,3", new BigDecimal("-5000.3")},
{"-5.000.000,3", new BigDecimal("-5000000.3")},
{"-5.000.000", new BigDecimal("-5000000")},
{"-5,000.3", new BigDecimal("-5000.3")},
{"-5,000,000.3", new BigDecimal("-5000000.3")},
{"-5,000,000", new BigDecimal("-5000000")},
{null, null}
};
}
Here is how I would do it:
public String cleanDecimalString(String input, boolean americanFormat) {
if (americanFormat)
return input.replaceAll(",", "");
else
return input.replaceAll(".", "");
}
Obviously, if this were going in production code, it wouldn't be that simple.
I see no issue with simply removing the commas from the String.
resultString = subjectString.replaceAll("[^.\\d]", "");
will remove all characters except digits and the dot from your string.
To make it locale-aware, you might want to use getDecimalSeparator() from java.text.DecimalFormatSymbols. I don't know Java, but it might look like this:
sep = getDecimalSeparator()
resultString = subjectString.replaceAll("[^"+sep+"\\d]", "");
Old topic but maybe the easiest is to use Apache commons NumberUtils which has a method createBigDecimal (String value)....
I guess (hope) it takes locales into account or else it would be rather useless.
Please try this its working for me
BigDecimal bd ;
String value = "2000.00";
bd = new BigDecimal(value);
BigDecimal currency = bd;

Categories

Resources