Dunaj follows Clojure’s design for numbers and math related functionalities. Focused on performance, Dunaj uses host number types for representing numbers, without any additional wraping. Moreover, dedicated functions are available that provide host performance, trading off precision and overflow safety.

Dunaj provides multitude of math related functionalities, including:

Math related namespaces

Types of numbers

There are no built-in number types provided. Numbers in Dunaj are of host specific number types and are not extensible.

In rare cases where specific host number type is required, dunaj.host.number namespace provides coercion functions to host number types.

Dunaj however provides number type signatures and their respective predicates. Following type signatures map to one or more host number types:

  • Number type signature and number? predicate - Any number. In Dunaj lite, this type signature is named Number+

  • Integer and integer? - Integer numbers. Note that this is not a java.lang.Integer type. In Dunaj lite, this type signature is named Integer+

  • Decimal and decimal? - Decimal numbers.

  • Float and float? - Floating point numbers. Note that this is not a java.lang.Float type. In Dunaj lite, this type signature is named Float+

  • Rational and rational? - Rational numbers.

In addition, an INumerical protocol is provided and a num function returns a numerical value for objects that are not a number, but have a canonical numerical representation, such as instants, UUIDs and characters.

Dunaj’s num is different from Clojure num function, which is in Dunaj renamed to number.

Syntax

Dunaj follows Clojure’s syntax for number literals. Following table compares syntax for JSON, EDN and CLJ formats:

Table 1. Integers
JSON EDN CLJ

arbitrary precision indicator

NO

N suffix

N suffix

valid signs

-

- +

- +

-0 is valid

YES

YES

YES

decimal integer

YES

YES

YES

custom radix

NO

NO

NNrNNNNN

hexa integer

NO

NO

0xNNNNN, but undocumented

octal integer

NO

NO

0NNN, but undocumented

Table 2. Non-integral numbers
JSON EDN CLJ

ratios

NO

NO

NN/NN

decimals

NO

M suffix

M suffix

floats

YES

YES

YES

valid signs

-

- +

- +

Precise and unchecked arithmetics

Following arithmetic functions have their precise and unchecked variants: +, -, *, /, add, dec, inc, multiply, negate and subtract.

  • Arithmetics operations specified in dunaj.math namespace do not auto-promote and will throw on overflow. These functions are automatically refered in Dunaj API.

  • Functions defined in dunaj.math.precise are slower than normal math operations, but auto-promote when needed.

  • dunaj.math.unchecked namespace defines unchecked operations that are fast but prone to overflow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(ns foo.bar
  (:api dunaj)
  (:require [dunaj.math.unchecked :as mu]
            [dunaj.math.precise :as mp]
            [dunaj.host.number :as hn]))

(+ java.lang.Long/MAX_VALUE 1)
;; java.lang.ArithmeticException: integer overflow

(mu/+ java.lang.Long/MAX_VALUE 1)
;;=> -9223372036854775808

(mp/+ java.lang.Long/MAX_VALUE 1)
;;=> 9223372036854775808N
add subtract and multiply are 2 arguments aliases of +, - and * functions and are provided for convenience. negate is a 1 argument alias of -.

Random numbers

Dunaj defines a concept of a random number generator, which is a homogeneous collection recipe of random bytes. Random numbers are obtained by reducing given rng collection recipe. An IRngFactory factory protocol is provided for custom random number generators.

The random number generator is created with the rng function. Dunaj provides four rng types, each having its own rng factory:

  • secure-rng - A thread safe secure rng with host-specific algorithm and providers.

  • seedable-rng - A thread safe rng with the ability to specify initial seed and with a guarantee that the same seed yields same sequence of items in the first reduction of the rng. Slower compared to other rngs.

  • splittable-rng - A non thread safe rng which is however intended for fork-join tasks, as it is able to split into two separate rngs. Can be seeded with same guarantees as in seedable-rng.

  • thread-local-rng - A fast thread local rng.

Collection of random bytes can be futher converted with following transducer returning functions:

  • booleans - Converts into a collection of random booleans.

  • floats - Converts into a collection of random floats from the [begin end) inteval, both of which can be nil. Default begin value is 0.0 and default end value is 1.0.

  • gaussian - Converts into a collection of random floats in normal distribution.

  • integers - Converts into a collection of random integers from the [begin end) inteval, both of which can be nil. Default begin is set to 0.

  • sample - Randomly filters out items with probability prob, a value from the interval [0.0 - 1.0].

Additional functions rand, rand-integer and rand-nth are provided for user convenience.

Bitwise operations

Bitwise functions perform operations on individual bits of the input number(s). Following operations are provided:

<< >> and >>> are aliases of shift- functions and are provided for user convenience

Functions defined in this namespace work with any integer type. More performant functions that work with Int type (see following section) can be found in dunaj.host.int. Boolean logic functions can be found in dunaj.boolean namespace.

