Overview
Package dec is a go wrapper package around the libDecnumber library.
This is a work in progress. The API is in a more or less final state, all the decContext functions
have been implemented, but most decNumber functions are still missing, and there is no decQuad
implementation yet.
C decNumber to Go
The decNumber package is split into modules: the decContext module (required), the decNumber module
(for arbitrary precision arithmetic), float modules, namely decSingle, decDouble and decQuad, and
a few other modules for conversion between various formats. The float modules are based on the
32-bit, 64-bit, and 128-bit decimal types in the IEEE 754 Standard for Floating Point Arithmetic. In
contrast to the arbitrary-precision decNumber module, these modules work directly from the
decimal-encoded formats designed by the IEEE 754 committee. Their implementation is also faster: an
Add() with 34 digits numbers takes 433 cycles with de decQuad module versus 1180 for the decNumber
module.
Note that there is no standard libdecnumber.so. The decNumber package is provided as-is, with no
makefile to make a shared library out of it (the Makefile in this repository is not part of the
original decNumber archive and is only a test).
From a C programming perspective, all one has to do to compile code for decNumber is:
gcc -DSOME_TUNING_DEFINE=1234 mysource.c decContext.c decQuad.c decNumber.c -o myprogram
or if only decNumber is needed:
gcc mysource.c decContext.c decNumber.c -o myprogram
My initial intent was to split the wrapper into modules, in the same way as the C version. However,
this lead to unsolvable compilation issues, like missing references in the Number (Go) module to the
decContext (C) module. This is currently a no-Go (sorry) without shared libraries. However, I did
not want to force the end-user of the package to build and install a custom shared library, and I
also wanted the package to be go get
'able. Also considering the design decisions discussed in the
next section, I ended up making a monolithic package around decNumber and decContext.
The usual way to link Go code against a static library is to use a #cgo LDFLAGS: static/patch/to/lib/lib.a
directive. Since this package is meant to be imported into other
projects, using LDFLAGS was not possible without some standardized/portable/flexible way to specify
the path to the static library (whatever a package puts into LDFLAGS propagates to the project
importing it).
The trick to make it work is to use Go source files as wrappers around the relevant C files and
#include
them. This works quite well, except for long build times and a weird behaviour of go test
if not using a relative import path in test source files. The files decContext.go and
decNumber.go just do this: include the corresponding C file. See the topic about building/installing
for more options.
Most of the short C functions (accessors) have been reimplemented in Go in order to improve
performance. Use
go build -gcflags=-m . 2>&1 | grep inline
to check which functions can be inlined.
Numbers, Context and precision
The decNumber module can be built to use fixed precision numbers or arbitrary precision (changeable
at runtime), or a mix of both. In order to make things easier and more flexible for the clients of
this package, the decNumber module is setup for arbitrary precision numbers.
Most functions take a context parameter which provides the context for operations (precision,
rounding mode, etc.) and also controls the handling of exceptional conditions. For example, the
Exp() function is defined like this (C version):
decNumber * decNumberExp(decNumber *res, const decNumber *rhs, decContext *set)
It will set res to e raised to the power of rhs. The rhs operand can be in any precision
(i.e. context independent). However, *res, the decNumber structure that will hold the result, has
to have enough storage space to hold the precision specified in the decContext set.
One of the major problems a programmer may have when working with the decNumber library is keeping
track of which decNumber was created to work with which decContext (i.e. with enough storage space
for that context). We tried several approaches to get rid of this context parameter in the Go
implementation, but with no success. Suggestions welcome!
Note that this is a non-issue for applications using a fixed precision (global Context), while
applications that require dynamic precision can leverage the dec.NumberPool facility to keep track
of their working context and free Number list with a single variable.
Go implementation details
-
Contexts are created with a immutable precision (i.e. number of digits). If one needs to change
precision on the fly, discard the existing context and create a new one with the required precision.
-
From a programming standpoint, any initialized Number is a valid operand in arithmetic operations,
regardless of the settings or existence of its creator Context (not to be confused with having a
valid value in a given arithmetic operation).
-
Arithmetic functions are Number methods. The value of the receiver of the method will be set to
the result of the operation. For example:
n.Add(x, y, context) // n = x + y
-
Arithmetic methods always return the receiver in order to allow chain calling:
n.Add(x, y, context).Multiply(n, z, context) // n = (x + y) * z
-
Using the same Number as operand and result, like in n.Multiply(n, n, ctx)
, is legal and will not
produce unexpected results.
-
A few functions like the context status manipulation functions have been moved to their own type
(Status). The C call decContextTestStatus(ctx, mask) is therefore replaced by ctx.Status().Set(mask)
in the Go implementation. The same goes for decNumberClassToString(number) which is repleaced by
number.Class().String() in go.
In the C implementation, decQuads are defined in a supporting module for the decimal128 format and
provide a set of functions that work directly in this format. The decimal128 and decQuad structures
are identical (except in name) so pointers to the structures can safely be cast from one to the
other. The separation between decQuad and decimal128 in the source code allowed to use the decQuad
module stand-alone (that is, it has no dependency on the decNumber module).
The same goes for decSingle and decimal32, decDouble and decimal64.
In the Go implementation, and even if we could split the wrapper into sub-packages, this separation
does not make much sense since we want to provide access to everything that decNumber has to offer;
the linker will take care of including only the used bits and pieces into the final application
executable. As such, the decimal32/64/128 are merged into Single, Double and Quad.
Error handling
Active eror handling via traps is not supported in the Go implementation. The os/signal package does
not seem to be able to handle signals raised from C code (this always causes a panic), while
external signals can be handled just fine.
Although most arithmetic functions can cause errors, the standard Go error handling is not used in
its idiomatic form. That is, arithmetic functions do not return errors. Instead, the type of the
error is ORed into the status flags in the current context (Context type). It is the responsibility
of the caller to clear the status flags as required. The result of any routine which returns a
number will always be a valid number (which may be a special value, such as an Infinity or NaN).
This permits the use of much fewer error checks; a single check for a whole computation is often
enough. For axample:
ctx.ZeroStatus() // Reset the Context's status flags
rate.Divide(rate, hundred, ctx) // rate=rate/100
rate.Add(rate, one, ctx) // rate=rate+1
rate.Power(rate, years, ctx) // rate=rate**years
total.Multiply(rate, start, ctx) // total=rate*start
if err := ctx.ErrorStatus(); err != nil {
// Something went wrong somewhere
if ctx.Status().Test(dec.Overflow) || total.IsInfinite() {
fmt.Println("You probably shouldn't borrow that much")
}
}
To check for errors, get the Context's status with the Status() function (see the Status type), or
use the Context's ErrorStatus() function.
Free-list of Numbers
The package provides facilities for managing free-lists of Numbers in order to relieve pressure on
the garbage collector in computation intensive applications. NumberPool is in fact a simple wrapper
around a *Context and a sync.Pool (or the lighter util.Pool provided in the util subpackage);
NumberPool will automatically cast the return value of Get() to the desired type.
For example:
ctx := dec.NewContext(dec.InitDecimal128, 0)
// idomatic code for NumberPool creation
pool := &dec.NumberPool{
&sync.Pool{
New: func() interface{} { return dec.NewNumber(ctx.Digits()) },
},
ctx, // same context as the one used in New()
}
number := pool.Get() // with no need to type cast to *Number
defer pool.Put(number) // idiomatic code for short lived numbers
number.FromString("1243", pool.Context)
Note the use of pool.Context
on the last statement.
If an application needs to change its arithmetic precision on the fly, any NumberPool built on top
of the affected Context's will need to be discarded and recreated along with the Context. This will
not affect existing numbers that can still be used as valid operands in arithmetic functions.
Threading, goroutines
The decNumber library is thread safe as long as threads do not share decContext or decNumber
structures. The same rule applies to the Go wrapper package. The provided Pool is not thread safe
either.
A thread safe application could use an immutable global context with a sync.Pool to manage Number
allocation, and share Number's between goroutines by communicating.
What about decSingle, decDouble, decQuad ?
Right now, the main focus of the dec package is on decNumber. Other modules are only partially
implemented with just enough functionality to be able to run the C decNumber examples. decQuad will
be next.
Building / Installing
go get
If you only intend to include this package in your own project, just run
go get -u github.com/wildservices/go-decnumber
and you're all set.
.syso technique
Another option for package maintainers is to use the .syso mechanism which greatly speeds up
the build process. The idea is to bundle together all the .o files into a single .syso file. When
such a file is present in a package folder, it will automatically be linked with the other object
files.
To make the .syso file:
cd libdecnumber
make syso
cd ..
And benefit:
go test -tags="syso" -i ./...
go test -tags="syso "./...
When the syso
tag is specified in a Go build, the wrapper files (decContext.go and decNumber.go)
are ignored and the build uses the precompiled object file libdecnumber_${GOOS}_${GOARCH}.syso. The
difference with a static library is that we do not have to use a #cgo LDFLAGS
directive that would
bring in the static library in client projects as well.
The top level Makefile (at the root of the package folder) has the following targets:
make # builds using the "normal" wrapper mechanism, removing any syso file beforehand
make build # build using the syso file, compiling it if necessary
make test # test using the syso file, compiling it if necessary
make clean # the usual + removes the syso file.
Running tests using the Go->C wrappers takes 6.7 seconds on my workstation, versus only 1.6 second
when using the syso mechanism.
TODO
- Implement basic math functions.
- Thoroughly test free-list management and proper resource clean-up.
Licensing
go-decnumber (dec package)
The go-decnumber wrapper is:
Copyright 2014 Denis Bernard (wldsvc at gmail.com)
All rights reserved.
Use of this package is governed by a BSD-style license that can be found in the LICENSE file.
decNumber C library
The decNumber C library is:
Copyright (c) 1995-2010 International Business Machines Corporation and others
All rights reserved.
The decNumber C library is made available under the terms of the ICU License -- ICU 1.8.1 and later,
which can be found in the LICENSE-ICU file.