Как получить десятичную часть числа с плавающей запятой?

Мне нужно извлечь десятичную часть числа с плавающей запятой, но я получаю странные результаты:

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

Почему это происходит? Почему я получаю эти значения вместо 0.65?


person Cristian    schedule 16.02.2011    source источник
comment
Для всех, кого еще смущает тот факт, что описание вопроса, кажется, отвечает на него, вся эта ветка - это люди, объясняющие пользователю, что только потому, что вы указали 22,65 как литерал, это не означает, что число может быть сохранено в двоичной форме . У чисел с плавающей запятой нет бесконечной точности, и если вы просто напечатаете n, вы все равно получите 6.4999....   -  person TrisT    schedule 13.09.2020


Ответы (10)


float имеет точность всего в несколько цифр, поэтому вы должны ожидать довольно легко увидеть круглую ошибку. попробуйте double, это имеет большую точность, но все еще имеет ошибки округления. Вы должны округлить любой полученный ответ, чтобы получить разумный результат.

Если это нежелательно, вы можете использовать BigDecimal, который не имеет ошибок округления, но имеет свои головные боли IMHO.

РЕДАКТИРОВАТЬ: Вы можете найти это интересным. Float.toString() по умолчанию использует минимальное округление, но часто этого недостаточно.

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);

Отпечатки

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
person Peter Lawrey    schedule 16.02.2011
comment
+1 Отличный материал @Peter, спасибо. Любая идея, если вам гарантировано, что n - Math.floor(n) ‹ 1.0? Есть ли шанс, что из-за странностей с плавающей запятой 1.99999999999... даст = 1? - person Gray; 03.05.2013
comment
@Gray Вы должны получить странность только в том случае, если 1.99999999999999999999 на самом деле представлено как 2. Если вы вычитаете целую часть числа с плавающей запятой из числа с плавающей запятой, это не должно приводить к округлению. Это может выявить основную ошибку представления. Удаляя целую часть, вы даете результату большую точность, чем это необходимо для представления значения. Можете ли вы привести пример значения, которое дает странность? - person Peter Lawrey; 04.05.2013
comment
Нет, я не могу, @Peter. Я просто писал защитный код. Я так мало работаю с числами с плавающей запятой (к счастью), и мне просто интересно. Ваше объяснение имеет смысл. Спасибо. - person Gray; 04.05.2013

Я думаю, что это будет самый простой способ:

float n = 22.65f;
float x = n - (int) n;
person VinceStyling    schedule 29.07.2014
comment
Это приведет к ошибкам округления try: float a = 22.3f; a - (int)(a) Результат будет 0,29999924 вместо 0,3 - person Prateek Batla; 15.06.2016

Я немного длинный, но работает:

BigDecimal.valueOf(2.65d).divideAndRemainder(BigDecimal.ONE)[1].floatValue()
person rodion    schedule 16.02.2011
comment
может кто поможет, мне это было не сразу очевидно). integerPart - BigDecimal.valueOf(weightInLB).divideAndRemainder(BigDecimal.ONE)[0].toInt() - person Evansgelist; 25.03.2021

Потому что не все рациональные числа могут быть представлены в виде числа с плавающей запятой, а 0.6499996... является ближайшим приближением для 0.65.

Например, попробуйте напечатать первые 20 цифр числа 0.65:

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

->

 0.64999997615814210000

edit
Ошибки округления, которые накапливаются во время вычислений, также играют в этом роль, как отмечали другие.

person Nikita Rybak    schedule 16.02.2011

Если вы просто хотите напечатать число в 2dp, вы можете использовать DecimalFormat.

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

Если вам нужны числа с фиксированной точкой внутри, используйте BigDecimal

person ashbyp    schedule 16.02.2011

Попробуй это. Если значение timer равно 10,65, то h заканчивается как первые два десятичных разряда * 100 = 65.

Это быстрый и простой способ отделить то, что вы хотите, без проблем с округлением.

float h = (int)((timer % 1) * 100);
person Smorgon    schedule 10.09.2014
comment
@SvenRojek Не используйте форматирование кода для текста, который не является кодом. - person user207421; 31.12.2016
comment
Для 50.01 иногда выдается 0 из-за использования типа данных float. - person Sudhanshu Gaur; 24.02.2018

Краткий ответ: вы не можете точно представить некоторые числа в двоичном формате, которые являются «точными» в десятичном виде.

Длинный ответ: http://www-users.math.umd.edu/~jkolesar/mait613/floating_point_math.pdf

[Изменить]

Также интересно почитать: http://www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf

person Landei    schedule 16.02.2011
comment
Добавил еще одно интересное :) - person Landei; 16.02.2011

Надежный и идеальный способ получить десятичную часть типов данных с плавающей запятой и двойного числа - использовать со строкой такой код:

float num=2.35f;
String s= new Float(num).toString();
String p=s.substring(s.indexOf('.')+1,s.length());
int decimal=Integer.parseInt(p);
person Gholamali-Irani    schedule 18.08.2016

Попробуйте java.math.BigDecimal.

person Bozho    schedule 16.02.2011

Этот код будет работать для любого количества десятичных цифр.

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);
person Anand Kumar    schedule 31.12.2016