2

I want to validate a string input for a float value.

Is there a way to check if a float value is being truncated (precision is being lost) when parsing a String with a call to Float.parseFloat()?

For example, a user enters too many numbers in an EditText. I want to check when this happens so I can prevent it. I am not trying to get an exact result, I'm just trying to limit the value that a user can enter so it does not exceed the precision limit of a float.

float f = Float.parseFloat("9999999.999"); //this returns 1.0E7 but doesn't throw any error.

I understand that for accuracy I would be using BigDecimal rather a floating-point type. But floating-point accuracy is not my concern here. My concern is validation of the user’s textual input representing a float value.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
eXistenZ
  • 326
  • 4
  • 14
  • 2
    Float and Double types are not really good when you try to get exact results. Use instead BigDecimal Object. – Fabien MIFSUD Dec 04 '20 at 15:29
  • I'm not trying to get an exact result, I'm trying to limit the value that a user can enter so it does not exceed the limit of a float. Thanks – eXistenZ Dec 04 '20 at 15:32
  • Max Float Value is : 3.40282347 x 10^38 so 9999999.999 is a valid float value. – Fabien MIFSUD Dec 04 '20 at 15:42
  • @FabienMIFSUD Max float value says nothing about precision. – Federico klez Culloca Dec 04 '20 at 15:44
  • @eXistenZ you could convert to float, then back to string and see if it corresponds to the original string. Not perfect but better than nothing. – Federico klez Culloca Dec 04 '20 at 15:45
  • You try to see if there was a rounding done on your float ? – Fabien MIFSUD Dec 04 '20 at 15:46
  • Perhaps I was not clear with my question sorry, I'm looking to prevent precision loss. – eXistenZ Dec 04 '20 at 15:46
  • 1
    @eXistenZ you can't. Not with a float or a double. You're bound to have values that are not representable. Either you accept that or you use something like `BigDecimal` as Fabien suggested. – Federico klez Culloca Dec 04 '20 at 15:48
  • @Federico klez Culloca This is what I was thinking but when I convert to float it will add decimals if for example my value was just 999 and it will not be the same as the original string. – eXistenZ Dec 04 '20 at 15:48
  • @Federico klez Culloca By prevent I mean just check when the precision is lost. – eXistenZ Dec 04 '20 at 15:49
  • The problem is not just at `parseFloat`. `float f = 9999999.999F;` gives same result. – KunLun Dec 04 '20 at 15:50
  • I'm guessing that the precision is lost then as well. I just want to check if the precision of the float is lost. There must be a way to do this? – eXistenZ Dec 04 '20 at 15:58
  • @eXistenZ out of curiosity at this point, but is there a particular reason you absolutely **need** a float? And what do you plan to do after you decided the float inserted by the user has been rounded? – Federico klez Culloca Dec 04 '20 at 16:16
  • @Federico klez Culloca I call a function that requires a float and I want to load the exact same value in the EditBox when the user goes back to that screen so it will not be confusing to see a truncated number there. If I can check that the inserted float has been truncated/rounded I can use InputFilter and prevent the user from entering this number. – eXistenZ Dec 04 '20 at 16:33
  • I wonder if it might be as simple as testing if passing the string input and the `float` to `BigDecimal` constructors result in equal objects: `new BigDecimal( input ).equals( new BigDecimal( Float.parseFloat( input ) ) )` – Basil Bourque Dec 04 '20 at 17:31
  • @Basil Bourque I tried using that approach but they are never equal if you try "999.99" for example. Thanks – eXistenZ Dec 04 '20 at 17:46
  • @eXistenZ No, of course they won’t be equal because of the inherent [inaccuracy of floating-point technology](https://en.m.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems) that `BigDecimal` is built to avoid. Bad suggestion on my part. I should not wrangle math problems before I’ve had my coffee. – Basil Bourque Dec 04 '20 at 17:51
  • @Basil Bourque To be honest I wouldn't have known if I didn't test it out, don't worry about it, I've been scratching my head about this for a while now, I may have to come up with a different solution to solve my specific problem, but I think it's still an interesting question and I can see cases where this could be used, if it will ever be solved. – eXistenZ Dec 04 '20 at 17:56
  • 1
    @eXistenZ Agreed, this is a valid question: how to validate a string input for a `float` value. – Basil Bourque Dec 04 '20 at 20:28

2 Answers2

1

This is what I could come up with using BigDecimal, it seems to do the job just fine.

String input = "9999999.999";
boolean precisionLost = false;
try {
    BigDecimal bd = new BigDecimal(input);
    BigDecimal bd2 = new BigDecimal(String.valueOf(bd.floatValue()));
    if (bd.compareTo(bd2) != 0) {
        precisionLost = true;
    }
} catch (NumberFormatException e) { // when values are absurdly too large even for BigDecimal
    precisionLost = true;
}


input = "9999999.999"; // precisionLost is true
input = "9.999999"; // precisionLost is true
input = "9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999.999"; // exception is thrown, precisionLost is true
input = "999"; // precisionLost is false
input = "999.999"; // precisionLost is false
input = ".999"; // precisionLost is false
input = "999."; // precisionLost is false

I wonder if even something as simple as this would be enough? Any thoughts?

String input = "9999999.999";
boolean precisionLost = false;
try {
    float f = Float.parseFloat(input);
    if (String.valueOf(f).contains("E")) {
        precisionLost = true;
    }
} catch (NumberFormatException e) {
    precisionLost = true;
}
eXistenZ
  • 326
  • 4
  • 14
-1

If you insist on doing this check, you can try this function:

private static boolean isPrecise(String input) {
    float f = Float.parseFloat(input);
    
    String back = String.format(Locale.ENGLISH, "%f", f);
    System.out.println(back);

    if (back.length() > input.length())
        back = back.substring(0, input.length());

    return back.equals(input);
}

Note the use of String.format: this is to make sure that translation back is done with decimal notation, instead of scientific notation (i.e. 10000000.000000 instead of 1.0E7 you would get from String.valueOf)

However, this check is not really useful, other than satisfying curiosity.
That is why I added printing of back variable - if you run this on examples provided by Arvind Kumar Avinash, you will see that in some cases precision is lost, but digits are actually added instead of being truncated.

Specifically:

999.999 => 999.999023

That's why I added the substring part.

The real question is, what do you do with the answer of the function in your code?

Because of the way float point numbers work, you can't create a simple limitation on an EditText that will make sure user only enters valid floats.

And there is no way a reasonable person will be able to use an app that gives them error popups every time their input suffers precision loss.

Lev M.
  • 6,088
  • 1
  • 10
  • 23
  • You don't have to give them a popup error, you can just prevent them from entering the number using InputFilter and would solve the problem nicely. Do you let your users enter any value in the EditText and then when they come back to the screen they see a confusing truncated value (last entered value)? – eXistenZ Dec 04 '20 at 16:49
  • 2
    @eXistenZ What I do with the input depends on the context. When company I worked for developed cash register software, we avoided using `float` and `double` completely because legal regulation demanded the software to be precise. It handles money after all. So we used integer math, and only represented the numbers as having decimal parts. You can do whatever you want in your app, I just can't imagine your proposed input filter to be useful for a user who doesn't have a deep understanding of floating point math. I imagine such user being stuck trying to guess what number will pass... – Lev M. Dec 04 '20 at 16:55
  • @eXistenZ no, you use a datatype that can handle precise numbers. I understand you can't use one, but then you'll have to accept that what you're trying to do is not possible. – Federico klez Culloca Dec 04 '20 at 16:55
  • The point is to show the user what the maximum value would be and stop them from entering more numbers in, they don't have to understand what floats are, it's really simple and works fine this way. If I allow the user to enter any larger value they will never know that this value cannot be used or when they come back they will see a truncated number which will make things even more confusing. I understand that this may not be possible, but saying that this isn't useful in programming is just wrong. (By the way I didn't downvote this, would be nice to know why it was downvoted, whoever did it). – eXistenZ Dec 04 '20 at 17:01
  • 1
    @eXistenZ if that's what you need (no more than N digits) you can use the heuristics described [here](https://stackoverflow.com/questions/13542944/how-many-significant-digits-do-floats-and-doubles-have-in-java) but that's probably the best you can do. – Federico klez Culloca Dec 04 '20 at 17:07
  • @eXistenZ the problem I see is that floating point precision is not just about the amount of digits in a number, it is also about representation of certain fractions. For example, you can not precisely represent 1/3 or 2/3 with decimal notation, because the fraction part would run off forever. Because floats use powers of 2, not all decimal fractions can be precisely represented. But again - it all comes down to what your program is doing. Sometimes some lack of precision is OK, other times there are better solutions using different types. If this check works for you, good luck! – Lev M. Dec 04 '20 at 17:15
  • 1
    @eXistenZ just to make sure: do you want the example of 999.999 to be considered precise or not? My current code ignores added digits and considers the number precise as long as the original amount of digits matches user input. But it would be easy to change it so it only ignores trailing zeros instead. – Lev M. Dec 04 '20 at 17:26
  • @Lev M. That's what I was trying to say, that for my particular case precision is not important, it's only about entering a float value in an EditText and then showing the exact same float value when the user comes back to that screen, I appreciate your help, thanks! – eXistenZ Dec 04 '20 at 17:27
  • @Lev M. Yes "999.999" should be considered precise as it doesn't change after parseFloat() – eXistenZ Dec 04 '20 at 17:36
  • @Federico klez Culloca I'll check that out, thanks for your help! – eXistenZ Dec 04 '20 at 17:37