Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Quickstart

intops is a Nim library that implements multi-precision arithmetic operations for integers. Unlike Nim’s stdlib, intops offers multiple flavors for each operations and multiple implementations of each flavors. On top of that, intops chooses the best implementation for each OS, CPU architecture, and C compiler combo.

intops is designed for Nim developers working on bigint-related projects, e.g. crypto libraries.

What’s included in the package

intops implements the following operations for signed and unsigned 32- and 64-bit integers:

additionsubtractionmultiplicationdivisionmuladd
overflowingNim, intrinsicsNim, intrinsics
saturatingNim, intrinsics, ASMNim, intrinsics, ASM
carryingNim, intrinsics, C
borrowingNim, intrinsics, C
wideningNim, intrinsics, CNim, intrinsics, C
narrowingNim, intrinsics, ASM

Overflowing operations return an explicit didOverflow bool flag that tells you if an overflow happened during the operation. These operations wrap for both signed and unsigned integers.

Saturating operations do not return an overflow flag but silently return the highest or the lowest available value for a given type if an overflow is to occur.

Carrying and borrowing operations accept two operands and a flag that determines if an overflow happened earlier and should be taken into account. These operations return the new flag value along with the calculated result.

Widening operations accept two single-word operands and return a two-word results, i.e. widen the resulting type compared to the input type.

Narrowing operations do the opposite: accepting a two-word operand, they return a single-word result.

Additionally, intops ships with composite operations that purely combine other primitives to offer convenience operations for cryptography calculations:

  • Square and accumulate (mulDoubleAdd2)
  • Column accumulator for Comba multiplication (mulAcc)

Installation

$ nimble install -y intops

Add intops to your .nimble file:

requires "intops"

Usage

The most straightforward way to use intops is by importing intops and calling the operations from it:

import intops

echo carryingAdd(12'u64, 34'u64, false)

Output:

(res: 46, carryOut: false)

intops.carryingAdd is a dispatcher. When invoked, it calls the best implementation of this operation out of the available ones for the given environment.

Notice that we call carryingAdd with uint64 type set explicitly. This is important because int can mean different things under different circumstances and so intops doesn’t allow this kind of ambiguity.

If you try to call carryingAdd with an int, your code simply won’t compile:

echo not compiles carryingAdd(12, 34, false)

Output:

true

All available dispatchers are listed in the Imports section of the API docs for intops module.

The operations are grouped into families, each one living in a separate submodule. For example, carryingAdd operation mentioned above is imported from intops/ops/add submodule, so this is where you find its documentation: /apidocs/intops/ops/add.html.

Calling Specific Operations

import intops imports all available operations.

If you only need specific operations, you can import them individually:

import intops/ops/[add, sub]

echo compiles carryingAdd(12'u64, 34'u64, false)
echo not compiles wideningMul(12'u64, 34'u64)

Output:

true
true

Composite Operations

On top of the primitive operations like addition and multiplication, intops offers convenience operations that combine several primitives as a single composite operation:

import intops/ops/composite

echo mulDoubleAdd2(12'u64, 34'u64, 45'u64, 78'u64, 90'u64)

Output:

(t2: 0, r1: 78, r0: 951)

Read more in the API docs for composite module.

Calling Specific Implementations

You may want to override the dispatcher’s choice. To do that, import a particular implementation from intops/impl directly and call the function from it:

import intops/impl/intrinsics

echo intrinsics.gcc.carryingAdd(12'u64, 34'u64, false)

Output:

(46, false)

Implementations are also grouped into families: pure Nim, C intrinsics, inline C, and inline Assembly. Each family can be further split into subgroups.

For example, the carryingAdd implementation above is based on C intrinsics that are specific to GCC/Clang. All such implementations live in intops/impl/intrinsics/gcc. There’s also a subgroup that provides an implementation based on Intel/AMD specific C intrinsics—intops/impl/intrinsics/x86.

To see all available implementations for a particular operation, find it in the API index.

Caveat

When you use a dispatcher, you can be sure that your code will compile in any environment. It is the dispatcher’s job to provide a code path for any case, so you don’t have to worry about it.

But if you choose to call an implementation manually, it is your job to validate that this implementation is available in the environment you’re using it in. If you attempt to use an implementation that is unavailable, you’ll get a compile-time error.

For example, trying to use an Inline Assembly implementation with intopsNoInlineAsm flag (more on those in the section below) will cause a compilation error:

import intops/impl/inlineasm

echo not compiles inlineasm.x86.carryingAdd(12'u64, 34'u64, false)

If compiled with -d:intopsNoInlineAsm, this outputs:

true

Compilation Flags

You can control which implementations are forbidden to be picked by dispatchers using the compilation flags:

  • intopsNoIntrinsics
  • intopsNoInlineAsm
  • intopsNoInlineC

For example, to avoid using Inline Assembly implementations, compile your code with -d:intopsNoInlineAsm:

$ nim c -d:intopsNoInlineAsm mycode.nim

Of course, you can combine those flags. For example, if you want to use only pure Nim implementations, pass all three forbidding flags:

$ nim c -d:intopsNoIntrinsics -d:intopsNoInlineAsm -d:intopsNoInlineC mycode.nim