Мне нужен метод преобразования числа с двойной точностью в строку, которая округляется с использованием метода половинного увеличения, т.е. если десятичное число, которое нужно округлить, равно 5, оно всегда округляется до следующего числа. Это стандартный метод округления, на который рассчитывает большинство людей в большинстве ситуаций.
Я также хотел бы, чтобы отображались только значащие цифры - т.е. не должно быть конечных нулей.
Я знаю, что один из способов сделать это - использовать метод String.format:
String.format("%.5g%n", 0.912385);
возвращает:
0.91239
что здорово, однако он всегда отображает числа с 5 десятичными знаками, даже если они не имеют значения:
String.format("%.5g%n", 0.912300);
возвращает:
0.91230
Другой способ - использовать DecimalFormatter:
DecimalFormat df = new DecimalFormat("#.#####");
df.format(0.912385);
возвращает:
0.91238
Однако, как вы можете видеть, здесь используется округление наполовину. То есть округление в меньшую сторону, если предыдущая цифра четная. Я бы хотел вот что:
0.912385 -> 0.91239
0.912300 -> 0.9123
Как лучше всего добиться этого на Java?




Предполагая, что value - это double, вы можете:
(double)Math.round(value * 100000d) / 100000d
Это для 5-значной точности. Количество нулей указывает количество десятичных знаков.
Я думаю, что это лучше, чем использование DecimalFormat. DecimalFormat будет неявно выделять различные объекты, а также String при каждом вызове decimalFormat.Format (). Я считаю, что лучше привести несколько примитивов, чем выделять объекты.
ОБНОВЛЕНИЕ: я только что подтвердил, что делать это НАМНОГО быстрее, чем использовать DecimalFormat. Я использовал DecimalFormat 200 раз, и этот метод. DecimalFormat занял 14 мсек для завершения 200 циклов, этот метод занял менее 1 мс. Как я и подозревал, это быстрее. Если вам платят по тактовому циклу, это то, что вам следует делать. Я удивлен, что Крис Кадмор даже сказал то, что он сказал, если честно. размещение объектов всегда дороже, чем приведение примитивов и использование статических методов (Math.round () в отличие от decimalFormat.format ()).
@Andi Jay Я не говорю, что вы ошибаетесь или правы, но вы не можете сказать, что изолированный тест на одной машине подтверждает вашу теорию.
Этот метод не работает более чем в 90% случаев. -1.
Действительно, это не удается: Math.round(0.1 * Math.pow(10,20))/Math.pow(10,20) == 0.09223372036854775.
Я думаю, умножение будет быстрее деления, поэтому (double)Math.round(value * 100000) * 0.000001
Я использовал этот метод долгое время, но теперь понял, что это не выход
@ RobertTupelo-Schneck Это не работает, потому что double имеет точность только примерно 14 знаков после запятой (что-либо большее, чем это, в любом случае будет неточным)
Будьте очень осторожны при использовании этого метода (или любого округления чисел с плавающей запятой). Он терпит неудачу для чего-то столь же простого, как 265.335. Промежуточный результат 265,335 * 100 (точность 2 цифры) равен 26533,499999999996. Это означает, что он округляется до 265,33. При преобразовании чисел с плавающей запятой в вещественные десятичные числа просто возникают проблемы. См. Ответ EJP здесь, на stackoverflow.com/a/12684082/144578
У @ChrisCudmore есть лучший ответ здесь прокрутите вниз, также Math.pow (10,20) слишком велик, если вам нужно 20 десятичных знаков точности, то double не для вас.
Это не работает и с очень маленькими значениями: (double)Math.round(1.005 * 100) / 100 == 1.0 Не используйте его!
1.005 == 1.00 и 265.335 == 265.33 -> ну и что! это нормально. есть преобразования с плавающей запятой, переходящие в раунд, есть преобразования с плавающей запятой, переходящие в деление, есть преобразования с плавающей запятой, переходящие в 100.0, вам просто нужно принять, что с плавающей запятой является FLOATING POINT ... это приближение! десятичных знаков. double с плавающей запятой быстро !!! конечно, вы теряете некоторую точность, но кого это волнует .. это супер быстро!
конечно, вы теряете некоторую яркую точность ROUND_EVEN / ROUND_UP в этих мизерных случаях, но кого это волнует ... это супер быстро! вот почему программисты в первую очередь используют double. вы все равно округляете эти дополнения до конца, поэтому они, очевидно, не имеют значения для вас или ваших пользователей. в противном случае вы бы вообще не использовали двойное число (то есть с плавающей запятой).
@AndiJay: «Если вам платят по тактовому циклу, это то, что вы должны делать» - я не согласен. Если бы мне платили за такт, я бы использовал DecimalFormat, чтобы сделать мой код как можно медленнее: чем больше тактовых циклов он требует, тем больше для меня денег;)
для скорости я преобразовал в int до того, как 5 долларов превратились в 500. Сверхбыстрая математика, всегда округление до 2 десятичных знаков, а для отображения требуется только деление на 100.
@SebastiaanvandenBroek: Ух ты, я никогда не знал, что получить неправильный ответ так легко. Однако, если кто-то работает с неточными числами, нужно признать, что любое значение не совсем. 265.335 на самом деле означает 265.335 += tolerance, где допуск зависит от предыдущих операций и диапазона входных значений. Мы не знаем истинной, точной стоимости. При граничных значениях ответ либо, вероятно, правильный. Если быть точным, работать вдвоем не стоит. fail здесь не в двойном преобразовании. В OP думают, что он может полагаться на входящий 265.335 как на это.
Не думаю, что отливка (double) необходима.
@ToolmakerSteve Если исходное число состоит из трех десятичных знаков, а вам нужно только два, это дает вам правильный (округленный HALF_EVEN) ответ: x = Math.rint(1000*x); x = Math.rint(x/10.0); return x / 100.0;. Очевидно, что результат не совсем 365.34, но это самое близкое приближение, и он распечатывается следующим образом.
Этот метод никогда не будет работать надежно. Причина в том, что результатом является не строка, а значение типа double, внутренне имеющее двоичное представление. Но результат, скорее всего, не будет представлен в виде двоичного числа.
double myNum = .912385;
int precision = 10000; //keep 4 digits
myNum= Math.floor(myNum * precision +.5)/precision;
да, это именно то, что math.round делает для положительных чисел, но пробовали ли вы это с отрицательными числами? люди используют math.round в других решениях, чтобы также охватить случай отрицательных чисел.
Примечание: Math.floor(x + 0.5) и Math.round(x)
Используйте setRoundingMode, установите RoundingMode явно для обработки вашей проблемы с половинным четным раундом, а затем используйте шаблон формата для требуемого вывода.
Пример:
DecimalFormat df = new DecimalFormat("#.####");
df.setRoundingMode(RoundingMode.CEILING);
for (Number n : Arrays.asList(12, 123.12345, 0.23, 0.1, 2341234.212431324)) {
Double d = n.doubleValue();
System.out.println(df.format(d));
}
дает результат:
12
123.1235
0.23
0.1
2341234.2125
РЕДАКТИРОВАТЬ: исходный ответ не касается точности значений типа double. Это нормально, если вам все равно, округляется оно в большую или меньшую сторону. Но если вам нужно точное округление, вам нужно принять во внимание ожидаемую точность значений. Значения с плавающей запятой имеют внутреннее двоичное представление. Это означает, что такое значение, как 2,7735, на самом деле не имеет такого точного значения внутри. Он может быть чуть больше или чуть меньше. Если внутреннее значение немного меньше, оно не будет округлено до 2,7740. Чтобы исправить эту ситуацию, вам необходимо знать о точности значений, с которыми вы работаете, и добавлять или вычитать это значение перед округлением. Например, если вы знаете, что ваши значения являются точными до 6 цифр, то для округления половинных значений в большую сторону добавьте эту точность к значению:
Double d = n.doubleValue() + 1e-6;
Чтобы округлить, вычтите точность.
Это, наверное, лучшее решение из представленных на данный момент. Причина, по которой я не заметил эту возможность, когда впервые посмотрел на класс DecimalFormat, заключается в том, что она была представлена только в Java 1.6. К сожалению, я ограничен использованием 1.5, но об этом будет полезно знать в будущем.
Не работает с десятичным форматом экспоненты, например с форматом ("0.0E00"). В этом примере 0,0155 округляется до 0,0E00 вместо 1,6E-02.
Я пробовал это с: "#.##", округляя HALF_UP. 256.335f -> "256.33" ... (пример взят из комментариев к ответу @ asterite).
Будьте осторожны, так как DecimalFormat зависит от вашей текущей локальной конфигурации, вы не можете получить точку в качестве разделителя. Я лично предпочитаю ответ Астерита ниже
Также имейте в виду, что не следует ожидать, что DecimalFormat будет потокобезопасным. Согласно Документы Java: Десятичные форматы обычно не синхронизируются. Рекомендуется создавать отдельные экземпляры формата для каждого потока. Если несколько потоков обращаются к формату одновременно, он должен быть синхронизирован извне.
как мне сделать так, чтобы он правильно округлял, чтобы он не округлял 0,0004 до 0,001
Невозможно заставить это работать с HALF_UP: пример DecimalFormat df = new DecimalFormat("0.00"); df.setMaximumFractionDigits(2); df.setRoundingMode(RoundingMode.HALF_UP); df.format(3.775) возвращает 3,77
Нетрудно создать случаи, когда это просто не работает. Я бы не стал использовать этот метод, если важно получить правильный результат.
@ThorstenNiehues Тот факт, что вы указали его как 3.775, не означает, что он имеет это значение внутри. Внутренне он имеет двоичное значение, и 3,775 не может быть представлен в точности как двоичное значение. Таким образом, он может иметь внутреннее значение, соответствующее 3,77499999999. При округлении всегда добавляйте ожидаемую точность значения.
Вы также можете использовать
DecimalFormat df = new DecimalFormat("#.00000");
df.format(0.912385);
чтобы убедиться, что у вас есть завершающие 0.
Я считаю, что одна из целей вопроса заключалась в том, чтобы «в нет должны быть какие-либо конечные нули».
В этом вопросе оператору не нужны нули, но это именно то, что я хотел. Если у вас есть список чисел с 3 десятичными знаками, вы хотите, чтобы все они имели одинаковые цифры, даже если это 0.
Вы забыли указать RoundingMode.
@IgorGanapolsky по умолчанию Decimal mode использует RoundingMode.HALF_EVEN.
new BigDecimal(String.valueOf(double)).setScale(yourScale, BigDecimal.ROUND_HALF_UP);
даст вам BigDecimal. Чтобы получить строку, просто вызовите метод BigDecimaltoString или метод toPlainString для Java 5+ для строки в простом формате.
Пример программы:
package trials;
import java.math.BigDecimal;
public class Trials {
public static void main(String[] args) {
int yourScale = 10;
System.out.println(BigDecimal.valueOf(0.42344534534553453453-0.42324534524553453453).setScale(yourScale, BigDecimal.ROUND_HALF_UP));
}
Это мое предпочтительное решение. Еще короче: BigDecimal.valueOf (doubleVar) .setScale (yourScaleHere, BigDecimal.ROUND_HALF_UP); BigDecimal.valueOf (double val) фактически вызывает Double.toString () под капотом;)
Хороший. Не срезайте углы и используйте new BigDecimal(doubleVar), так как вы можете столкнуться с проблемами округления чисел с плавающей запятой.
@Edd, интересно, проблема округления возникает в случае, если Себастьян ВанденБрук упоминает в комментарии к ответу астерита. double val = 265.335;, BigDecimal.valueOf(val).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString(); => 265.34, но (new BigDecimal(val)).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString(); => 265.33.
@ToolmakerSteve Это потому, что использование new BigDecimal с двойным принимает значение double напрямую и пытается использовать его для создания BigDecimal, тогда как при использовании BigDecimal.valueOf или формы tostring сначала разбирает его в строку (более точное представление) перед преобразованием .
Практическое руководство по Java от Real сообщения - это решение, которое также совместимо с версиями до Java 1.6.
BigDecimal bd = new BigDecimal(Double.toString(d));
bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
return bd.doubleValue();
BigDecimal bd = new BigDecimal(Double.toString(number));
bd = bd.setScale(decimalPlaces, RoundingMode.HALF_UP);
return bd.doubleValue();
Предположим, у вас есть
double d = 9232.129394d;
вы можете использовать BigDecimal
BigDecimal bd = new BigDecimal(d).setScale(2, RoundingMode.HALF_EVEN);
d = bd.doubleValue();
или без BigDecimal
d = Math.round(d*100)/100.0d;
с обоими решениями d == 9232.13
Думаю, это лучшее решение для пользователей Java 1.5 (и ниже). Один комментарий, однако, не используйте режим округления HALF_EVEN, поскольку он имеет различное поведение для нечетных и четных чисел (например, 2,5 округления до 2, а 5,5 округления до 6), если это не то, что вы хотите.
Первое решение правильное: второе не работает. См. здесь для доказательства.
@EJP: Даже первое решение с RoundingMode.HALF_UP неверно. Попробуйте с 1.505. Правильный способ - использовать BigDecimal.valueOf(d).
Маттиас Браун, решение в порядке, следовательно, 31 иб. 1,505 десятичного числа хранится в двойном формате с плавающей запятой как 1,50499998, если вы хотите взять 1,505 и преобразовать из двойного в десятичное, тогда вам нужно сначала преобразовать его в Double.toString (x) затем поместите его в BigDecimal (), но это очень медленно и в первую очередь лишает смысла использование double для скорости.
Выполните цикл 100k с BigDecimal (заняло 225 мс) и Math.round (2 мс), и вот время ... Затраченное время: 225 миллисекунд для преобразования с использованием в: 9232,13 Затраченное время: 2 миллисекунды для преобразования в : 9232.13 techiesinfo.com
это решение в 112 раз медленнее, чем другие методы. Я думаю, если вы не заботитесь о написании быстрого кода, зачем вообще использовать двойной. Если вам нужны правильные числа с основанием 10, НИКОГДА не используйте двойное число.
@IcedDante Режим HALF_EVEN известен как «Банковское округление», часто используемый в финансовых вопросах (отсюда и название). Этот подход более справедлив с математической точки зрения, чем «Школьное округление», которому обычно учат детей. Так как 5 находится точно посередине, всегда округляется в большую сторону («Школа») смещает результаты в сторону больших чисел. Округление Банкира разделяет разницу, поэтому половина значений округляется в большую сторону, а половина в меньшую (при случайном распределении значений).
Первое решение также неверно в частном случае: BigDecimal bd = new BigDecimal (12.475d) .setScale (2, RoundingMode.HALF_EVEN); bd.doubleValue (); -> Результат: 12.47 Правильный способ: BigDecimal bd = new BigDecimal (Double.toString (12.475d)). SetScale (2, RoundingMode.HALF_EVEN); Причина: новый BigDecimal (12.475d) создает BigDecimal со значением 12.4749999999999996447286321199499070644378662109375
Вам нужен BigDecimal.valueOf(d) вместо new BigDecimal(d), если вы хотите избежать ошибок из-за неточности с плавающей запятой.
Вы можете использовать следующий служебный метод:
public static double round(double valueToRound, int numberOfDecimalPlaces)
{
double multipicationFactor = Math.pow(10, numberOfDecimalPlaces);
double interestedInZeroDPs = valueToRound * multipicationFactor;
return Math.round(interestedInZeroDPs) / multipicationFactor;
}
@mariolpantunes: Это не удастся. Попробуйте это: round(1.005,2); или round(0.50594724957626620092, 20);
Оно работает. Но неинформативно float и double - это приближения. Давайте рассмотрим ваш первый пример. Если вы распечатаете вывод заинтересованныхInZeroDPs перед Math.round, он напечатает 100.49999999999999. Вы потеряли точность как таковую Math.round округлите его до 100. Из-за природы или чисел с плавающей запятой и двойных значений есть пограничные случаи, когда он не работает должным образом (подробнее здесь en.wikipedia.org/wiki/Floating_point#Accuracy_problems)
двойной быстрый! десятичный - медленный. компьютеры не утруждают себя обработкой своего мышления в десятичной системе счисления. вам нужно отказаться от некоторой десятичной точности, чтобы скорость с плавающей запятой была двойной.
@hamish Вопрос в точности, а не в скорости.
@Milhous: десятичный формат для округления отличный:
You can also use the
DecimalFormat df = new DecimalFormat("#.00000"); df.format(0.912385);to make sure you have the trailing 0's.
Я бы добавил, что этот метод очень хорош в обеспечении реального числовой, механизм округления - не только визуально, но и при обработке.
Гипотетический: вам нужно реализовать механизм округления в графическом интерфейсе. программа. Чтобы изменить точность / точность вывода результата, просто изменить формат каретки (т.е. в скобках). Чтобы:
DecimalFormat df = new DecimalFormat("#0.######");
df.format(0.912385);
вернется как вывод: 0.912385
DecimalFormat df = new DecimalFormat("#0.#####");
df.format(0.912385);
вернется как вывод: 0.91239
DecimalFormat df = new DecimalFormat("#0.####");
df.format(0.912385);
вернется как вывод: 0.9124
[Обновлено: также, если формат каретки такой ("# 0. ############") и вы
введите десятичную дробь, например 3.1415926, для аргументации DecimalFormat
не производит мусора (например, завершающие нули) и вернет:
3.1415926 .. если ты к этому склоняешься. Конечно, это немного многословно
на усмотрение некоторых разработчиков - но, эй, у него мало памяти
во время обработки и очень легко реализовать.]
По сути, прелесть DecimalFormat в том, что он одновременно обрабатывает строку внешний вид - а также установленный уровень точности округления. Ergo: ты получите два преимущества по цене одной реализации кода. ;)
Если вам действительно нужны десятичные числа для расчета (а не только для вывода), не используйте двоичный формат с плавающей запятой как double. Используйте BigDecimal или любой другой десятичный формат.
Если вам действительно нужны десятичные числа для вычислений (и не только для вывода), не используйте двоичный формат с плавающей запятой, такой как double.
Use BigDecimal or any other decimal-based format.
Я использую BigDecimal для вычислений, но имейте в виду, что это зависит от размера числа, с которыми вы имеете дело. В большинстве своих реализаций я нахожу синтаксический анализ от double или Целого числа в Long достаточно для вычислений очень большого числа.
На самом деле я недавно использовал parsed-to-Long для получения точных представлений (в отличие от шестнадцатеричных результатов) в графическом интерфейсе для чисел размером до ############################### символов (как пример).
В приведенном ниже фрагменте кода показано, как отображать n цифр. Уловка состоит в том, чтобы установить для переменной pp значение 1, за которым следует n нулей. В приведенном ниже примере значение переменной pp имеет 5 нулей, поэтому будет отображаться 5 цифр.
double pp = 10000;
double myVal = 22.268699999999967;
String needVal = "22.2687";
double i = (5.0/pp);
String format = "%10.4f";
String getVal = String.format(format,(Math.round((myVal +i)*pp)/pp)-i).trim();
Вы можете использовать класс DecimalFormat.
double d = 3.76628729;
DecimalFormat newFormat = new DecimalFormat("#.##");
double twoDecimal = Double.valueOf(newFormat.format(d));
Есть ли причина, по которой Double.valueOf() был выбран вместо Double.parseDouble()? Метод valueOf() возвращает объект Double, а parseDouble() возвращает примитив double. С учетом того, как написан текущий код, вы также применяете автоматическую распаковку к возврату, чтобы преобразовать его в примитив, который ожидает ваша переменная twoDouble, дополнительная операция байт-кода. Я бы изменил ответ на использование parseDouble().
Double.parseDouble() требует ввода String.
Как отмечали некоторые другие, правильный ответ - использовать DecimalFormat или BigDecimal. Плавающая точка не имеет десятичных знаков имеют, поэтому вы не можете округлить / усечь их до определенного количества. Вы должны работать с десятичной системой счисления, и это то, что делают эти два класса.
Я публикую следующий код в качестве контрпримера ко всем ответам в этом потоке и, действительно, во всем StackOverflow (и в других местах), которые рекомендуют умножение с последующим усечением с последующим делением. Сторонники этого метода обязаны объяснить, почему следующий код дает неправильный результат более чем в 92% случаев.
public class RoundingCounterExample
{
static float roundOff(float x, int position)
{
float a = x;
double temp = Math.pow(10.0, position);
a *= temp;
a = Math.round(a);
return (a / (float)temp);
}
public static void main(String[] args)
{
float a = roundOff(0.0009434f,3);
System.out.println("a = "+a+" (a % .001) = "+(a % 0.001));
int count = 0, errors = 0;
for (double x = 0.0; x < 1; x += 0.0001)
{
count++;
double d = x;
int scale = 2;
double factor = Math.pow(10, scale);
d = Math.round(d * factor) / factor;
if ((d % 0.01) != 0.0)
{
System.out.println(d + " " + (d % 0.01));
errors++;
}
}
System.out.println(count + " trials " + errors + " errors");
}
}
Вывод этой программы:
10001 trials 9251 errors
Обновлено: Чтобы ответить на некоторые комментарии ниже, я переделал модульную часть тестового цикла, используя BigDecimal и new MathContext(16) для операции модуля следующим образом:
public static void main(String[] args)
{
int count = 0, errors = 0;
int scale = 2;
double factor = Math.pow(10, scale);
MathContext mc = new MathContext(16, RoundingMode.DOWN);
for (double x = 0.0; x < 1; x += 0.0001)
{
count++;
double d = x;
d = Math.round(d * factor) / factor;
BigDecimal bd = new BigDecimal(d, mc);
bd = bd.remainder(new BigDecimal("0.01"), mc);
if (bd.multiply(BigDecimal.valueOf(100)).remainder(BigDecimal.ONE, mc).compareTo(BigDecimal.ZERO) != 0)
{
System.out.println(d + " " + bd);
errors++;
}
}
System.out.println(count + " trials " + errors + " errors");
}
Результат:
10001 trials 4401 errors
Хитрость в том, что во всех ваших 9251 ошибках результат печати все еще верен.
@DidierL Это доказывает, что «напечатанный результат» и «результат» - не одно и то же. На правильность «результата печати» влияет то, что происходит во время печати. Само по себе умножение и деление не решает проблемы.
@DidierL Меня это не удивляет. Мне посчастливилось пройти «Численные методы» в качестве своего первого курса по вычислениям, и я с самого начала познакомился с тем, что можно и чего нельзя делать с плавающей запятой. Большинство программистов довольно расплывчато об этом.
такой же результат был произведен версией этой программы для Python.
хм, похоже в вашей программе ошибка. System.out.println ("a =" + a + "(a% 0,0001) =" + (a% 0,001));
Что ж, признаюсь, я довольно расплывчато относился к этим «численным методам». Если у вас есть время, я хотел бы узнать ваше мнение о мой предыдущий вопрос.
Хотя исходная точка остается в силе (усечение числа с плавающей запятой - это не то же самое, что округление десятичных чисел), этот контрпример вызывает у меня некоторое подозрение. Обратите внимание, что мы знаем, что двойная 0,25 точно равна 1/4, но ((0.25 % 0.01) == 0.0) оценивается как ложь.
Я бы изменил тест с if ((d % 0.01) != 0.0) на if (BigDecimal.valueOf(d).compareTo(new BigDecimal(d)) != 0), что указывает на то, что частота ошибок даже выше, чем первоначально предполагалось (и увеличивается с увеличением шкалы до 99,9% для шкалы 5).
Есть объяснение: (d% 0,01) добавляет неточности к результату и затмевает точность операции округления. Таким образом, это доказывает только то, что в 92% случаев операция модуля не является точной. У меня есть встречный пример: code double d1, d2; d1 = 10.0100000000000000000123D; d2 = 10.0100000D; System.out.println(Math.round(d1 * 100)/100D == d2); // true double d3 = 0.0000090000000000000000000789D; System.out.println(Math.round(d3 * 100)/100D == 0.0D); // true Кстати, я не защищаю эту технику. Склонен к возможному переполнению.
Все, что вы делаете, - это опровергаете тот факт, что с плавающей точкой не всегда точно отображается много десятичных значений, что, надеюсь, мы все понимаем. Не то чтобы округление действительно было проблемой. Как вы признаете, цифры по-прежнему печатаются так, как ожидалось.
Ваш тест не работает, возьмите round (), и тест не проходит в 94% случаев. ideone.com/1y62CY печатает 100 trials 94 errors. Вы должны начать с теста, который прошел успешно, и показать, что введение округления ломает тест.
Опровержение, опровергнутое здесь. Использование Math.round для этого диапазона double как отсутствие ошибок ideone.com/BVCHh3
@PeterLawrey Хороший замечание, но это правда, только если невозможно округлить 0,001 до точного числа десятичных знаков, которое составляет quod erat manifestrandum.
да, точность означала бы использование памяти в 100 раз больше, а точность означала бы в 100 раз медленнее. делайте собственные выводы относительно того, почему программисты и процессоры любят числа с плавающей запятой в два раза :) через 50 лет, когда процессоры будут в 10 000 раз быстрее .. никто больше не будет заботиться о дублировании :) даже сейчас мы видим более быстрые четырехъядерные процессоры ARM Android .. Вот почему люди, уже работающие в этой ветке, хотят, чтобы точность была важнее скорости.
@ToolmakerSteve Вопрос в том, чтобы получить ровно п десятичных знаков. Любой другой результат не по теме, как и ваши замечания о моем отношении.
@hamish Для этого требуется десятичная система счисления, а не в 100 раз больше памяти.
Хотя я согласен с вами, поскольку 0.01 не может быть представлен в точности, этот тест плохой. Лучшим тестом было бы преобразовать результат roundOff в BigDecimal и сравнить его с точным результатом, вычисленным с помощью BigDecimal.
@geza Я переделал часть цикла, используя BigDecimal/MathContext с точностью до 16 знаков для модуля упругости, и получил 50% отказов.
a *= temp; имеет неточность в округлении значений задачи около границы. Например. x.xx5 и position == 2. Продукт, сохраненный в a, округляется, что время от времени приводит к неверному значению roundOff(). Здесь было бы разумнее сохранить дополнительную точность double с a = Math.round(a * temp);, а не a *= temp; a = Math.round(a);.
Попробуйте это: org.apache.commons.math3.util.Precision.round (двойной x, масштаб int)
См .: http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/util/Precision.html
Домашняя страница библиотеки математики Apache Commons: http://commons.apache.org/proper/commons-math/index.html
Внутренняя реализация этого метода:
public static double round(double x, int scale) {
return round(x, scale, BigDecimal.ROUND_HALF_UP);
}
public static double round(double x, int scale, int roundingMethod) {
try {
return (new BigDecimal
(Double.toString(x))
.setScale(scale, roundingMethod))
.doubleValue();
} catch (NumberFormatException ex) {
if (Double.isInfinite(x)) {
return x;
} else {
return Double.NaN;
}
}
}
Где дп = десятичный разряд, который вы хотите, а ценить - это двойник.
double p = Math.pow(10d, dp);
double result = Math.round(value * p)/p;
Производит 1.0 для value = 1.005 и dp = 2. Вместо этого используйте это.
это нормально, Мэтт, твой пример неверен. потому что 1.005 в любом случае не может быть представлен в формате double с плавающей запятой. он должен храниться действительно выше или ниже 1,005, то есть он сохраняется как двойной при компиляции: 1,0049998 (он не сохраняется как десятичный в вашем скомпилированном коде, как вы могли бы подумать читатели) цель правильная, он сохраняет значения как с плавающей запятой двойной, где крайности, подобные вашему, в любом случае несущественны. если бы это было так, то вы использовали бы 3dp, затем преобразовали бы его в десятичное, а затем выполняли бы функцию округления десятичного числа, как и опубликованная вами ссылка.
@hamish Я не понимаю, где Матиас «заставил бы читателей поверить» в такую вещь, как то, что значение компилируется как десятичное. Не вкладывайте слова в уста других людей.
Вы можете использовать BigDecimal
BigDecimal value = new BigDecimal("2.3");
value = value.setScale(0, RoundingMode.UP);
BigDecimal value1 = new BigDecimal("-2.3");
value1 = value1.setScale(0, RoundingMode.UP);
System.out.println(value + "n" + value1);
См .: http://www.javabeat.net/precise-rounding-of-decimals-using-rounding-mode-enumeration/
Если вы используете DecimalFormat для преобразования double в String, это очень просто:
DecimalFormat formatter = new DecimalFormat("0.0##");
formatter.setRoundingMode(RoundingMode.HALF_UP);
double num = 1.234567;
return formatter.format(num);
Есть несколько значений перечисления RoundingMode на выбор, в зависимости от требуемого поведения.
Вот краткое изложение того, что вы можете использовать, если хотите получить результат в виде String:
DecimalFormat # setRoundingMode ():
DecimalFormat df = new DecimalFormat("#.#####");
df.setRoundingMode(RoundingMode.HALF_UP);
String str1 = df.format(0.912385)); // 0.91239
String str2 = new BigDecimal(0.912385)
.setScale(5, BigDecimal.ROUND_HALF_UP)
.toString();
Вот предложение, какие библиотеки вы можете использовать, если вам нужен double. Однако я бы не рекомендовал его для преобразования строк, поскольку double может не представлять то, что вы хотите точно (см., Например, здесь):
Я согласен с выбранным ответом на использование DecimalFormat --- или в качестве альтернативы BigDecimal.
Пожалуйста, сначала прочтите Обновлять ниже!
Однако, если вы делать хотите округлить двойное значение и получить результат значения double, вы можете использовать org.apache.commons.math3.util.Precision.round(..), как упомянуто выше. Реализация использует BigDecimal, работает медленно и создает мусор.
Аналогичный, но быстрый и свободный от мусора метод предоставляется утилитой DoubleRounder в библиотеке decimal4j:
double a = DoubleRounder.round(2.0/3.0, 3);
double b = DoubleRounder.round(2.0/3.0, 3, RoundingMode.DOWN);
double c = DoubleRounder.round(1000.0d, 17);
double d = DoubleRounder.round(90080070060.1d, 9);
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
Будет выводить
0.667
0.666
1000.0
9.00800700601E10
Видеть https://github.com/tools4j/decimal4j/wiki/DoubleRounder-Utility
Заявление об ограничении ответственности: Я участвую в проекте decimal4j.
Обновлять:
Как отметил @iaforek, DoubleRounder иногда возвращает нелогичные результаты. Причина в том, что он выполняет математически правильное округление. Например, DoubleRounder.round(256.025d, 2) будет округлен до 256,02, потому что двойное значение, представленное как 256.025d, несколько меньше рационального значения 256.025 и, следовательно, будет округлено в меньшую сторону.
Заметки:
BigDecimal(double) (но не на valueOf(double), который использует конструктор строк).По этим причинам и всему, что упомянуто выше в этом посте, я не могу рекомендовать использовать DoubleRounder.
Есть ли у вас показатели, показывающие, насколько ваше решение эффективно по сравнению с другими?
Я не сравнивал его с другими решениями, но в исходном коде есть тест jmh: github.com/tools4j/decimal4j/blob/master/src/jmh/java/org/… Я запускал тест на виртуальной машине, результаты доступны в виде файла csv здесь: github.com/tools4j/decimal4j/wiki/Performance
DoubleRounder не работает в следующих случаях: DoubleRounder.round (256.025d, 2) - ожидалось: 256,03, фактическое: 256,02 или для DoubleRounder.round (260.775d, 2) - ожидалось: 260,78, фактическое: 260,77.
@iaforek: это правильно, потому что DoubleRounder выполняет математически правильное округление. Однако я признаю, что это несколько нелогично, и поэтому обновлю свой ответ соответствующим образом.
На всякий случай кому-то еще нужна помощь с этим. Это решение отлично работает для меня.
private String withNoTrailingZeros(final double value, final int nrOfDecimals) {
return new BigDecimal(String.valueOf(value)).setScale(nrOfDecimals, BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString();
}
возвращает String с желаемым результатом.
Пожалуйста, укажите в комментарии причину, по которой вы проголосовали против, иначе это то, что мы называем запугиванием.
Поскольку я не нашел полного ответа на эту тему, я собрал класс, который должен справиться с этим должным образом, с поддержкой:
Использование довольно простое:
(Для этого примера я использую настраиваемый языковой стандарт)
public static final int DECIMAL_PLACES = 2;
NumberFormatter formatter = new NumberFormatter(DECIMAL_PLACES);
String value = formatter.format(9.319); // "9,32"
String value2 = formatter.format(0.0000005); // "5,00E-7"
String value3 = formatter.format(1324134123); // "1,32E9"
double parsedValue1 = formatter.parse("0,4E-2", 0); // 0.004
double parsedValue2 = formatter.parse("0,002", 0); // 0.002
double parsedValue3 = formatter.parse("3423,12345", 0); // 3423.12345
Вот класс:
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.Locale;
public class NumberFormatter {
private static final String SYMBOL_INFINITE = "\u221e";
private static final char SYMBOL_MINUS = '-';
private static final char SYMBOL_ZERO = '0';
private static final int DECIMAL_LEADING_GROUPS = 10;
private static final int EXPONENTIAL_INT_THRESHOLD = 1000000000; // After this value switch to exponential notation
private static final double EXPONENTIAL_DEC_THRESHOLD = 0.0001; // Below this value switch to exponential notation
private DecimalFormat decimalFormat;
private DecimalFormat decimalFormatLong;
private DecimalFormat exponentialFormat;
private char groupSeparator;
public NumberFormatter(int decimalPlaces) {
configureDecimalPlaces(decimalPlaces);
}
public void configureDecimalPlaces(int decimalPlaces) {
if (decimalPlaces <= 0) {
throw new IllegalArgumentException("Invalid decimal places");
}
DecimalFormatSymbols separators = new DecimalFormatSymbols(Locale.getDefault());
separators.setMinusSign(SYMBOL_MINUS);
separators.setZeroDigit(SYMBOL_ZERO);
groupSeparator = separators.getGroupingSeparator();
StringBuilder decimal = new StringBuilder();
StringBuilder exponential = new StringBuilder("0.");
for (int i = 0; i < DECIMAL_LEADING_GROUPS; i++) {
decimal.append("###").append(i == DECIMAL_LEADING_GROUPS - 1 ? "." : ",");
}
for (int i = 0; i < decimalPlaces; i++) {
decimal.append("#");
exponential.append("0");
}
exponential.append("E0");
decimalFormat = new DecimalFormat(decimal.toString(), separators);
decimalFormatLong = new DecimalFormat(decimal.append("####").toString(), separators);
exponentialFormat = new DecimalFormat(exponential.toString(), separators);
decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
decimalFormatLong.setRoundingMode(RoundingMode.HALF_UP);
exponentialFormat.setRoundingMode(RoundingMode.HALF_UP);
}
public String format(double value) {
String result;
if (Double.isNaN(value)) {
result = "";
} else if (Double.isInfinite(value)) {
result = String.valueOf(SYMBOL_INFINITE);
} else {
double absValue = Math.abs(value);
if (absValue >= 1) {
if (absValue >= EXPONENTIAL_INT_THRESHOLD) {
value = Math.floor(value);
result = exponentialFormat.format(value);
} else {
result = decimalFormat.format(value);
}
} else if (absValue < 1 && absValue > 0) {
if (absValue >= EXPONENTIAL_DEC_THRESHOLD) {
result = decimalFormat.format(value);
if (result.equalsIgnoreCase("0")) {
result = decimalFormatLong.format(value);
}
} else {
result = exponentialFormat.format(value);
}
} else {
result = "0";
}
}
return result;
}
public String formatWithoutGroupSeparators(double value) {
return removeGroupSeparators(format(value));
}
public double parse(String value, double defValue) {
try {
return decimalFormat.parse(value).doubleValue();
} catch (ParseException e) {
e.printStackTrace();
}
return defValue;
}
private String removeGroupSeparators(String number) {
return number.replace(String.valueOf(groupSeparator), "");
}
}
Имейте в виду, что String.format () и DecimalFormat создают строку с использованием локали по умолчанию. Таким образом, они могут записывать форматированные числа с точкой или запятой в качестве разделителя между целыми и десятичными частями. Чтобы убедиться, что округленная строка имеет формат, который вы хотите, используйте java.text.NumberFormat следующим образом:
Locale locale = Locale.ENGLISH;
NumberFormat nf = NumberFormat.getNumberInstance(locale);
// for trailing zeros:
nf.setMinimumFractionDigits(2);
// round to 2 digits:
nf.setMaximumFractionDigits(2);
System.out.println(nf.format(.99));
System.out.println(nf.format(123.567));
System.out.println(nf.format(123.0));
Будет печататься на английском языке (независимо от вашего региона): 0,99 123,57 123.00
Пример взят из Фаренды - как правильно преобразовать double в String.
Если считать 5 или n десятичным числом. Может быть, этот ответ решит вашу проблему.
double a = 123.00449;
double roundOff1 = Math.round(a*10000)/10000.00;
double roundOff2 = Math.round(roundOff1*1000)/1000.00;
double roundOff = Math.round(roundOff2*100)/100.00;
System.out.println("result:"+roundOff);
Результат будет: 123.01
это можно решить с помощью цикла и рекурсивной функции.
DecimalFormat - лучший способ вывода, но я не предпочитаю его. Я всегда делаю это все время, потому что он возвращает двойное значение. Так что я могу использовать его не только для вывода.
Math.round(selfEvaluate*100000d.0)/100000d.0;
ИЛИ ЖЕ
Math.round(selfEvaluate*100000d.0)*0.00000d1;
Если вам нужно значение с большими десятичными знаками, вы можете вместо этого использовать BigDecimal. В любом случае .0 важен. Без него округление 0,33333d5 вернет 0,33333 и только 9 цифр допустимы. Вторая функция без .0 имеет проблемы с 0.30000 return 0.30000000000000004.
Я пришел сюда, просто чтобы получить простой ответ о том, как округлить число. Это дополнительный ответ.
Чаще всего используется Math.round().
Math.round(3.7) // 4
Числа округляются до ближайшего целого числа. Значение .5 округляется в большую сторону. Если вам нужно другое поведение округления, чем это, вы можете использовать одну из других функций Математика. См. Сравнение ниже.
Как указано выше, выполняется округление до ближайшего целого числа. Десятичные числа .5 округляются в большую сторону. Этот метод возвращает int.
Math.round(3.0); // 3
Math.round(3.1); // 3
Math.round(3.5); // 4
Math.round(3.9); // 4
Math.round(-3.0); // -3
Math.round(-3.1); // -3
Math.round(-3.5); // -3 *** careful here ***
Math.round(-3.9); // -4
Любое десятичное значение округляется до следующего целого числа. Он переходит в потолокing. Этот метод возвращает double.
Math.ceil(3.0); // 3.0
Math.ceil(3.1); // 4.0
Math.ceil(3.5); // 4.0
Math.ceil(3.9); // 4.0
Math.ceil(-3.0); // -3.0
Math.ceil(-3.1); // -3.0
Math.ceil(-3.5); // -3.0
Math.ceil(-3.9); // -3.0
Любое десятичное значение округляется до следующего целого числа. Этот метод возвращает double.
Math.floor(3.0); // 3.0
Math.floor(3.1); // 3.0
Math.floor(3.5); // 3.0
Math.floor(3.9); // 3.0
Math.floor(-3.0); // -3.0
Math.floor(-3.1); // -4.0
Math.floor(-3.5); // -4.0
Math.floor(-3.9); // -4.0
Это похоже на округление десятичных значений до ближайшего целого числа. Однако, в отличие от round, значения .5 округляются до четного целого числа. Этот метод возвращает double.
Math.rint(3.0); // 3.0
Math.rint(3.1); // 3.0
Math.rint(3.5); // 4.0 ***
Math.rint(3.9); // 4.0
Math.rint(4.5); // 4.0 ***
Math.rint(5.5); // 6.0 ***
Math.rint(-3.0); // -3.0
Math.rint(-3.1); // -3.0
Math.rint(-3.5); // -4.0 ***
Math.rint(-3.9); // -4.0
Math.rint(-4.5); // -4.0 ***
Math.rint(-5.5); // -6.0 ***
вы решаете только частный случай округления до 0 десятичных знаков. Исходный вопрос более общий.
Краткое решение:
public static double round(double value, int precision) {
int scale = (int) Math.pow(10, precision);
return (double) Math.round(value * scale) / scale;
}
См. Также https://stackoverflow.com/a/22186845/212950 Спасибо jpdymond за это.
Если вы используете технологию с минимальным JDK. Вот способ без каких-либо библиотек Java:
double scale = 100000;
double myVal = 0.912385;
double rounded = (int)((myVal * scale) + 0.5d) / scale;
Это не сработает в тех случаях, когда myVal не меньше 1 и с нулями после десятичной дроби, превышающей значение шкалы. Допустим, у вас myVal = 9.00000000912385; Вышеупомянутое вернет 9.0. Я думаю, мы должны предоставить решение, которое будет работать во всех случаях myVal. Не специально для указанного вами значения.
@ user102859 В вашем примере 9.0 - правильный результат. Я не понимаю, как это могло потерпеть неудачу.
Вот лучшая функция, которая правильно округляет крайние случаи, такие как 1.005.
Просто мы добавляем наименьшее возможное значение с плавающей запятой (= 1 ulp; единица в последнем месте) к числу перед округлением. Это переходит к следующему представимому значению после числа, отличному от нуля.
Это небольшая программа для проверки: ideone.com
/**
* Round half away from zero ('commercial' rounding)
* Uses correction to offset floating-point inaccuracies.
* Works symmetrically for positive and negative numbers.
*/
public static double round(double num, int digits) {
// epsilon correction
double n = Double.longBitsToDouble(Double.doubleToLongBits(num) + 1);
double p = Math.pow(10, digits);
return Math.round(n * p) / p;
}
// test rounding of half
System.out.println(round(0.5, 0)); // 1
System.out.println(round(-0.5, 0)); // -1
// testing edge cases
System.out.println(round(1.005, 2)); // 1.01
System.out.println(round(2.175, 2)); // 2.18
System.out.println(round(5.015, 2)); // 5.02
System.out.println(round(-1.005, 2)); // -1.01
System.out.println(round(-2.175, 2)); // -2.18
System.out.println(round(-5.015, 2)); // -5.02
Для этого мы можем использовать это средство форматирования:
DecimalFormat df = new DecimalFormat("#.00");
String resultado = df.format(valor)
или же:
DecimalFormat df = new DecimalFormat("0.00"); :
Используйте этот метод, чтобы всегда получать два десятичных знака:
private static String getTwoDecimals(double value){
DecimalFormat df = new DecimalFormat("0.00");
return df.format(value);
}
Определение этих значений:
91.32
5.22
11.5
1.2
2.6
Используя этот метод, мы можем получить следующие результаты:
91.32
5.22
11.50
1.20
2.60
вот мой ответ:
double num = 4.898979485566356;
DecimalFormat df = new DecimalFormat("#.##");
time = Double.valueOf(df.format(num));
System.out.println(num); // 4.89
Итак, после прочтения большинства ответов я понял, что большинство из них не будут точными, на самом деле использование BigDecimal кажется лучшим выбором, но если вы не понимаете, как работает RoundingMode, вы неизбежно потеряете точность. Я понял это, работая с большими числами в проекте, и подумал, что это может помочь другим, у которых возникают проблемы с округлением чисел. Например.
BigDecimal bd = new BigDecimal("1363.2749");
bd = bd.setScale(2, RoundingMode.HALF_UP);
System.out.println(bd.doubleValue());
Вы ожидаете получить 1363.28 на выходе, но в конечном итоге получите 1363.27, чего не ожидается, если вы не знаете, что делает RoundingMode. Заглянув в Документы Oracle, вы найдете следующее описание RoundingMode.HALF_UP.
Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
Итак, зная это, мы поняли, что не получим точного округления, если только не захотим округлить до ближайший сосед. Итак, чтобы выполнить адекватный раунд, нам нужно будет перейти от десятичной дроби n-1 к желаемым десятичным цифрам. Например.
private double round(double value, int places) throws IllegalArgumentException {
if (places < 0) throw new IllegalArgumentException();
// Cast the number to a String and then separate the decimals.
String stringValue = Double.toString(value);
String decimals = stringValue.split("\.")[1];
// Round all the way to the desired number.
BigDecimal bd = new BigDecimal(stringValue);
for (int i = decimals.length()-1; i >= places; i--) {
bd = bd.setScale(i, RoundingMode.HALF_UP);
}
return bd.doubleValue();
}
Это даст нам ожидаемый результат, которым будет 1363.28.
Я использовал ниже, как в java 8. Он работает для меня.
double amount = 1000.431;
NumberFormat formatter = new DecimalFormat("##.00");
String output = formatter.format(amount);
System.out.println("output = " + output);
Выход:
output = 1000.43
При необходимости можно использовать следующий метод double
double getRandom(int decimalPoints) {
double a = Math.random();
int multiplier = (int) Math.pow(10, decimalPoints);
int b = (int) (a * multiplier);
return b / (double) multiplier;
}
например getRandom(2)
DecimalFormat decimalFormatter = new DecimalFormat("#.00000"); decimalFormatter.format(0.350500); // result 0.350500
DecimalFormat decimalFormatter= new DecimalFormat("#.#####"); decimalFormatter.format(0.350500); // result o.3505
Простой способ сравнения, если количество десятичных знаков ограничено. Вместо DecimalFormat, Math или BigDecimal мы можем использовать Casting!
Вот образец,
public static boolean threeDecimalPlaces(double value1, double value2){
boolean isEqual = false;
// value1 = 3.1756
// value2 = 3.17
//(int) (value1 * 1000) = 3175
//(int) (value2 * 1000) = 3170
if ((int) (value1 * 1000) == (int) (value2 * 1000)){
areEqual = true;
}
return isEqual;
}
public static double formatDecimal(double amount) {
BigDecimal amt = new BigDecimal(amount);
amt = amt.divide(new BigDecimal(1), 2, BigDecimal.ROUND_HALF_EVEN);
return amt.doubleValue();
}
Тест с использованием Junit
@RunWith(Parameterized.class)
public class DecimalValueParameterizedTest {
@Parameterized.Parameter
public double amount;
@Parameterized.Parameter(1)
public double expectedValue;
@Parameterized.Parameters
public static List<Object[]> dataSets() {
return Arrays.asList(new Object[][]{
{1000.0, 1000.0},
{1000, 1000.0},
{1000.00000, 1000.0},
{1000.01, 1000.01},
{1000.1, 1000.10},
{1000.001, 1000.0},
{1000.005, 1000.0},
{1000.007, 1000.01},
{1000.999, 1001.0},
{1000.111, 1000.11}
});
}
@Test
public void testDecimalFormat() {
Assert.assertEquals(expectedValue, formatDecimal(amount), 0.00);
}
Math.round использует внутреннее приведение к long, которое затем возвращается к двойному.