Clojure округление до десятичных знаков

У меня есть строки, представляющие десятичные значения, например: «0,010», «0,0100000», «00,01000»

Я хочу округлить их до указанного формата, например: #.##

В Java у нас есть:

public BigDecimal setScale(int newScale, RoundingMode roundingMode) {
    return setScale(newScale, roundingMode.oldMode);
}

Каков наилучший подход для достижения этого в Clojure, а не с использованием взаимодействия?


person Isuru    schedule 25.05.2012    source источник


Ответы (5)


Для этой цели вы можете использовать Clojure format. Он должен предоставить вам решение вашей проблемы. Вот несколько примеров и ссылка:

user=> (format "%.2f" 0.010)
"0.01"
user=> (format "%.2f" 0.010000)
"0.01"
user=> (format "%.2f" 00.010000000)


user=> (doc format)
-------------------------
clojure.core/format
([fmt & args])
  Formats a string using java.lang.String.format, see java.util.Formatter for format
  string syntax
person number23_cn    schedule 25.05.2012
comment
cl-format следует использовать вместо формата - формат является лишь тонкой оболочкой вокруг java.util.Formatter и из-за этого он, например, не обрабатывает BigInt Clojure. Начиная с Clojure 1.3, если ваше число слишком велико в течение длительного времени, оно переполнится в BigInt, который является реализацией Clojure, вместо BigInteger, который является реализацией Java. Как вы могли догадаться, java.util.Formatter не обрабатывает реализацию Clojure и выдает исключение. - person Unknown; 04.01.2015
comment
не округляет, а просто выравнивает. - person celwell; 22.05.2015
comment
документы по Java 8 кажутся указать, что он округляется - person Jeremy Field; 31.03.2019

Это немного измененная версия примера на clojure-doc.org:

(defn round2
  "Round a double to the given precision (number of significant digits)"
  [precision d]
  (let [factor (Math/pow 10 precision)]
    (/ (Math/round (* d factor)) factor)))

Ответ @ number23_cn является правильным во многих случаях. Однако реальная функция округления с аргументом точности может быть полезна, если, например, вы хотите отобразить последовательность с округлением каждого числа. Затем вы можете просто сопоставить round2 последовательность, чтобы отформатировать каждое число одновременно:

(map (partial round2 2) [0.001 10.123456 9.5556])

который возвращает

(0.0 10.12 9.56)

Конечно, это более полезно для более длинной последовательности.


Другой вариант — использовать cl-format, который представляет собой реализацию format Common Lisp на языке Clojure. Он похож на format из Clojure (который основан на java.util.Formatter), но имеет другой синтаксис и позволяет использовать некоторые хитрости.

(clojure.pprint/cl-format nil "~,2f" 23.456)
; => "23.46"

Директива ~{ ~} позволяет обрабатывать последовательности, как в первом примере выше:

(clojure.pprint/cl-format nil "~{ ~,2f~}" [0.001 10.123456 9.5556])
; => " 0.00 10.12 9.56"

~{ ~} ожидает увидеть последовательность в качестве аргумента и будет потреблять элементы последовательности один за другим, используя любые директивы, находящиеся между ~{ и ~}.

(глава о format из книги Питера Сейбеля Practical Common Lisp — лучшее введение в Common Lisp format и, следовательно, в Clojure cl-format, imo. документация по CL format в обычном источнике, Common Lisp Hyperspec, иногда может быть сложно использовать. //www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000" rel="noreferrer">раздел о CL format в Common Lisp The Language немного лучше.)

person Mars    schedule 02.08.2014
comment
Строка документации для функции round2 неверно указывает, что параметр точности относится к числу, если оно состоит из значащих цифр. en.wikipedia.org/wiki/Significant_figures должно быть указано количество дробных цифр - person antonmos; 22.09.2017

Принятый ответ рекомендует format, но format не округляется (как указано в одном из комментариев). Другой ответ (от Марса) не будет работать для BigDecimals. Чтобы округлить bigdecs до числа знаков после запятой в Clojure, единственное решение, которое я нашел, - это использовать взаимодействие Java:

(defn round [s]
  (fn [n]
    (assert (bigdec? n))
    (.setScale n s RoundingMode/HALF_EVEN)))

(def round4 (round 4)) 
person Chris Murphy    schedule 09.09.2017

Использование функции Double/ParseDouble после использования format для десятичного числа вернет десятичное число, округленное до желаемого числа десятичных знаков, описанного с помощью format. Вот так:

user=> (Double/parseDouble (format "%.2f" 0.009) ) 
0.01

Если для дальнейших вычислений требуется округленное число, важно разобрать Double. Однако, если все, что нужно для вывода округленного числа, то использование format уместно.

person Charles Kornoelje    schedule 10.04.2019

Важно с самого начала преобразовать эти строки в значения BigDecimal. Любой числовой литерал, такой как 0.0001, или любое числовое значение может быть выведено как двойное или с плавающей запятой, и вы можете потерять точность для чисел, имеющих очень большие масштабы.

Во-вторых, вы, вероятно, не хотите явно форматировать его, просто масштабируйте до некоторого номинального масштаба:

(defn round
  [n scale rm]
  (.setScale ^java.math.BigDecimal (bigdec n)
             (int scale)
             ^RoundingMode (if (instance? java.math.RoundingMode rm)
                             rm
                             (java.math.RoundingMode/valueOf
                              (str (if (ident? rm) (symbol rm) rm))))))

(round "12.345" 2 :UP)
12.35M

(round "12.345" 2 :DOWN)
12.34M

(round "12.345" 2 :HALF_UP)
12.35M

(round "12.3" 2 :HALF_EVEN)
12.30M

Чтобы напечатать его без буквы M, просто используйте str или .toPlainString:

(str (round "0" 2 :HALF_EVEN))
"0.00"

(.toPlainString ^java.math.BigDecimal (round "1.2" 4 :UP))
"1.2000"

Вы также можете увидеть, как я абстрагировал аналогичную вещь в библиотеке Bankster (для игры с валютами и деньгами ) – ищите метод протокола apply:

https://github.com/randomseed-io/bankster/blob/main/src/io/randomseed/bankster/scale.clj

person siefca    schedule 17.07.2021