52

I need to extract the decimal part of a float number, but I get weird results:

float n = 22.65f;
// I want x = 0.65f, but...

x = n % 1; // x = 0.6499996

x = n - Math.floor(n); // x = 0.6499996185302734

x = n - (int)n; // x = 0.6499996

Why does this happen? Why do I get those values instead of 0.65?

Cristian
  • 198,401
  • 62
  • 356
  • 264
  • 1
    For anyone else confused by the fact that the description of the question seems to answer it, this whole thread is people explaining to the user that just because you put 22.65 as a literal, it doesn't mean the number can be stored in binary form. Floats don't have infinite precision, and if you just printed `n` you'd still get `6.4999...`. – TrisT Sep 12 '20 at 22:08

11 Answers11

46

float only has a few digit of precision so you should expect to see a round error fairly easily. try double this has more accuracy but still has rounding errors. You have to round any answer you get to have a sane output.

If this is not desireable you can use BigDecimal which does not have rounding errors, but has its own headaches IMHO.

EDIT: You may find this interesting. The default Float.toString() uses minimal rounding, but often its not enough.

System.out.println("With no rounding");
float n = 22.65f;
System.out.println("n= "+new BigDecimal(n));
float expected = 0.65f;
System.out.println("expected= "+new BigDecimal(expected));

System.out.println("n % 1= "+new BigDecimal(n % 1));
System.out.println("n - Math.floor(n) = "+new BigDecimal(n - Math.floor(n)));
System.out.println("n - (int)n= "+new BigDecimal(n - (int)n));

System.out.println("With rounding");
System.out.printf("n %% 1= %.2f%n", n % 1);
System.out.printf("n - Math.floor(n) = %.2f%n", n - Math.floor(n));
System.out.printf("n - (int)n= %.2f%n", n - (int)n);

Prints

With no rounding
n= 22.6499996185302734375
expected= 0.64999997615814208984375
n % 1= 0.6499996185302734375
n - Math.floor(n) = 0.6499996185302734375
n - (int)n= 0.6499996185302734375
With rounding
n % 1= 0.65
n - Math.floor(n) = 0.65
n - (int)n= 0.65
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • +1 Great stuff @Peter, thanks. Any idea if you are guaranteed that `n - Math.floor(n)` < 1.0? Any chance, because of floating point weirdness, that 1.99999999999... would give a >= 1? – Gray May 03 '13 at 19:09
  • 1
    @Gray You should only get weirdness if 1.9999999999999999999 is actually represented as 2. If you subtract the integer part of a floating point from a floating point, it shouldn't cause it to round up. It could expose the underlying representation error. By removing the integer part you are giving the result more precision than it needs to represent the value. Can you give an example of a value which gives weirdness? – Peter Lawrey May 04 '13 at 05:11
  • No, I can't @Peter. I was just writing some defensive code. I do so little with floating point numbers (luckily) and I was just wondering. Your explanation makes sense. Thanks. – Gray May 04 '13 at 13:14
26

I think this would be the most simple way :

float n = 22.65f;
float x = n - (int) n;
VinceStyling
  • 3,707
  • 3
  • 29
  • 44
7

Because not all rational numbers can be represented as a floating point number and 0.6499996... is the closest approximation for 0.65.

E.g., try printing first 20 digits of the number 0.65:

 System.out.printf("%.20f\n", 0.65f);

->

 0.64999997615814210000

edit
Rounding errors, which accumulate during computations, also play a part in it, as others noted.

Nikita Rybak
  • 67,365
  • 22
  • 157
  • 181
7

I bit long but works:

BigDecimal.valueOf(2.65d).divideAndRemainder(BigDecimal.ONE)[1].floatValue()
rodion
  • 14,729
  • 3
  • 53
  • 55
  • can anyone help, it was not immediately obvious to me). integerPart - BigDecimal.valueOf(weightInLB).divideAndRemainder(BigDecimal.ONE)[0].toInt() – Sergei S Mar 25 '21 at 11:11
5

The sound and perfect way to get decimal part of float and double data types, is using with String like this code:

float num=2.35f;
String s= new Float(num).toString();
String p=s.substring(s.indexOf('.')+1,s.length());
int decimal=Integer.parseInt(p);
Gholamali Irani
  • 4,391
  • 6
  • 28
  • 59
4

Try this. If timer is 10.65 then h ends up as the first two decimal places * 100 = 65.

This is a quick and easy way to separate what you want without the rounding issues.

float h = (int)((timer % 1) * 100);
Sven Rojek
  • 5,476
  • 2
  • 35
  • 57
Smorgon
  • 41
  • 1
3

If you just want to print the number to 2dp you can use DecimalFormat.

DecimalFormat df= new DecimalFormat("#.##");
System.out.println(df.format(f));

If you want fixed point numbers internally use BigDecimal

ashbyp
  • 326
  • 2
  • 14
2

Short answer: You can't represent some numbers exactly in binary that are "exact" in decimal.

Long answer: http://www-users.math.umd.edu/~jkolesar/mait613/floating_point_math.pdf

[Edit]

Also an interesting read: http://www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf

Landei
  • 54,104
  • 13
  • 100
  • 195
0

This code will work for any number of decimal digits.

float f = 2.3445f;
String s = Float.toString(f);
char[] c = s.toCharArray();
int length = s.length();
int flag = 0;
StringBuilder n = new StringBuilder();
for(int i = 0; i < length; i++)
{
    if(flag == 1)
    {
        n.append(c[i]);
    }
    if(c[i] == '.')
    {
        flag = 1;
    }
}
String result = n.toString();
int answer = Integer.parseInt(result);
System.out.println(answer);
Anand Kumar
  • 169
  • 1
  • 11
0

Try java.math.BigDecimal.

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
0

What about (Kotlin way):

val a = 1.1234
val dec = a - a.toInt()
val decimals = dec * 10f.pow(dec.toString().length - 2)

Take decimals and use/transform it as you want.

Lorenzo Vincenzi
  • 1,153
  • 1
  • 9
  • 26