I want to port a C function using 64 bits integers to Perl code. To do that, I use Perl XS.
My problem is there are no 64 bits integers in Perl XS types (only U32, U16 and U8).
So, what is the best way to use 64 bit integers in Perl XS code?
Here is a code example of what I want to do:
uint64_t
foo(integer)
uint64_t integer
CODE:
RETVAL = foo(integer);
OUTPUT:
RETVAL
foo() has the C prototype:
uint64_t foo(uint64_t);
I didn't find anything useful in the perlxs documentation and in stackoverflow.com.
64 bit integers: C++, example. You may need to handle very large numbers in the C language. An unsigned number of 32 bits cannot exceed a particular value. In order to handle larger integers, a separate data type for handling 64 bit integers can be used in the C programming language.
A 64-bit signed integer. It has a minimum value of -9,223,372,036,854,775,808 and a maximum value of 9,223,372,036,854,775,807 (inclusive).
There is no standard way for having data type greater than 64 bits. You should check the documentation of your systems, some of them define 128 bits integers. However, to really have flexible size integers, you should use an other representation, using an array for instance.
64-bits ~ [-263, 263 – 1] = [ -9,223,372,036,854,775,808 , 9,223,372,036,854,775,807 ]
Perl's internal type for unsigned integers is UV
. If your platform has 64-bit UVs, you should be fine.
With 32-bit UVs (32-bit OS and Perl was compiled without use64bitint), you can convert large integers to and from floating point numbers (NV
which is typically a double
). But since IEEE doubles only have 53 bits of mantissa, this will cause a loss of precision for large numbers.
uint64_t integer;
// Convert uint64_t to SV.
if (integer <= UV_MAX) {
RETVAL = newSVuv((UV)integer);
}
else {
RETVAL = newSVnv((NV)integer);
}
// Convert SV to uint64_t with range checks.
if (SvUOK(sv)) {
integer = (uint64_t)SvUV(sv);
}
else if (SvIOK(sv)) {
IV iv = SvIV(sv);
if (iv < 0) croak(...);
integer = (uint64_t)iv;
}
else if (SvNOK(sv)) {
NV nv = SvNV(sv);
if (nv < 0.0 || nv >= 18446744073709551616.0) croak(...);
integer = (uint64_t)nv;
}
else {
// Parse a uint64_t from the string value, or whatever.
}
If you can't live with loss of precision and want to support 32-bit UVs, you can use the C API of Math::Int64
(also see Module::CAPIMaker
):
#define MATH_INT64_NATIVE_IF_AVAILABLE
#include "perl_math_int64.h"
MODULE = ...
BOOT:
PERL_MATH_INT64_LOAD_OR_CROAK;
SV*
foo(args...)
CODE:
uint64_t integer = ...
RETVAL = newSVu64(integer);
OUTPUT:
RETVAL
With a typemap like
TYPEMAP
uint64_t T_UINT64
INPUT
T_UINT64
$var = SvU64($arg)
OUTPUT
T_UINT64
$arg = newSVu64($var);
you can use uint64_t
directly in your XS:
uint64_t
foo(arg)
uint64_t arg
The uint64_t
type is not defined in default typemap
, that comes with Perl distribution. According to perlxstypemap
:
In more practical terms, the typemap is a collection of code fragments which are used by the
xsubpp
compiler to map C function parameters and values to Perl values.
You can define your own typemap
(in the same directory as .xs
file). It might look like:
TYPEMAP
unsigned long long T_U_LONG_LONG
uint64_t T_U_LONG_LONG # equivalent to typedef unsigned long long uint64_t;
INPUT
T_U_LONG_LONG
$var = (unsigned long long)SvUV($arg)
OUTPUT
T_U_LONG_LONG
sv_setuv($arg, (UV)$var);
I am not sure about these mappings, especially SvUV
and UV
part, so you need to test it carefully in any real code. I suspect that they are just "plain" integers, thus uint64_t
is fully functional only for internal usage, that is within function's definition.
Note that unsigned long long
type is at least 64-bits wide according to C Standard (since C99), but it's 64-bit on every implementation I am aware of.
With test.xs
as:
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
MODULE = test PACKAGE = test
unsigned int
u64_mul_high(a, b)
unsigned int a
unsigned int b
CODE:
RETVAL = ((uint64_t) a * b) >> 32;
OUTPUT:
RETVAL
and test.pl
defined as:
#!/usr/bin/perl
use ExtUtils::testlib;
use test;
print test::u64_mul_high(2147483647, 1000), "\n";
you get an result of:
499 (0001 1111 0011)
that is higher 32-bit limb of an 32-bit x 32-bit
multiplication that results into 64-bit integer.
I checked this on 32-bit GNU/Linux with sizeof(long) = 4
and Perl 5.10.
$ perl -MConfig -le 'print $Config{use64bitint}'
will show if your perl
was compiled to use 64 bit integers. If so, IV is 64-bit.
See also perldoc perlguts:
What is an "IV"?
Perl uses a special typedef IV which is a simple signed integer type that is guaranteed to be large enough to hold a pointer (as well as an integer). Additionally, there is the UV, which is simply an unsigned IV.
Perl also uses two special typedefs, I32 and I16, which will always be at least 32-bits and 16-bits long, respectively. (Again, there are U32 and U16, as well.) They will usually be exactly 32 and 16 bits long, but on Crays they will both be 64 bits.
See also Math::Int64:
This module adds support for 64 bit integers, signed and unsigned, to Perl.
...
Fallback to native 64bit support if available
If the lexical pragma
Math::Int64::native_if_available
is used in your program and the version of perl in use has native support for 64bit integers, the functions imported from the module that create 64bit integers (i.e.uint64
,int64
,string_to_int64
,native_to_int64
, etc.) will return regular perl scalars.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With