-
Notifications
You must be signed in to change notification settings - Fork 763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add 128bit integer support #173
Changes from 2 commits
a87ddea
9ffbae3
0b964b5
66183d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
// | ||
// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython | ||
|
||
use std::os::raw::c_long; | ||
use std::os::raw::{c_long, c_uchar, c_int}; | ||
|
||
extern crate num_traits; | ||
use self::num_traits::cast::cast; | ||
|
@@ -174,6 +174,56 @@ macro_rules! int_convert_u64_or_i64 ( | |
) | ||
); | ||
|
||
// for 128bit Integers | ||
macro_rules! int_convert_bignum ( | ||
($rust_type: ty, $byte_size: expr, $is_little_endian: expr, $is_signed: expr) => ( | ||
impl ToPyObject for $rust_type { | ||
#[inline] | ||
fn to_object(&self, py: Python) -> PyObject { | ||
self.into_object(py) | ||
} | ||
} | ||
impl IntoPyObject for $rust_type { | ||
fn into_object(self, py: Python) -> PyObject { | ||
unsafe { | ||
let bytes = ::std::mem::transmute::<_, [c_uchar; $byte_size]>(self); | ||
let obj = ffi::_PyLong_FromByteArray( | ||
bytes.as_ptr() as *const c_uchar, | ||
$byte_size, | ||
$is_little_endian, | ||
$is_signed, | ||
); | ||
PyObject::from_owned_ptr_or_panic(py, obj) | ||
} | ||
} | ||
} | ||
impl<'source> FromPyObject<'source> for $rust_type { | ||
#[cfg(target_endian = "little")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having the cfg attached to the method will make the compilation fail if when target_endian is big. If I understood the _PyLong_AsByteArray documentation correctly, extract should also work on big endian. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah it's just my mistake, thanks. |
||
fn extract(ob: &'source PyObjectRef) -> PyResult<$rust_type> { | ||
unsafe { | ||
let num = ffi::PyNumber_Index(ob.as_ptr()); | ||
if num.is_null() { | ||
return Err(PyErr::fetch(ob.py())); | ||
} | ||
let buffer: [c_uchar; $byte_size] = [0; $byte_size]; | ||
let ok = ffi::_PyLong_AsByteArray( | ||
ob.as_ptr() as *mut ffi::PyLongObject, | ||
buffer.as_ptr() as *const c_uchar, | ||
$byte_size, | ||
$is_little_endian, | ||
$is_signed, | ||
); | ||
if ok == -1 { | ||
Err(PyErr::fetch(ob.py())) | ||
} else { | ||
Ok(::std::mem::transmute::<_, $rust_type>(buffer)) | ||
} | ||
} | ||
} | ||
} | ||
) | ||
); | ||
|
||
|
||
int_fits_c_long!(i8); | ||
int_fits_c_long!(u8); | ||
|
@@ -204,6 +254,13 @@ int_fits_larger_int!(usize, u64); | |
// u64 has a manual implementation as it never fits into signed long | ||
int_convert_u64_or_i64!(u64, ffi::PyLong_FromUnsignedLongLong, ffi::PyLong_AsUnsignedLongLong); | ||
|
||
// manual implementation for 128bit integers | ||
#[cfg(target_endian = "little")] | ||
const IS_LITTLE_ENDIAN: c_int = 1; | ||
#[cfg(not(target_endian = "little"))] | ||
const IS_LITTLE_ENDIAN: c_int = 0; | ||
int_convert_bignum!(i128, 16, IS_LITTLE_ENDIAN, 1); | ||
int_convert_bignum!(u128, 16, IS_LITTLE_ENDIAN, 0); | ||
|
||
#[cfg(test)] | ||
mod test { | ||
|
@@ -236,6 +293,8 @@ mod test { | |
num_to_py_object_and_back!(to_from_u64, u64, u64); | ||
num_to_py_object_and_back!(to_from_isize, isize, isize); | ||
num_to_py_object_and_back!(to_from_usize, usize, usize); | ||
num_to_py_object_and_back!(to_from_i128, i128, i128); | ||
num_to_py_object_and_back!(to_from_u128, u128, u128); | ||
num_to_py_object_and_back!(float_to_i32, f64, i32); | ||
num_to_py_object_and_back!(float_to_u32, f64, u32); | ||
num_to_py_object_and_back!(float_to_i64, f64, i64); | ||
|
@@ -284,4 +343,47 @@ mod test { | |
assert_eq!(v, obj.extract::<u64>(py).unwrap()); | ||
assert!(obj.extract::<i64>(py).is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_i128_min() { | ||
let gil = Python::acquire_gil(); | ||
let py = gil.python(); | ||
let v = std::i128::MIN; | ||
let obj = v.to_object(py); | ||
assert_eq!(v, obj.extract::<i128>(py).unwrap()); | ||
assert!(obj.extract::<i64>(py).is_err()); | ||
assert!(obj.extract::<u128>(py).is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_u128_max() { | ||
let gil = Python::acquire_gil(); | ||
let py = gil.python(); | ||
let v = std::u128::MAX; | ||
let obj = v.to_object(py); | ||
assert_eq!(v, obj.extract::<u128>(py).unwrap()); | ||
assert!(obj.extract::<i128>(py).is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_u128_overflow() { | ||
use ffi; | ||
use object::PyObject; | ||
use objects::exc; | ||
use std::os::raw::c_uchar; | ||
let gil = Python::acquire_gil(); | ||
let py = gil.python(); | ||
let overflow_bytes: [c_uchar; 20] = [255; 20]; | ||
unsafe { | ||
let obj = ffi::_PyLong_FromByteArray( | ||
overflow_bytes.as_ptr() as *const c_uchar, | ||
20, | ||
super::IS_LITTLE_ENDIAN, | ||
0, | ||
); | ||
let obj = PyObject::from_owned_ptr_or_panic(py, obj); | ||
let err = obj.extract::<u128>(py).unwrap_err(); | ||
assert!(err.is_instance::<exc::OverflowError>(py)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
// | ||
// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython | ||
|
||
use std::os::raw::c_long; | ||
use std::os::raw::{c_long, c_uchar, c_int}; | ||
|
||
extern crate num_traits; | ||
use self::num_traits::cast::cast; | ||
|
@@ -138,6 +138,55 @@ macro_rules! int_convert_u64_or_i64 ( | |
) | ||
); | ||
|
||
// for 128bit Integers | ||
macro_rules! int_convert_bignum ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That macro and its tests should go in a common file for python 2 and 3. I know there's already some duplication between num2 and num3, but we shouldn't make it worse. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
👍 |
||
($rust_type: ty, $byte_size: expr, $is_little_endian: expr, $is_signed: expr) => ( | ||
impl ToPyObject for $rust_type { | ||
#[inline] | ||
fn to_object(&self, py: Python) -> PyObject { | ||
self.into_object(py) | ||
} | ||
} | ||
impl IntoPyObject for $rust_type { | ||
fn into_object(self, py: Python) -> PyObject { | ||
unsafe { | ||
let bytes = ::std::mem::transmute::<_, [c_uchar; $byte_size]>(self); | ||
let obj = ffi::_PyLong_FromByteArray( | ||
bytes.as_ptr() as *const c_uchar, | ||
$byte_size, | ||
$is_little_endian, | ||
$is_signed, | ||
); | ||
PyObject::from_owned_ptr_or_panic(py, obj) | ||
} | ||
} | ||
} | ||
impl<'source> FromPyObject<'source> for $rust_type { | ||
#[cfg(target_endian = "little")] | ||
fn extract(ob: &'source PyObjectRef) -> PyResult<$rust_type> { | ||
unsafe { | ||
let num = ffi::PyNumber_Index(ob.as_ptr()); | ||
if num.is_null() { | ||
return Err(PyErr::fetch(ob.py())); | ||
} | ||
let buffer: [c_uchar; $byte_size] = [0; $byte_size]; | ||
let ok = ffi::_PyLong_AsByteArray( | ||
ob.as_ptr() as *mut ffi::PyLongObject, | ||
buffer.as_ptr() as *const c_uchar, | ||
$byte_size, | ||
$is_little_endian, | ||
$is_signed, | ||
); | ||
if ok == -1 { | ||
Err(PyErr::fetch(ob.py())) | ||
} else { | ||
Ok(::std::mem::transmute::<_, $rust_type>(buffer)) | ||
} | ||
} | ||
} | ||
} | ||
) | ||
); | ||
|
||
int_fits_c_long!(i8); | ||
int_fits_c_long!(u8); | ||
|
@@ -168,6 +217,16 @@ int_fits_larger_int!(usize, u64); | |
// u64 has a manual implementation as it never fits into signed long | ||
int_convert_u64_or_i64!(u64, ffi::PyLong_FromUnsignedLongLong, ffi::PyLong_AsUnsignedLongLong); | ||
|
||
// manual implementation for 128bit integers | ||
#[cfg(target_endian = "little")] | ||
const IS_LITTLE_ENDIAN: c_int = 1; | ||
#[cfg(not(target_endian = "little"))] | ||
const IS_LITTLE_ENDIAN: c_int = 0; | ||
|
||
#[cfg(not(Py_LIMITED_API))] | ||
int_convert_bignum!(i128, 16, IS_LITTLE_ENDIAN, 1); | ||
#[cfg(not(Py_LIMITED_API))] | ||
int_convert_bignum!(u128, 16, IS_LITTLE_ENDIAN, 0); | ||
|
||
#[cfg(test)] | ||
mod test { | ||
|
@@ -224,6 +283,10 @@ mod test { | |
test_common!(u64, u64); | ||
test_common!(isize, isize); | ||
test_common!(usize, usize); | ||
#[cfg(not(Py_LIMITED_API))] | ||
test_common!(i128, i128); | ||
#[cfg(not(Py_LIMITED_API))] | ||
test_common!(u128, u128); | ||
|
||
#[test] | ||
fn test_u32_max() { | ||
|
@@ -267,4 +330,62 @@ mod test { | |
assert_eq!(v, obj.extract::<u64>(py).unwrap()); | ||
assert!(obj.extract::<i64>(py).is_err()); | ||
} | ||
|
||
#[test] | ||
#[cfg(not(Py_LIMITED_API))] | ||
fn test_i128_max() { | ||
let gil = Python::acquire_gil(); | ||
let py = gil.python(); | ||
let v = std::i128::MAX; | ||
let obj = v.to_object(py); | ||
assert_eq!(v, obj.extract::<i128>(py).unwrap()); | ||
assert_eq!(v as u128, obj.extract::<u128>(py).unwrap()); | ||
assert!(obj.extract::<u64>(py).is_err()); | ||
} | ||
|
||
#[test] | ||
#[cfg(not(Py_LIMITED_API))] | ||
fn test_i128_min() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those tests are great 👍 |
||
let gil = Python::acquire_gil(); | ||
let py = gil.python(); | ||
let v = std::i128::MIN; | ||
let obj = v.to_object(py); | ||
assert_eq!(v, obj.extract::<i128>(py).unwrap()); | ||
assert!(obj.extract::<i64>(py).is_err()); | ||
assert!(obj.extract::<u128>(py).is_err()); | ||
} | ||
|
||
#[test] | ||
#[cfg(not(Py_LIMITED_API))] | ||
fn test_u128_max() { | ||
let gil = Python::acquire_gil(); | ||
let py = gil.python(); | ||
let v = std::u128::MAX; | ||
let obj = v.to_object(py); | ||
assert_eq!(v, obj.extract::<u128>(py).unwrap()); | ||
assert!(obj.extract::<i128>(py).is_err()); | ||
} | ||
|
||
#[test] | ||
#[cfg(not(Py_LIMITED_API))] | ||
fn test_u128_overflow() { | ||
use ffi; | ||
use object::PyObject; | ||
use objects::exc; | ||
use std::os::raw::c_uchar; | ||
let gil = Python::acquire_gil(); | ||
let py = gil.python(); | ||
let overflow_bytes: [c_uchar; 20] = [255; 20]; | ||
unsafe { | ||
let obj = ffi::_PyLong_FromByteArray( | ||
overflow_bytes.as_ptr() as *const c_uchar, | ||
20, | ||
super::IS_LITTLE_ENDIAN, | ||
0, | ||
); | ||
let obj = PyObject::from_owned_ptr_or_panic(py, obj); | ||
let err = obj.extract::<u128>(py).unwrap_err(); | ||
assert!(err.is_instance::<exc::OverflowError>(py)); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The arguments should better be given their original names, such as
is_little_endian
, instead ofarg3
.