You can use binary and hexa functions that return binary and hexadecimal string representation of input number.

When using numeric constants, remember that CLJ format supports hexadecimal and binary notations for integer literals. It will make your code more readable.

1
2
3
4
5
(binary (bit/and 2r10110 2r11011))
;;=> \"00010010\"

(hexa (bit/and 0x0F0 0x1BC))
;;=> \"0x00B0\"

Prefer aliasing this namespace to refering its functions.

1
2
3
4
5
6
7
8
9
(ns foo.bar
  (:api dunaj)
  (:require [dunaj.bit :as bit]))

(def ALL_OPTS
  (bit/or java.nio.channels.SelectionKey/OP_READ
          java.nio.channels.SelectionKey/OP_WRITE
          java.nio.channels.SelectionKey/OP_ACCEPT
          java.nio.channels.SelectionKey/OP_CONNECT))

Handling primitive number types

Under JVM, Clojure chose to use Long as a default type for integers. This simplifies many things, but there are times where it is better to directly support an Integer type, as JDK mainly uses int for passing integer values. For those rare cases, Dunaj provides a dedicated namespace called dunaj.host.int that implements macros for working with int values without unnecessary promoting or boxing. Following functionalities are available:

  • An Int type, iint constructor, and value predicates (e.g. izero?, ineg? and iodd?)

  • Macro returning common int constants, including ASCII codes (as Clojure automatically compiles integer literals and constants as longs)

  • iloop macro for loops that do not promote ints to longs. This feature is not available in Dunaj lite.

  • Basic math facilities for working with ints

  • Bitwise manipulation operations on top of ints

Names of all vars provided by dunaj.host.int namespace are prefixed with the small letter i. Numerics constants are implemented as macros and must be unintuitively enclosed in parens.
Example of using array manager and dunaj.host.int namespace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(ns foo.bar
  (:api dunaj)
  (:require
   [dunaj.host :refer [ArrayManager]]
   [dunaj.host.array :refer [array array-manager-from]]
   [dunaj.host.int :refer [Int iint iloop i== iinc iadd imul i0 i1 i31]]))

(defn array-hash :- Int
  "Compute host hash code for an array section."
  [am :- ArrayManager, arr :- Array, begin :- Int, end :- Int]
  (iloop [i (iint begin), ret (i1)]
    (if (i== i end)
      ret
      (let [v (.get am arr i)]
        (recur
         (iinc i)
         (iadd (if (nil? v) (i0) (.hashCode ^java.lang.Object v))
               (imul (i31) ret)))))))
;;=> #'foo.bar/array-hash

(def arr (array [0 1 2 3 4 5 6 7 8 9]))
;;=> #'foo.bar/arr

(.hashCode [5 6 7 8])
;;=> 1078467

(array-hash (array-manager-from arr) arr 5 9)
;;=> 1078467

Elimination of autoboxing

This feature is not available in Dunaj lite.

Clojure compiles functions into host classes that by default treat input arguments and the return value as instances of java.lang.Object. This can be customized by specifying type hints. However, primitive types require special handling, and due to the combinatorial explosion, not all combinations of primitive types can be supported. Clojure supports arbitrary combinations of java.lang.Object with 2 primitive types (long and double), and only up to 4 input arguments.

Dunaj adds support for other combinations of primitive types (and a non-primitive java.lang.Object):

  • 0 or 1 arity functions have full support for primitives

  • 2 and 3 arity functions are supported when all input arguments are of a same type. Return type can be any primitive or java.lang.Object.

  • additional 2 and 3 arity functions are supported when first argument is java.lang.Object and the rest of arguments are of a same type

Dunaj further simplifies the process of creating functions that support primitive types. Type signatures automatically emit primitive type hints and only for cases where given combination of primitive types is allowed.

A special type signature NotPrimitive is provided for cases where primitive types are not desirable.
pt macro returns keyword based on type of argument, and is used to determine whether value is of primitive type or not.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(ns foo.bar
  (:api dunaj)
  (:require [dunaj.host.number :refer [byte]]
            [dunaj.host.int :refer [Int iadd iint]]))

(defn foo
  [x]
  x)

(pt (foo 4))
;;=> :object

(defn foo+ :- java.lang.Byte
  [x :- Int, y :- Int]
  (byte (iadd x y)))

(pt (foo+ 40 2))
;;=> :byte

(pt (foo+ 400 2))
;; java.lang.IllegalArgumentException: Value out of range for byte: 402

;; following combination of primitive types is not supported,
;; Dunaj emits non-primitive type hints
(defn foo++ :- java.lang.Byte
  [x :- Int, y :- Any]
  (byte (iadd x (iint y))))

(pt (foo++ 40 2))
;;=> :object