Clojure rounding to decimal places
Solution 1
You can use Clojure's format
for this purpose. It should provide you a solution to your problem. Here are some examples and a reference:
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
Solution 2
This is a slightly modified version of an example at 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's answer is the right one for many cases. However, a real rounding function with a precision argument may be useful if, for example, you want to display a sequence with every number rounded. Then you can simply map round2
over the sequence to format every number at once:
(map (partial round2 2) [0.001 10.123456 9.5556])
which returns
(0.0 10.12 9.56)
This is more useful for a longer sequence, of course.
Another option is to use cl-format
, which is a Clojure implementation of Common Lisp's format
. It's similar to Clojure's format
(which is based on java.util.Formatter
) but has a different syntax, and allows some fancier tricks.
(clojure.pprint/cl-format nil "~,2f" 23.456)
; => "23.46"
The ~{ ~}
directive allows processing sequences, as in the first example above:
(clojure.pprint/cl-format nil "~{ ~,2f~}" [0.001 10.123456 9.5556])
; => " 0.00 10.12 9.56"
~{ ~}
expects to see a sequence as an argument, and will eat elements of the sequence one by one using whatever directives appear between ~{
and ~}
.
(The chapter on format
from Peter Seibel's Practical Common Lisp is the best introduction to Common Lisp's format
, and therefore to Clojure's cl-format
, imo. The documentation on CL's format
in the usual source, the Common Lisp Hyperspec, can be difficult to use sometimes. The section on CL's format
in Common Lisp The Language is slightly better.)
Solution 3
The accepted answer recommends format
, yet format
does not round (as pointed out in one of the comments). The other answer (by Mars) will not work for BigDecimal
s. To round bigdec
s to a number of decimal places in Clojure the only solution I have found is to use Java interop:
(defn round [s]
(fn [n]
(assert (bigdec? n))
(.setScale n s RoundingMode/HALF_EVEN)))
(def round4 (round 4))
Solution 4
Using the function Double/ParseDouble
after using format
on the decimal number will return a decimal rounded to the desired number of decimals described by using format
. Like so:
user=> (Double/parseDouble (format "%.2f" 0.009) )
0.01
If the rounded number is needed for further calculations, parsing a Double is important. However, if outputting a rounded number is all that is needed, then the use of format
is appropriate.
Solution 5
It's important to convert these strings to BigDecimal values at the very beginning. Any numeric literal like 0.0001
or any numeric value may be inferred to a double or a float and you may loose the precision for numbers having very big scales.
Second thing is you probably don't want to explicitly format it, just rescale to some nominal scale:
(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
To print it without the M
letter just use str
or .toPlainString
:
(str (round "0" 2 :HALF_EVEN))
"0.00"
(.toPlainString ^java.math.BigDecimal (round "1.2" 4 :UP))
"1.2000"
You can also see how I abstracted similar thing in the Bankster library (for playing with currencies and money) – look for the apply
protocol method:
https://github.com/randomseed-io/bankster/blob/main/src/io/randomseed/bankster/scale.clj
Related videos on Youtube
Isuru
logger.info("About to execute the Golden rule of risking Matter A, B") if(a.getValue() < b.getValue()) { a.addToIsRiskableList(b) }
Updated on July 18, 2021Comments
-
Isuru almost 3 years
I have strings which represents the decimal values, ex: "0.010", "0.0100000" "00.01000"
I want to round them to specified format, ex: #.##
In Java we have:
public BigDecimal setScale(int newScale, RoundingMode roundingMode) { return setScale(newScale, roundingMode.oldMode); }
What is the best approach to achieve this in Clojure rather than using interop?
-
Unknown over 9 yearscl-format should be used instead of format - format is only a thin wrapper around the java.util.Formatter and because of that it does not handle the Clojure's BigInt for example. As of Clojure 1.3 if your number is too big for a long it will overflow in a BigInt, which is the Clojure implementation, instead of BigInteger, which is the Java implementation. As you might guess java.util.Formatter does not handle the Clojure implementation and throws exception.
-
celwell almost 9 yearsdoesn't round, just floors it.
-
antonmos over 6 yearsThe docstring on round2 function is incorrectly stating that the precision parameter is referring to the number if significant digits. en.wikipedia.org/wiki/Significant_figures it should say "number of fractional digits"
-
Jeremy Field about 5 yearsThe Java 8 docs seem to indicate that it does round