Skip to content

Using GMP Library with int64_t

dimxy edited this page Dec 18, 2019 · 9 revisions

How to correctly use big number arithmetic library GMP with int64_t / uint64_t types

There was a problem discovered in komodod with int64_t when it was used with GMP lib and appeared on the Windows platform. Here is the problem description and solution.

Problem description

In komodod int64_t is used for representing amount values. In some cases we need to use GMP library for arithmetic operation on amount operand to avoid overflow. It was discovered that GMP mpz_set_si(mpz, value) function truncates the value parameter on Windows.

The problem is in that that mpz_set_si has signature: __GMP_DECLSPEC void mpz_set_si (mpz_ptr, signed long int);

This signed long int has the size of 8 bytes on Linux and 4 bytes on Windows (https://en.cppreference.com/w/cpp/language/types) So when we are trying to pass always 8-byte int64_t value to 4 bytes long int parameter on Windows the value is truncated.

The problem continues when we use arithmetic GMP function which accept as a second param basic type 'long int' or

Part I. Solution for set/get for int64_t and uint64_t values

To be correct we cannot use mpz_set_si, mpz_get_si, and also mpz_set_ui and mpz_get_ui (the last two are for unsigned long int) with int64_t or uint64_t type because the long int type has variable length on different platforms. (Please note that there are some GMP library distribution which are patched for use 'unsigned long long int' for particular mpz functions but this does not solve the problem completely.)

The GMP library has special functions mpz_import and mpz_export which allow to load/unload integers of various types.

This is an implementation of mpz_t set/get functions for int64_t and uint64_t types.

void mpz_set_si64( mpz_t rop, int64_t op )
{
    int isneg = op < 0 ? 1 : 0;
    int64_t op_abs = op < 0 ? -op : op;
    mpz_import(rop, 1, 1, sizeof(op_abs), 0, 0, &op_abs);

    // NOTE: mpz_import does not understand signed values so we need to set the sign ourselves:
    // as the GMP library doc says:
    // https://gmplib.org/manual/Integer-Import-and-Export.html#Integer-Import-and-Export
    if (isneg)
    	mpz_neg(rop, rop);   
}

int64_t mpz_get_si64( mpz_t op )
{
    uint64_t u, u_abs;

    mpz_export(&u, NULL, 1, sizeof(u), 0, 0, op);
    u_abs = u < 0 ? -u : u;
    // NOTE: mpz_export treats values as unsigned so we need to analyze the sign ourselves:
    if (mpz_sgn(op) < 0)
	return -(int64_t)u_abs;
    else
        return (int64_t)u_abs;
}

void mpz_set_ui64( mpz_t rop, uint64_t op )
{
    mpz_import(rop, 1, 1, sizeof(op), 0, 0, &op);
}

uint64_t mpz_get_ui64( mpz_t op )
{
    uint64_t u;
    mpz_export(&u, NULL, 1, sizeof(u), 0, 0, op);
    return u;
}

This implementation is added to komodod source (see gmp_i64.h)

Part II. Do not use functions mpz_xxx_si or mpz_xxx_ui with int64_t or uint64_t values

Please also be careful when using GMP arithmetic functions which accept long int or unsigned long types as the last parameter like mpz_add_ui, mpz_sub_ui, mpz_mul_si, mpz_mul_ui, mpz_cmp_si etc because of the same truncation problem on Windows.
For example, load a int64_t value first into an mpz_t variable instead of using int64_t value as a unsigned long parameter of mpz_add_ui.

Incorrect:

mpz_t mpzValue;
mpz_init(mpzValue);
mpz_set_si64(mpzValue, 0x200000000000000000LL);
int64_t int64Value = 0x100000000000000000LL;
mpz_add_ui(mpzValue, mpzValue, int64value);  // int64value will be truncated on Windows

Correct:

mpz_t mpzValue1, mpzValue2;
mpz_init(mpzValue1);
mpz_init(mpzValue2);
mpz_set_si64(mpzValue1, 0x200000000000000000LL);
int64_t int64Value = 0x100000000000000000LL;
mpz_set_si64(mpzValue2, int64value);
mpz_add(mpzValue1, mpzValue1, mpzValue2);  
Clone this wiki locally