如何在Clojure中进行指数运算?

131

我该如何在Clojure中进行指数运算?目前我只需要整数指数,但这个问题也适用于分数。


28
作为一个不了解Clojure语言的人,但是因为喜欢Lisp语言、函数式编程和众多实用库而有倾向性,我很失望这个简单的问题有这么多答案,或者说需要被问到。我本以为指数运算会是一种基本的函数,不需要进行任何特殊处理。不过,我还是很高兴这个问题被问出来了。 - Mars
2
嗯,可能某个版本应该在核心中。但我认为许多答案仍然是一个好迹象。“多种实现路径”似乎是许多这些功能未提供的原因——用户应该知道他们正在使用的函数的细节,以提高效率。例如(正如所选择的答案中指出的那样),有些方法可能会潜在地使堆栈溢出,而其他方法则不太可能。也许有些是懒惰的,有些是急切的……所有这些细节都需要在Clojure中付出一些注意力,这就是为什么我认为大多数非平凡库由于哲学原因而未提供的原因。 - jm0
4
在Clojure核心中没有仅限于指数函数的原因是,出于效率考虑,Clojure的数值体系存在严重问题。因此,指数运算可能有各种不同的含义。例如,(exp 2 (exp 2 200)) 应该是一个错误还是需要花费很长时间才能计算出来的巨大整数?如果只想要通常的浮点数指数运算,则可以使用Java内置的函数。如果希望使用像实数一样的数字,不惜代价,可以使用Scheme而不是Clojure。 - John Lawrence Aspden
15个回答

157

经典递归(观察一下,它会引发堆栈溢出)

(defn exp [x n]
     (if (zero? n) 1
         (* x (exp x (dec n)))))
尾递归
(defn exp [x n]
  (loop [acc 1 n n]
    (if (zero? n) acc
        (recur (* x acc) (dec n)))))

函数式的

(defn exp [x n]
  (reduce * (repeat n x)))

sneaky(也称为blows stack,但不那么容易)

(defn exp-s [x n]
  (let [square (fn[x] (* x x))]
    (cond (zero? n) 1
          (even? n) (square (exp-s x (/ n 2)))
          :else (* x (exp-s x (dec n))))))

