From 7d8bea6d3ad03f1feac66680ee71870eecdd4411 Mon Sep 17 00:00:00 2001 From: Hugo Gruson Date: Mon, 4 May 2026 08:30:12 +0200 Subject: [PATCH 1/8] Let C code handle string conversion --- R/read_npy.R | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/R/read_npy.R b/R/read_npy.R index 3d6035f..6d74e36 100644 --- a/R/read_npy.R +++ b/R/read_npy.R @@ -175,24 +175,6 @@ parse_npy_data <- function(bytes, shape, datatype, signed, typesize, endian) { FUN.VALUE = character(1), USE.NAMES = FALSE ) - } else if (datatype == "string") { - ints <- readBin( - bytes, - what = "integer", - size = 1, - n = num_elements * typesize, - endian = "little" - ) - tmp <- split( - ints, - f = ceiling(seq_along(ints) / typesize) - ) - data <- vapply( - tmp, - function(x) rawToChar(as.raw(x)), - FUN.VALUE = character(1), - USE.NAMES = FALSE - ) } else { # FIXME: optimize this bytes <- readBin(bytes, "raw", n = num_elements * typesize) From ec9717c73316227dafbec3671816d29c164b1c1a Mon Sep 17 00:00:00 2001 From: Hugo Gruson Date: Mon, 4 May 2026 10:52:51 +0200 Subject: [PATCH 2/8] Run more iterations in continuous benchmarks --- touchstone/script.R | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/touchstone/script.R b/touchstone/script.R index 222b8d0..1bf9490 100644 --- a/touchstone/script.R +++ b/touchstone/script.R @@ -10,7 +10,7 @@ touchstone::branch_install() touchstone::benchmark_run( pkg_load = library(grumpy), - n = 50 + n = 100 ) # Types ---------- @@ -21,7 +21,7 @@ touchstone::benchmark_run( library(grumpy) }, read_bool = read_npy("inst/extdata/test_bool.npy"), - n = 50 + n = 100 ) # float32 @@ -30,7 +30,7 @@ touchstone::benchmark_run( library(grumpy) }, read_float32 = read_npy("inst/extdata/test_float32.npy"), - n = 50 + n = 100 ) # float64 @@ -39,7 +39,7 @@ touchstone::benchmark_run( library(grumpy) }, read_float64 = read_npy("inst/extdata/test_float64.npy"), - n = 50 + n = 100 ) # int8 @@ -48,7 +48,7 @@ touchstone::benchmark_run( library(grumpy) }, read_int8 = read_npy("inst/extdata/test_int8.npy"), - n = 50 + n = 100 ) # int16 @@ -57,7 +57,7 @@ touchstone::benchmark_run( library(grumpy) }, read_int16 = read_npy("inst/extdata/test_int16.npy"), - n = 50 + n = 100 ) # int32 @@ -66,7 +66,7 @@ touchstone::benchmark_run( library(grumpy) }, read_int32 = read_npy("inst/extdata/test_int32.npy"), - n = 50 + n = 100 ) # string @@ -75,7 +75,7 @@ touchstone::benchmark_run( library(grumpy) }, read_string = read_npy("inst/extdata/test_str_bytes.npy"), - n = 50 + n = 100 ) # unicode @@ -84,7 +84,7 @@ touchstone::benchmark_run( library(grumpy) }, read_unicode = read_npy("inst/extdata/test_str_unicode.npy"), - n = 50 + n = 100 ) # uint8 @@ -93,7 +93,7 @@ touchstone::benchmark_run( library(grumpy) }, read_uint8 = read_npy("inst/extdata/test_uint8.npy"), - n = 50 + n = 100 ) # uint16 @@ -102,7 +102,7 @@ touchstone::benchmark_run( library(grumpy) }, read_uint16 = read_npy("inst/extdata/test_uint16.npy"), - n = 50 + n = 100 ) # uint32 @@ -111,7 +111,7 @@ touchstone::benchmark_run( library(grumpy) }, read_uint32 = read_npy("inst/extdata/test_uint32.npy"), - n = 50 + n = 100 ) # uint64 @@ -120,7 +120,7 @@ touchstone::benchmark_run( library(grumpy) }, read_uint64 = read_npy("inst/extdata/test_uint64.npy"), - n = 50 + n = 100 ) # More ---------- @@ -131,7 +131,7 @@ touchstone::benchmark_run( library(grumpy) }, read_npz = read_npz("inst/extdata/test.npz"), - n = 50 + n = 100 ) # bigendian @@ -140,7 +140,7 @@ touchstone::benchmark_run( library(grumpy) }, read_bigendian = read_npy("inst/extdata/test_bigendian.npy"), - n = 50 + n = 100 ) # empty @@ -149,7 +149,7 @@ touchstone::benchmark_run( library(grumpy) }, read_empty = read_npy("inst/extdata/test_empty.npy"), - n = 50 + n = 100 ) touchstone::benchmark_analyze() From bec61fe510da19cecaa9af2e28f3c9bb9de6d601 Mon Sep 17 00:00:00 2001 From: Hugo Gruson Date: Mon, 4 May 2026 11:07:49 +0200 Subject: [PATCH 3/8] Mark 'a' as a synonym for 'S' --- R/read_npy.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/R/read_npy.R b/R/read_npy.R index 6d74e36..f56c6b8 100644 --- a/R/read_npy.R +++ b/R/read_npy.R @@ -119,6 +119,7 @@ parse_npy_datatype <- function(descr) { "i" = "integer", "u" = "integer", "b" = "logical", + "a" = "string", "S" = "string", "U" = "unicode", stop("Unsupported data type in .npy file: ", descr[1]) @@ -130,6 +131,7 @@ parse_npy_datatype <- function(descr) { "i" = "int", "u" = "uint", "b" = "bool", + "a" = "string", "S" = "string", "U" = "unicode" ) @@ -140,6 +142,7 @@ parse_npy_datatype <- function(descr) { "i" = as.integer(descr[4]), "u" = as.integer(descr[4]), "b" = 1L, + "a" = as.integer(descr[4]), "S" = as.integer(descr[4]), "U" = as.integer(descr[4]) * 4L ) From 78b0bcafb8d814ce41a1c6b31560760c2af5010e Mon Sep 17 00:00:00 2001 From: Hugo Gruson Date: Mon, 4 May 2026 11:09:05 +0200 Subject: [PATCH 4/8] Mark ? as a synonym for b --- R/read_npy.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/R/read_npy.R b/R/read_npy.R index f56c6b8..5e51b92 100644 --- a/R/read_npy.R +++ b/R/read_npy.R @@ -118,6 +118,7 @@ parse_npy_datatype <- function(descr) { "f" = "numeric", "i" = "integer", "u" = "integer", + "?" = "logical", "b" = "logical", "a" = "string", "S" = "string", @@ -130,6 +131,7 @@ parse_npy_datatype <- function(descr) { "f" = "float", "i" = "int", "u" = "uint", + "?" = "bool", "b" = "bool", "a" = "string", "S" = "string", @@ -141,6 +143,7 @@ parse_npy_datatype <- function(descr) { "f" = as.integer(descr[4]), "i" = as.integer(descr[4]), "u" = as.integer(descr[4]), + "?" = 1L, "b" = 1L, "a" = as.integer(descr[4]), "S" = as.integer(descr[4]), From 8ded17d26174e24c91d2ef9988fae03662e06509 Mon Sep 17 00:00:00 2001 From: Hugo Gruson Date: Mon, 4 May 2026 11:32:33 +0200 Subject: [PATCH 5/8] Handle unicode in C --- R/read_npy.R | 48 ++++++++++++------------------------- src/grumpy.c | 1 + src/type_conversion.c | 55 +++++++++++++++++++++++++++++++++++++++++++ src/type_conversion.h | 1 + 4 files changed, 72 insertions(+), 33 deletions(-) diff --git a/R/read_npy.R b/R/read_npy.R index 5e51b92..b689854 100644 --- a/R/read_npy.R +++ b/R/read_npy.R @@ -163,41 +163,23 @@ parse_npy_datatype <- function(descr) { parse_npy_data <- function(bytes, shape, datatype, signed, typesize, endian) { num_elements <- prod(shape) - if (datatype == "unicode") { - ints <- readBin( - bytes, - what = "integer", - size = 4, - n = num_elements * typesize / 4, - endian = "little" - ) - tmp <- split( - ints, - f = ceiling(seq_along(ints) / (typesize / 4)) - ) - data <- vapply( - tmp, - intToUtf8, - FUN.VALUE = character(1), - USE.NAMES = FALSE - ) - } else { - # FIXME: optimize this - bytes <- readBin(bytes, "raw", n = num_elements * typesize) - if (!is.na(endian) && endian != .Platform$endian) { - ind <- rep_len(rev(seq_len(typesize)), length(bytes)) + - (seq_along(bytes) - 1L) %/% typesize * typesize - bytes <- bytes[ind] - } - - data <- .Call( - paste0("type_convert_", datatype), - bytes, - typesize, - PACKAGE = "grumpy" - ) + bytes <- readBin(bytes, "raw", n = num_elements * typesize) + # Unicode is UTF-32: each codepoint is 4 bytes, so swap per codepoint. + swap_unit <- if (datatype == "unicode") 4L else typesize + # FIXME: optimize this + if (!is.na(endian) && endian != .Platform$endian) { + ind <- rep_len(rev(seq_len(swap_unit)), length(bytes)) + + (seq_along(bytes) - 1L) %/% swap_unit * swap_unit + bytes <- bytes[ind] } + data <- .Call( + paste0("type_convert_", datatype), + bytes, + typesize, + PACKAGE = "grumpy" + ) + dim(data) <- shape return(data) diff --git a/src/grumpy.c b/src/grumpy.c index 3827888..6c78c09 100644 --- a/src/grumpy.c +++ b/src/grumpy.c @@ -7,6 +7,7 @@ static const R_CallMethodDef callMethods[] = { {"type_convert_float", (DL_FUNC) &type_convert_float, 2}, {"type_convert_bool", (DL_FUNC) &type_convert_bool, 2}, {"type_convert_string", (DL_FUNC) &type_convert_string, 2}, + {"type_convert_unicode", (DL_FUNC) &type_convert_unicode, 2}, {NULL, NULL, 0} }; diff --git a/src/type_conversion.c b/src/type_conversion.c index 237fbc2..5237adb 100644 --- a/src/type_conversion.c +++ b/src/type_conversion.c @@ -1,4 +1,5 @@ #include "type_conversion.h" +#include SEXP type_convert_int(SEXP input, SEXP _n_bytes) { @@ -174,6 +175,60 @@ SEXP type_convert_string(SEXP input, SEXP _n_bytes) { SET_STRING_ELT(data, i, mkCharCE(field, CE_BYTES)); } + UNPROTECT(1); + return(data); +} + +SEXP type_convert_unicode(SEXP input, SEXP _n_bytes) { + + // n_bytes is the total bytes per string element (num_codepoints * 4). + // The raw bytes have already been endian-swapped per codepoint (4 bytes each) + // before being passed here, so the encoding is UTF-32LE. + const size_t n_bytes = (size_t)INTEGER(_n_bytes)[0]; + const R_xlen_t length = xlength(input); + const char *raw_buffer = (const char *)RAW(input); + + const R_xlen_t data_length = length / n_bytes; + R_xlen_t i; + SEXP data; + + // Worst case: 4 UTF-8 bytes per UTF-32 codepoint. + char *utf8_buf = (char *)R_alloc(n_bytes + 1, 1); + + void *cd = Riconv_open("UTF-8", "UTF-32LE"); + if (cd == (void *)-1) + error("Riconv_open failed: cannot convert UTF-32LE to UTF-8"); + + data = PROTECT(allocVector(STRSXP, data_length)); + + for (i = 0; i < data_length; i++) { + const char *inbuf = raw_buffer + i * n_bytes; + + // Find the actual length: stop at the first null codepoint (4 zero bytes), + // or at n_bytes if there is no null terminator. + // This allows us to handle both fixed-length and null-terminated strings. + size_t field_bytes = 0; + while (field_bytes + 4 <= n_bytes) { + uint32_t cp; + memcpy(&cp, inbuf + field_bytes, 4); + if (cp == 0) break; + field_bytes += 4; + } + + size_t inbytesleft = field_bytes; + size_t outbytesleft = n_bytes; // safe upper bound + char *outbuf = utf8_buf; + + Riconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft); + *outbuf = '\0'; + + SET_STRING_ELT(data, i, mkCharCE(utf8_buf, CE_UTF8)); + + // Reset the converter state for the next element. + Riconv(cd, NULL, NULL, NULL, NULL); + } + + Riconv_close(cd); UNPROTECT(1); return(data); } \ No newline at end of file diff --git a/src/type_conversion.h b/src/type_conversion.h index 2d0d9d2..661be1c 100644 --- a/src/type_conversion.h +++ b/src/type_conversion.h @@ -8,3 +8,4 @@ SEXP type_convert_uint(SEXP input, SEXP _n_bytes); SEXP type_convert_float(SEXP input, SEXP _n_bytes); SEXP type_convert_bool(SEXP input, SEXP _n_bytes); SEXP type_convert_string(SEXP input, SEXP _n_bytes); +SEXP type_convert_unicode(SEXP input, SEXP _n_bytes); From 516b0b613ea800d4e20ded3d4e8637be48e690f1 Mon Sep 17 00:00:00 2001 From: Hugo Gruson Date: Mon, 4 May 2026 11:39:52 +0200 Subject: [PATCH 6/8] Use more complex test for unicode --- inst/extdata/test_str_unicode.npy | Bin 464 -> 1088 bytes inst/scripts/generate_test_data.py | 2 +- tests/testthat/test-read_npy.R | 24 ++++++++++++------------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/inst/extdata/test_str_unicode.npy b/inst/extdata/test_str_unicode.npy index 1bbfd798654361a2dad3051f0c78e8bdb47af9f4..134828402899800bad6974d2147f8ccd80d92f01 100644 GIT binary patch literal 1088 zcmbW#F-ikL6vpuv>_n`@QYMA0LXa4nVtMT|m-8XH}b)u=I%-Izvlik!eBSXd}R z3bFGBHVSr@9zg$}?t)E7$b(;I=FQAwHZ#3qd26SVvW{JQwIHg7-m3G~%DJqUao%w& zY=?gHuoczFp$C?Frz{mX#y!g4ONm~mYr!ywGQR?lpUi7X!EzEN0f-Yu^NcK*Bn;mVNbt`5m`r}4&zq&{H zLIw_cKgk`x)7L!=`c9^PLut#Te#;_(b^ H0i(nZjqX`r literal 464 zcmbV{y$ZrG6oupJQ)G)m7hME#c2itBI7zgr6>3wH`h((A_`;rp4YX1hFMK5T9CFXi zYZ)(A>z=rXhf0j!IW?7P7Ee?pRcc-3bg{86G1TWe_XfJpbZO8JM&oE0$*26k`l%4Y z*5GK>7M%vJrGA>=-8;M!v@^JMco{SU@)~A`y5ho~dGr$yihEEi0cWre#a%eF0!^L% ezSfR7Nz>>2u*>&b>df-s0GQ3)3cx@A%6kKS_%R6p diff --git a/inst/scripts/generate_test_data.py b/inst/scripts/generate_test_data.py index 57fd098..0def9c9 100644 --- a/inst/scripts/generate_test_data.py +++ b/inst/scripts/generate_test_data.py @@ -39,7 +39,7 @@ np.save("inst/extdata/test_empty.npy", np.array([], dtype="float32")) # String types (unicode and byte strings) -np.save("inst/extdata/test_str_unicode.npy", np.array(["foo", "bar", "baz", "qux", "hello", "world", "alpha", "beta", "gamma", "delta", "epsilon", "zeta"], dtype="U7")) +np.save("inst/extdata/test_str_unicode.npy", np.array(['¡Hola mundo!', 'Hej Världen!', 'Servus Woid!', 'Hei maailma!', 'Xin chào thế giới', 'Njatjeta Botë!', 'Γεια σου κόσμε!', 'こんにちは世界', '世界,你好!', 'Helló, világ!', 'Zdravo svete!', 'เฮลโลเวิลด์'], dtype="U20")) np.save("inst/extdata/test_str_bytes.npy", np.array([b"foo", b"bar", b"baz", b"qux", b"hello", b"world", b"alpha", b"beta", b"gamma", b"delta", b"eps", b"zeta"], dtype="S5")) # NPZ archive (multiple arrays) diff --git a/tests/testthat/test-read_npy.R b/tests/testthat/test-read_npy.R index e8d06f4..48b1278 100644 --- a/tests/testthat/test-read_npy.R +++ b/tests/testthat/test-read_npy.R @@ -187,18 +187,18 @@ test_that("string dtypes raise an informative error", { expect_no_condition() |> expect_identical(array( c( - "foo", - "bar", - "baz", - "qux", - "hello", - "world", - "alpha", - "beta", - "gamma", - "delta", - "epsilon", - "zeta" + '¡Hola mundo!', + 'Hej Världen!', + 'Servus Woid!', + 'Hei maailma!', + 'Xin chào thế giới', + 'Njatjeta Botë!', + 'Γεια σου κόσμε!', + 'こんにちは世界', + '世界,你好!', + 'Helló, világ!', + 'Zdravo svete!', + 'เฮลโลเวิลด์' ), dim = 12L )) From 7bc2b4682cc237b70ae6e087e16fbe763e9ed3d5 Mon Sep 17 00:00:00 2001 From: Hugo Gruson Date: Mon, 4 May 2026 12:51:35 +0200 Subject: [PATCH 7/8] Move string tests to the correct section --- tests/testthat/test-read_npy.R | 54 ++++++++++++++++------------------ 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/tests/testthat/test-read_npy.R b/tests/testthat/test-read_npy.R index 48b1278..5e1feb8 100644 --- a/tests/testthat/test-read_npy.R +++ b/tests/testthat/test-read_npy.R @@ -153,35 +153,7 @@ test_that("different types work", { ), dim = 12L )) -}) - -test_that("big endian files work", { - system.file("extdata", "test_bigendian.npy", package = "grumpy") |> - read_npy() |> - expect_no_condition() |> - expect_identical(array(as.double(1:12), dim = 12L)) -}) - -test_that("higher-dimension arrays work", { - system.file("extdata", "test_2d.npy", package = "grumpy") |> - read_npy() |> - expect_no_condition() |> - expect_identical(array(0:11, dim = c(3L, 4L))) - - system.file("extdata", "test_3d.npy", package = "grumpy") |> - read_npy() |> - expect_no_condition() |> - expect_identical(array(0:23, dim = c(2L, 3L, 4L))) -}) - -test_that("fortran order arrays work", { - system.file("extdata", "test_fortran.npy", package = "grumpy") |> - read_npy() |> - expect_no_condition() |> - expect_identical(matrix(0:11, nrow = 3L, ncol = 4L, byrow = TRUE)) -}) -test_that("string dtypes raise an informative error", { system.file("extdata", "test_str_unicode.npy", package = "grumpy") |> read_npy() |> expect_no_condition() |> @@ -225,6 +197,32 @@ test_that("string dtypes raise an informative error", { )) }) +test_that("big endian files work", { + system.file("extdata", "test_bigendian.npy", package = "grumpy") |> + read_npy() |> + expect_no_condition() |> + expect_identical(array(as.double(1:12), dim = 12L)) +}) + +test_that("higher-dimension arrays work", { + system.file("extdata", "test_2d.npy", package = "grumpy") |> + read_npy() |> + expect_no_condition() |> + expect_identical(array(0:11, dim = c(3L, 4L))) + + system.file("extdata", "test_3d.npy", package = "grumpy") |> + read_npy() |> + expect_no_condition() |> + expect_identical(array(0:23, dim = c(2L, 3L, 4L))) +}) + +test_that("fortran order arrays work", { + system.file("extdata", "test_fortran.npy", package = "grumpy") |> + read_npy() |> + expect_no_condition() |> + expect_identical(matrix(0:11, nrow = 3L, ncol = 4L, byrow = TRUE)) +}) + test_that("scalar or empty arrays work", { system.file("extdata", "test_scalar.npy", package = "grumpy") |> read_npy() |> From f1b834190832caa8e92fb749855d9a0e933e06a0 Mon Sep 17 00:00:00 2001 From: Hugo Gruson Date: Mon, 4 May 2026 12:54:04 +0200 Subject: [PATCH 8/8] Handle unicode endianness in C code --- R/read_npy.R | 30 +++++++++++++++++++----------- src/grumpy.c | 2 +- src/type_conversion.c | 11 +++++++---- src/type_conversion.h | 2 +- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/R/read_npy.R b/R/read_npy.R index b689854..dd8cda7 100644 --- a/R/read_npy.R +++ b/R/read_npy.R @@ -164,21 +164,29 @@ parse_npy_data <- function(bytes, shape, datatype, signed, typesize, endian) { num_elements <- prod(shape) bytes <- readBin(bytes, "raw", n = num_elements * typesize) - # Unicode is UTF-32: each codepoint is 4 bytes, so swap per codepoint. - swap_unit <- if (datatype == "unicode") 4L else typesize # FIXME: optimize this - if (!is.na(endian) && endian != .Platform$endian) { - ind <- rep_len(rev(seq_len(swap_unit)), length(bytes)) + - (seq_along(bytes) - 1L) %/% swap_unit * swap_unit + if (datatype != "unicode" && !is.na(endian) && endian != .Platform$endian) { + ind <- rep_len(rev(seq_len(typesize)), length(bytes)) + + (seq_along(bytes) - 1L) %/% typesize * typesize bytes <- bytes[ind] } - data <- .Call( - paste0("type_convert_", datatype), - bytes, - typesize, - PACKAGE = "grumpy" - ) + if (datatype == "unicode") { + data <- .Call( + "type_convert_unicode", + bytes, + typesize, + endian, + PACKAGE = "grumpy" + ) + } else { + data <- .Call( + paste0("type_convert_", datatype), + bytes, + typesize, + PACKAGE = "grumpy" + ) + } dim(data) <- shape diff --git a/src/grumpy.c b/src/grumpy.c index 6c78c09..ebe24fa 100644 --- a/src/grumpy.c +++ b/src/grumpy.c @@ -7,7 +7,7 @@ static const R_CallMethodDef callMethods[] = { {"type_convert_float", (DL_FUNC) &type_convert_float, 2}, {"type_convert_bool", (DL_FUNC) &type_convert_bool, 2}, {"type_convert_string", (DL_FUNC) &type_convert_string, 2}, - {"type_convert_unicode", (DL_FUNC) &type_convert_unicode, 2}, + {"type_convert_unicode", (DL_FUNC) &type_convert_unicode, 3}, {NULL, NULL, 0} }; diff --git a/src/type_conversion.c b/src/type_conversion.c index 5237adb..383cf7d 100644 --- a/src/type_conversion.c +++ b/src/type_conversion.c @@ -179,11 +179,11 @@ SEXP type_convert_string(SEXP input, SEXP _n_bytes) { return(data); } -SEXP type_convert_unicode(SEXP input, SEXP _n_bytes) { +SEXP type_convert_unicode(SEXP input, SEXP _n_bytes, SEXP _endian) { // n_bytes is the total bytes per string element (num_codepoints * 4). - // The raw bytes have already been endian-swapped per codepoint (4 bytes each) - // before being passed here, so the encoding is UTF-32LE. + // Bytes are passed as-is from the file; we select UTF-32LE or UTF-32BE + // based on the file's endianness so no R-side byte-swapping is needed. const size_t n_bytes = (size_t)INTEGER(_n_bytes)[0]; const R_xlen_t length = xlength(input); const char *raw_buffer = (const char *)RAW(input); @@ -192,10 +192,13 @@ SEXP type_convert_unicode(SEXP input, SEXP _n_bytes) { R_xlen_t i; SEXP data; + const char *endian = CHAR(STRING_ELT(_endian, 0)); + const char *utf32_enc = (strcmp(endian, "big") == 0) ? "UTF-32BE" : "UTF-32LE"; + // Worst case: 4 UTF-8 bytes per UTF-32 codepoint. char *utf8_buf = (char *)R_alloc(n_bytes + 1, 1); - void *cd = Riconv_open("UTF-8", "UTF-32LE"); + void *cd = Riconv_open("UTF-8", utf32_enc); if (cd == (void *)-1) error("Riconv_open failed: cannot convert UTF-32LE to UTF-8"); diff --git a/src/type_conversion.h b/src/type_conversion.h index 661be1c..bc0162c 100644 --- a/src/type_conversion.h +++ b/src/type_conversion.h @@ -8,4 +8,4 @@ SEXP type_convert_uint(SEXP input, SEXP _n_bytes); SEXP type_convert_float(SEXP input, SEXP _n_bytes); SEXP type_convert_bool(SEXP input, SEXP _n_bytes); SEXP type_convert_string(SEXP input, SEXP _n_bytes); -SEXP type_convert_unicode(SEXP input, SEXP _n_bytes); +SEXP type_convert_unicode(SEXP input, SEXP _n_bytes, SEXP _endian);