For hexadecimal values that fit into 63 bits (i.e. strictly positive signed values), you can use the following conversion function:
hex_to_int64 = function (x) {
hex_digits = setNames(0 : 15, c(0 : 9, letters[1 : 6]))
Reduce(
\(value, digit) value * 16L + unname(hex_digits[digit]),
strsplit(sub('^0x', '', x), '')[[1L]],
bit64::integer64(1L)
)
}
However, this won’t work for the value from your example (0x8107d13ec8130cad
), since that exceeds 263 − 1 (0x7fffffffffffffff
) and thus overflows the signed 64 bit integer range.
Unfortunately ‘bit64’ does not provide an unsigned integer type, nor does it provide a two’s complement function or bit shift, so manually implementing the above function for values ≥ 263 is rather onerous. I suggest using a different package.
The easiest solution I could find that supports values ≥ 263 does a detour via binary representation and then tests whether the 64th digit is 1:
hex_to_int64_signed = function (x) {
hex_digits = setNames(0 : 15, c(0 : 9, letters[1 : 6]))
bin_digits = t(expand.grid(0 : 1, 0 : 1, 0 : 1, 0 : 1))
from_hex = function (x) unname(hex_digits[x])
to_bin = function (x) rev(bin_digits[, x + 1L])
complement = function (x) {
x = 1L - x
carry = 1L
for (i in rev(seq_along(x))) {
res = x[i] + carry
x[i] = res %% 2L
carry = res %/% 2L
}
x
}
# Step 1: convert to binary representation
binary = unlist(lapply(
strsplit(sub('^0x', '', x), '')[[1L]],
\(d) to_bin(from_hex(d))
))
stopifnot(`result does not fit into 64 bits` = length(binary) <= 64L)
# Step 2: from binary to integer64
add = 0L
sign = 1L
if (length(binary) == 64L && binary[1L] == 1L) {
sign = -1L
binary = complement(binary)
# FIXME: This does not work due to a bug in ‘bit64’
if (binary[1L] == 1L) {
add = -bit64::as.integer64(2L) ^ 63L
sign = 0L
}
}
add + sign * Reduce(\(x, d) x * 2L + d, utils::tail(binary, 63L), bit64::as.integer64(0L))
}
We can now calculate the integer64
value of 0x8107d13ec8130cad
:
> hex_to_int64_signed('0x8107d13ec8130cad')
integer64
[1] -9149114050405004115
However, note the FIXME:
comment! A bug in ‘bit64’ prevents it from being able to represent the smallest valid 64 bit signed integer, which has the value −263:
> bit64::as.integer64('-9223372036854775807')
integer64
[1] -9223372036854775807
> bit64::as.integer64('-9223372036854775808')
integer64
[1] <NA>
> -bit64::as.integer64(2L) ^ 63L
integer64
[1] -9223372036854775807
# ^ THIS IS WRONG!
As a consequence, the hex_to_int64_signed
function fails on the entirely valid input 0x8000000000000000
, but it works on both sides of it, i.e. 0x7fffffffffffffff
(= 9223372036854775807) and 0x8000000000000001
(= −9223372036854775807).
… as I keep saying: use a different package.