-
Notifications
You must be signed in to change notification settings - Fork 4
Using GMP Library with int64_t
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.
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
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)
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);