(require 'clojure.contrib.math)

请查看下面这个巧妙解决方案的完全迭代版本:https://dev59.com/gG445IYBdhLWcg3wE2Je#22977674 - Karl Rosaen
8
Clojure.contrib.math现已被弃用,请参考Math.Numeric-Tower - alvaro g
1
第二个建议(尾递归)在 n < 0 时会出现整数溢出。 - Pointo Senshi
1
太棒了,以下是使用宏定义的方法: (defmacro exp [x n] `(* ~@(take n (repeat x)))) - Daniel Szmulewicz
没有任何导入现有函数的答案可行(包括此答案中的(require 'clojure.contrib.math))。但是(defn exp [x n] (reduce * (repeat n x)))非常好用,而且易于理解! - Andrew Koster
显示剩余2条评论

84

Clojure具有一个功能强大的幂函数:我建议使用它,而不是通过Java互操作性去处理所有Clojure任意精度数字类型。它在命名空间clojure.math.numeric-tower中。

它被称为expt,表示指数运算,而不是powerpow,这可能解释了为什么有点难找…无论如何,这里有一个小例子(请注意,use可以工作,但最好使用require):

(require '[clojure.math.numeric-tower :as math :refer [expt]])  ; as of Clojure 1.3
;; (use 'clojure.contrib.math)     ; before Clojure 1.3

(expt 2 200)
=> 1606938044258990275541962092341162602522202993782792835301376

关于软件包安装的提醒

您必须首先安装Java软件包org.clojure.math.numeric-tower才能使Clojure命名空间clojure.math.numeric-tower可访问!

在命令行上:

$ lein new my-example-project
$ cd lein new my-example-project

然后编辑 project.clj 文件,在依赖向量中添加 [org.clojure/math.numeric-tower "0.0.4"]

启动 lein REPL(不是 clojure REPL)。

$ lein repl

现在:

(require '[clojure.math.numeric-tower :as math])
(math/expt 4 2)
;=> 16

或者

(require '[clojure.math.numeric-tower :as math :refer [expt]])
(expt 4 2)
;=> 16

1
可能不是标准Clojure的一部分,所以不为人知。当我尝试这样做时,1.3.0会抛出有关找不到math.clj的错误。 - Brian Knoblauch
9
我认为自Clojure 1.3版本以来,它现在位于“clojure.math.numeric-tower”中(因为 clojure.contrib 被拆分为单独的库)。 - mikera
1
添加了“关于软件包安装的提醒”,以缓解用户的不满。 - David Tonhofer

72

你可以使用Java的Math.powBigInteger.pow方法:

(Math/pow base exponent)

(.pow (bigdec base) exponent)

1
+1,虽然我知道你可以与Java库进行交互;但是,1)Math.pow适用于双精度浮点数,我需要整数,请给出一个例子?2)对于像幂这样简单的东西,您真的需要使用Interop吗? - Peter
1
@Peter:1)除非您的幂值太大而不能用双精度准确表示,否则将结果转换为int没有任何问题。2)我不明白为什么写 Math/pow 比写 math-pow 或者如果有clojure等效方法的话它的名字会更复杂。如果已经有一个简单的Java方法可以实现您想要的功能,那么没有必要在Clojure中重新创建该功能。Java互操作并不是本质上有害的。 - sepp2k
4
@达芬奇:奇怪的话,它有自己的语言,并且具有许多与Java相同的功能(例如字符串反转)。 - Peter
4
如果你想获得精确的大整数幂,我认为最好使用Clojure的clojure.contrib.math/expt。它在底层可能与通过Java互操作性实现的方式相同,但要比后者更简便。 - mikera
1
我得到了 No matching method pow ... for class clojure.lang.BigInt -- 难道不应该是 (.pow (biginteger base) exponent) 吗? - johncip
显示剩余3条评论

15

这个答案很棒,因为它可以正确处理所有Clojure精确(即BigDecimal / BigInteger)算术运算。 - mikera

9
user=> (.pow (BigInteger. "2") 10)
1024
user=> (.pow (BigInteger. "2") 100)
1267650600228229401496703205376

6
也可以使用字面符号表示法:(.pow 2M 100) - noisesmith
1
它们的类型不同。用户=>(type 2M) java.math.BigDecimal 用户=>(type(BigInteger。“2”)) java.math.BigInteger - KIM Taegyoon
最佳解决方案是使用现有库,包括处理大整数的功能。+1 - Daniel Gruszczyk
对我来说,(Math/pow Math/E x) 就可以解决问题(将 Math/E 替换为您选择的底数)。 - Zaz

6

如果您确实需要一个函数而不是方法,可以简单地将其包装:

 (defn pow [b e] (Math/pow b e))

在这个函数中,你可以将它转换为int或类似的类型。函数通常比方法更有用,因为你可以将它们作为参数传递给另一个函数 - 在这种情况下,我想到了map

如果你真的需要避免Java互操作,你可以编写自己的幂函数。例如,这是一个简单的函数:

 (defn pow [n p] (let [result (apply * (take (abs p) (cycle [n])))]
   (if (neg? p) (/ 1 result) result)))

这个函数计算整数指数的幂(即没有根号)。

此外,如果你要处理大量数字,你可能想使用BigInteger而不是int

如果你要处理非常大的数字,你可以将它们表示为数字列表,并编写自己的算术函数来流式传输它们,以计算结果并将结果输出到其他流中。


6
自从Clojure 1.11版本以来,clojure.math/pow已经包含在标准库中,并且适用于Clojure和ClojureScript。

4
我认为这也可以起作用:
(defn expt [x pow] (apply * (repeat pow x)))

但似乎最大只能达到2^63...绝对不可能达到2^200。 - TJ Trapp

4

SICP启发的完整迭代快速版本的“狡猾”实现如上所示。

(defn fast-expt-iter [b n]
  (let [inner (fn [a b n]
                (cond
                  (= n 0) a
                  (even? n) (recur a (* b b) (/ n 2))
                  :else (recur (* a b) b (- n 1))))
        ]
    (inner 1 b n)))

3

使用尾递归实现“巧妙”的方法,并支持负数指数:

(defn exp
  "exponent of x^n (int n only), with tail recursion and O(logn)"
   [x n]
   (if (< n 0)
     (/ 1 (exp x (- n)))
     (loop [acc 1
            base x
            pow n]
       (if (= pow 0)
         acc                           
         (if (even? pow)
           (recur acc (* base base) (/ pow 2))
           (recur  (* acc base) base (dec pow)))))))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接