Precision lost in float value using java
Given below the test code and its output. When I get float value from number value, precision is lost.. Can anyone tell me why this behaviour and also how to handle this?
public static void main(String[] args) < try < java.lang.Number numberVal = 676543.21; float floatVal = numberVal.floatValue(); System.out.println("Number value : " + numberVal); System.out.println("float value : " + floatVal); System.out.println("Float.MAX_VALUE : " + Float.MAX_VALUE); System.out.println("Is floatVal >Float.MAX_VALUE ? " + ( floatVal > Float.MAX_VALUE)); >catch(Exception e) < e.printStackTrace(); >>
Number value : 676543.21 float value : 676543.2 Float.MAX_VALUE : 3.4028235E38 Is floatVal > Float.MAX_VALUE ? false
5 Answers 5
float s are usually good up to 6 significant digits. Your number is 676543.21 which has 8 significant digits. You will see errors past 6 digits, and those errors will easily propagate to more significant digits the more calculations you perform. If you value your sanity (or precision), use double s. Floats can’t even count past 10 million accurately.
Now, you have 2 significant digits, which suggests to me that there is a chance you want to represent currency — DO NOT . Use your own class that internally represents values using fixed-point arithmetic.
Now, as to Float.MAX_VAL which is 3.4028235E38, meaning 3.4028235*10^38 which is about 10^32 times larger than your value. Notice the ‘E’ in there? That’s the exponent.
I love these. But I’ll make it quick and painless.
Floats and decimals (aka floating points) aren’t kept in the memory precisely either. But I don’t want to get into float accuracy vs precision issues here, i’ll just point you to a link — http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
As far as your other question, lemme put it this way, since floats are stored in scientific notation.
floatVal = 6.765432E6 = 6.7 * 10^6 MAX_VALUE = 3.4E38 = 3.4 * 10^38
MAX_VALUE is 32 orders of magnitude bigger than your float number. Feel free to take a look at here as well http://steve.hollasch.net/cgindex/coding/ieeefloat.html
I’ve spent a great deal of time comparing and fixing some FP issues a few months ago.
Try to use a small delta when comparing floats. Maybe this link will help you http://introcs.cs.princeton.edu/java/91float/
All data types have representation limits so the fact there is a limit shouldn’t be surprising.
float uses 24-bit for its «mantissa» which holds all the significant digits. This means it has about 7 digits of precision (as 2^^24 is about 16 million)
double uses 53-bit for it «mantissa» so it can hold about 16 digits accurately.
3.4028235E38 is greater than 676543.2. Float.MAX_VALUE is the largest float that is possible to represent.
Decimal literals like the one you have typed default to type double , not float . java.lang.Number also uses doubles by default. If you were to replace that with java.lang.Number numberVal = 676543.21f; I would expect the same level of precision loss from both. Alternatively, replace float floatVal = numberVal.floatValue(); with double doubleVal = numberVal.doubleValue(); In order not to lose precision.
EDIT, as an example, try running:
Number num = 676543.21; System.out.println(num); //676543.21 System.out.println(num.doubleValue()); //676543.21 System.out.println(num.floatValue()); //676543.2
to see the difference in precision of the types
Float.MAX_VALUE returns the largest value that a float can ever hold. any higher will overflow. If you look carefully, you'll see an 'E' in its textual representation. This is standard index form, that 'E' means "multiply by 10 to the power of whatever number follows the E" (or in java-speak *pow(10, numberAfterE))
Округление чисел в Java
Числа с плавающей точкой (float, double) применяются при вычислении выражений, в которых требуется точность до десятичного знака. Высокая точность часто нужна в бухгалтерских и других вычислительных операциях. Но всегда ли нам нужен длинный “хвост” чисел после запятой? Может нам достаточно точности в три знака вещественной части? И есть нас такой вариант устраивает, как правильно выполнить округление? Именно об этом мы сегодня и поговорим: рассмотрим способы округления чисел в Java .
String format
double value = 34.766674; String result = String.format("%.3f",value); System.out.print(result);// 34,767
В результате мы отформатируем наше число с плавающей запятой 34766674 с точностью до 3 десятичных разрядов , так как в инструкции для форматирования мы указали три знака после запятой "%.3f. В свою очередь %f при форматировании строки обозначает тип чисел с плавающей запятой, которое включает в себя тип данных double и float в Java. В примере выше мы выводили полученное значение в консоль. Теперь вопрос: как бы можно было это сократить? Все просто: нужно использовать printf, который в свою очередь является format + print. В итоге предыдущий пример у нас сократился бы до:
double value = 34.766674; System.out.printf("%.3f",value);
У экземпляра out класса PrintStream помимо этого метода есть ещё метод format, который работает аналогично:
double value = 34.766674; System.out.format("%.3f",value);
Округление происходит по режиму HALF_UP — в сторону числа, которое ближе к обрезаемому (к 0 или 10). Если же эти числа равноудалены (в случае с 5), то округление выполняется в большую сторону. Пример:
String firstResult = String.format("%.3f",7.0004);// 7,000 String secondResult = String.format("%.3f",7.0005);// 7,001 String thirdResult = String.format("%.3f",7.0006);// 7,001
Более подробно режимы округления мы разберем чуть ниже.
DecimalFormat
Еще один вариант — использовать класс DecimalFormat. Он предназначен для форматирования любого числа в Java, будь это целое число или число с плавающей запятой. При создании экземпляра DecimalFormat мы можем передать ему строку форматирования. Она укажет, сколько десятичных разрядов нужно отформатировать для ввода. Как будет выглядеть наш пример с использованием DecimalFormat :
double value = 34.766674; DecimalFormat decimalFormat = new DecimalFormat( "#.###" ); String result = decimalFormat.format(value); System.out.print(result);//34,767
Строка #.### — это шаблон, который указывает, что мы форматируем передаваемое значение до 3 десятичных знаков. Чтобы изменить шаблон после создания объекта DecimalFormat, можно использовать его методы applyPattern и applyLocalizedPattern :
DecimalFormat decimalFormat = new DecimalFormat("#.###"); decimalFormat.applyPattern("#.#"); decimalFormat.applyLocalizedPattern("#.#####");
Но мы сегодня говорим про округление, не правда ли? При обрезании числа с знаками после запятой, выходящими за заданный шаблон, DecimalFormat округляет число в большую сторону, если последний обрезаемое число больше 5. Но что если число — 5? Получается, что оно ровно посередине между ближайшими целыми. Что тогда? В этом случае учитывается предыдущее число. Если оно чётное, округление производится:
DecimalFormat decimalFormat = new DecimalFormat("#.###"); String result = decimalFormat.format(7.4565); System.out.println((result));// 7,457
DecimalFormat decimalFormat = new DecimalFormat("#.###"); String result = decimalFormat.format(7.4575); System.out.println((result));// 7,457
Есть небольшая разница между форматированием чисел с плавающей запятой с использованием String.format() и DecimalFormat.format(). Первый всегда будет печатать конечные нули, даже если нет дробной части. К примеру:
String firstResult = String.format("%.3f", 7.000132); System.out.println((firstResult)); // 7.000 DecimalFormat decimalFormat = new DecimalFormat("#.###"); String secondResult = decimalFormat.format(7.000132); System.out.println((secondResult)); // 7
Как мы видим, при форматировании числа 7.000132 с точностью до трёх десятичных разрядов, метод format() у String выводит 7.000, в то время как у DecimalFormat метод format() выведет 7. То есть вы можете выбирать String.format() или DecimalFormat.format() в зависимости от того, нужны ли вам конечные нули или нет. Применяя вышеописанные способы, мы получали результат в виде строки. Давайте же рассмотрим способы, как можно получить обратно именно числовые значения.
Math
Нельзя не упомянуть специальный класс, заточенный под разные арифметические операции с числами — Math. В этом классе есть и методы для округления, но в отличие от уже описанных, они не позволяют задать определенное число знаков после запятой, а округляют до целого числа:
- Math.ceil() округляет до ближайшего целого числа вверх, но отдаёт не целочисленный тип, а double:
double value = 34.777774; double result = Math.ceil(value); System.out.println((result)); //35.0
Даже если у нас будет 34.0000000, все равно после использования Math.ceil мы получим 35,0. Math.floor() округляет до ближайшего целого вниз, также результатом отдаёт double:
double value = 34.777774; double result = Math.floor(value); System.out.println((result)); //34.0
double value = 34.777774; int result = Math.round(value); System.out.println((result)); //35
Если у нас число 34.5, округление выполняется до 35, если же чуть чуть меньше 34.499, число обрезается до 34. Чтобы не просто обрезать всю вещественную часть, а регулировать этот процесс до определенного количества знаков и при этом округлять, число умножают на 10^n (10 в степени n), где n равно количеству необходимых знаков после запятой. После этого применяют какой-нибудь метод класса Math для округления, ну а затем снова делят на 10^n:
double value = 34.777774; double scale = Math.pow(10, 3); double result = Math.ceil(value * scale) / scale; System.out.println((result)); //34.778
Округление с BigDecimal
BigDecimal — это класс, который позволяет работать с числами с плавающей запятой. В частности, его основной фишкой является то, что в нём можно хранить дробные числа произвольной длины (то есть нет ограничения в диапазоне числа). Помимо этого, этот класс хранит различные методы для арифметической обработки, включая округление. Класс данного объекта можно создать, задав в конструктор double, string отображение числа с плавающей запятой, double и MathContext и так далее. MathContext — комбинация правила округления RoundingMode и числа, описывающая общее количество цифр в искомом значении. Правила округления RoundingMode: DOWN — округление в сторону до нуля. UP — режим округления от нуля. CEILING — округление в сторону положительной бесконечности. FLOOR — округление в сторону к отрицательной бесконечности. HALF_UP — округление до «ближайшего соседа», если оба соседа не равноудалены (то есть когда округляемое число — 5). В этом случае выполняется округление вверх. HALF_DOWN — округление в сторону «ближайшего соседа». Если оба соседа не равноудалены, в этом случае округление вниз. HALF_EVEN — округление до «ближайшего соседа», если оба соседа не равноудалены. В этом случае округляются до четного соседа (как в описанном выше DecimalFormat). UNNECESSARY — используется для подтверждения того, что запрошенная операция имеет точный результат. Следовательно, округление не требуется. Пример:
MathContext context = new MathContext(5, RoundingMode.HALF_UP); double value = 34.777554; BigDecimal result = new BigDecimal(value, context); System.out.println(result); //34.778
Помимо возможности задавать правило округления в конструкторе, BigDecimal позволяет задать режим округления уже после создания экземпляра. Для этого используется метод setScale , в который нужно задать количество знаков после запятой и правила округления:
double value = 34.777554; BigDecimal result = new BigDecimal(value); result = result.setScale(3, RoundingMode.DOWN); System.out.println(result); //34.777
Также у BigDecimal есть внутренние int переменные, предназначенные для задания режима округления ( ROUND_DOWN , ROUND_CEILING , ROUND_FLOOR . ) Представляют собой правила округления, аналогичные представленным в классе RoundingMode и аналогично используются в setScale :
BigDecimal result = new BigDecimal(value); result = result.setScale(3, BigDecimal.ROUND_DOWN);