Clojure закръгляване до десетични знаци

Имам низове, които представляват десетичните стойности, например: "0.010", "0.0100000" "00.01000"

Искам да ги закръгля до определен формат, например: #.##

В Java имаме:

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

Какъв е най-добрият подход за постигане на това в Clojure вместо използване на interop?


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


Отговори (5)


Можете да използвате format на Clojure за тази цел. Трябва да ви даде решение на проблема ви. Ето няколко примера и справка:

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 трябва да се използва вместо format - 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, което е реализация на Clojure на format на Common Lisp. Подобен е на 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 е най-доброто въведение в format на Common Lisp и следователно в cl-format на Clojure, imo. документацията за CL's format в обичайния източник, Common Lisp Hyperspec понякога може да бъде труден за използване. раздел на CL's format в person Mars    schedule 02.08.2014


Приетият отговор препоръчва format, но format не закръгля (както е посочено в един от коментарите). Другият отговор (от Марс) няма да работи за BigDecimals. За да закръгля bigdecs до брой десетични знаци в Clojure, единственото решение, което открих, е да използвам Java interop:

(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 или която и да е числова стойност може да бъде изведена като двойна или float и може да загубите точността за числа с много големи мащаби.

Второто нещо е, че вероятно не искате да го форматирате изрично, просто премащабирайте до някакъв номинален мащаб:

(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