8
votes

I'm trying to use BigDecimals in Clojure to model (if necessary) arbitrary precision numbers. I have a strange error when I try to instantiate a BigDecimal from an unscaled value and scale factor:

user=> 1.31M
1.31M (OK)
user=> (class 1.31M)
java.math.BigDecimal (OK)
user=> (.unscaledValue 1.31M)
131 (OK)
user=> (.scale 1.31M)
2 (OK)
user=> (.movePointLeft (BigDecimal. 131) 2)
1.31M (OK)
user=> (BigDecimal. (BigInteger. "131") 2)
1.31M
user=> (BigDecimal. 131N 2) (WRONG!!!)
IllegalArgumentException No matching ctor found for class java.math.BigDecimal  clojure.lang.Reflector.invokeConstructor (Reflector.java:183)
user=> (BigDecimal. (BigInteger. "131") 2)
1.31M

The problem here is that the clojure big integer IS NOT a java.math.BigInteger object. Even (bigint x) doesn't work:

user=> (doc bigint)
-------------------------
clojure.core/bigint
([x])
  Coerce to BigInt
nil

And by the way BigInteger constructor doesn't directly accept numeric values. I know I could also do something like:

user=> (BigDecimal. (BigInteger. (.toByteArray (.unscaledValue 1.31M))) (.scale 1.31M))
1.31M

My question is: is there a more idiomatic way of directly manage BigInteger objects from Clojure? Or am I stuck to wrap everything in a custom library, like:

user=> (defn my-bigint [x] (BigInteger. (.toString x)))
#'user/my-bigint
user=> (my-bigint 131)
131
user=> (BigDecimal. (my-bigint 131) 2)
1.31M

Thanks in advance for the help!

UPDATE: I NEED a BigInteger for serialization purposes: my idea is to store a BigDecimal as a byte array and an integer. My problem is that in Clojure, if I want, I cannot pass the result of .unscaledValue back and forth 'cause Clojure doesn't handle BigInteger created from Integers (neither do Java, for what it matters):

user=> (BigInteger. 3)
IllegalArgumentException No matching ctor found for class java.math.BigInteger  clojure.lang.Reflector.invokeConstructor (Reflector.java:183)

A call to .toString on the number is unuseful for the semantics of serialization (and more error prone). I would like to know if in Clojure there is an idiomatic way to write something like:

user=> (bigdec 131N 2)

No .movePointLeft (creation of two different objects without benefits), no .toString (I have a number, I stringify it and then create a BigInteger, another number, from it?), no slow and indirect method: just plain BigInteger and scale value.

Vinz

3
wrap a function with bigdec source. - number23_cn
Indeed my thought at the moment was to submit a patch that slighty modifies the clojure source to accept scale factor (which is the main problem). If you put this (the scale factor problem and the source code to be modified) I'll accept your answer :) (BTW, I'm starting to think this is a limitation of Java API, but it's another topic...) - Vincenzo Maggio
Accepted anyway, in the end your idea of bypassing BigInteger is not that bad, I'll wrap my head around it! - Vincenzo Maggio
Is there a reason you're not using the biginteger function? - Alex Taggart

3 Answers

9
votes
=> (type (.unscaledValue 1.31M))
java.math.BigInteger

=> (type (biginteger 131))
java.math.BigInteger

=> (BigDecimal. (biginteger 131) 2)
1.31M
4
votes
user=> (.movePointLeft (bigdec 131) 2)
1.31M
user=> (.movePointLeft (bigdec 131N) 2)
1.31M

user=> (source bigdec)
(defn bigdec
  "Coerce to BigDecimal"
  {:tag BigDecimal
   :added "1.0"
   :static true}
  [x] (cond
       (decimal? x) x
       (float? x) (. BigDecimal valueOf (double x))
       (ratio? x) (/ (BigDecimal. (.numerator x)) (.denominator x))
       (instance? BigInteger x) (BigDecimal. ^BigInteger x)
       (number? x) (BigDecimal/valueOf (long x))
       :else (BigDecimal. x)))
0
votes

I like this a bit better:

(-> 131M (.movePointLeft 2))