Clojure rounding to decimal places

20,040

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 BigDecimals. To round bigdecs 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

Share:
20,040

Related videos on Youtube

Isuru
Author by

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, 2021

Comments

  • Isuru
    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
    Unknown over 9 years
    cl-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
    celwell almost 9 years
    doesn't round, just floors it.
  • antonmos
    antonmos over 6 years
    The 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
    Jeremy Field about 5 years
    The Java 8 docs seem to indicate that it does round