Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • Kazan-team/algebraics
1 result
Show changes
Commits on Source (16)
/target
**/*.rs.bk
Cargo.lock
__pycache__
*.pyc
/.vscode
\ No newline at end of file
......@@ -2,7 +2,7 @@
# See Notices.txt for copyright information
[package]
name = "algebraics"
version = "0.1.1"
version = "0.1.2"
authors = ["Jacob Lifshay <programmerjake@gmail.com>"]
edition = "2018"
license = "LGPL-2.1-or-later"
......@@ -12,6 +12,15 @@ repository = "https://salsa.debian.org/Kazan-team/algebraics"
readme = "README.md"
categories = ["algorithms", "data-structures", "science"]
[features]
default = []
python = ["pyo3"]
python-extension = ["python", "pyo3/extension-module"]
[lib]
name = "algebraics"
crate-type = ["rlib", "cdylib"]
[dependencies]
num-traits = "0.2"
num-bigint = "0.2"
......@@ -20,3 +29,8 @@ num-rational = "0.2"
rand = "0.5"
rand_pcg = "0.1.1"
lazy_static = "1.4"
[dependencies.pyo3]
version = "0.8.2"
optional = true
features = ["num-bigint"]
[Algebraic Numbers](https://en.wikipedia.org/wiki/Algebraic_number) Library
## [Algebraic Numbers](https://en.wikipedia.org/wiki/Algebraic_number) Library
Use when you need exact arithmetic, speed is not critical, and rational numbers aren't good enough.
Example:
## Example:
```rust
use algebraics::prelude::*;
use algebraics::RealAlgebraicNumber as Number;
......@@ -66,3 +67,32 @@ assert_eq!(
000000000000000000023"
)
```
## Python support
Using algebraics from Python:
```bash
python3 -m pip install algebraics==0.1.2
```
```python
from algebraics import RealAlgebraicNumber
sqrt_2 = 2 ** (RealAlgebraicNumber(1) / 2)
assert sqrt_2 * sqrt_2 == 2
```
Using algebraics in your own Rust project:
```toml
[dependencies.algebraics]
version = "0.1.2"
features = ["python"]
```
Developing algebraics:
```bash
cargo install maturin
maturin develop --cargo-extra-args="--features python-extension"
```
# SPDX-License-Identifier: LGPL-2.1-or-later
# See Notices.txt for copyright information
[build-system]
requires = ["maturin"]
build-backend = "maturin"
[tool.maturin]
bindings = "pyo3"
cargo-extra-args = "--features python-extension"
features = ["python-extension"]
......@@ -11,6 +11,7 @@ pub(crate) mod lattice;
pub mod mod_int;
pub mod polynomial;
pub mod prelude;
pub mod python;
pub(crate) mod quadratic_numbers;
pub mod traits;
pub mod util;
......
// SPDX-License-Identifier: LGPL-2.1-or-later
// See Notices.txt for copyright information
#![cfg(feature = "python")]
use crate::algebraic_numbers::RealAlgebraicNumber;
use crate::traits::ExactDivAssign;
use num_bigint::BigInt;
use num_traits::Signed;
use num_traits::Zero;
use pyo3::basic::CompareOp;
use pyo3::exceptions::TypeError;
use pyo3::exceptions::ValueError;
use pyo3::exceptions::ZeroDivisionError;
use pyo3::prelude::*;
use pyo3::types::PyAny;
use pyo3::PyNativeType;
use pyo3::PyNumberProtocol;
use pyo3::PyObjectProtocol;
use std::sync::Arc;
impl FromPyObject<'_> for RealAlgebraicNumber {
fn extract(value: &PyAny) -> PyResult<Self> {
Ok((*RealAlgebraicNumberPy::extract(value)?.value).clone())
}
}
impl<'a> FromPyObject<'a> for &'a RealAlgebraicNumber {
fn extract(value: &'a PyAny) -> PyResult<Self> {
let result = RealAlgebraicNumberPy::extract(value)?.value.clone();
let result: &'a _ = value.py().register_any(result);
Ok(&**result)
}
}
impl IntoPy<PyObject> for RealAlgebraicNumber {
fn into_py(self, py: Python) -> PyObject {
RealAlgebraicNumberPy {
value: Arc::new(self),
}
.into_py(py)
}
}
impl IntoPy<PyObject> for &'_ RealAlgebraicNumber {
fn into_py(self, py: Python) -> PyObject {
RealAlgebraicNumberPy {
value: Arc::new(self.clone()),
}
.into_py(py)
}
}
impl ToPyObject for RealAlgebraicNumber {
fn to_object(&self, py: Python) -> PyObject {
self.into_py(py)
}
}
#[pyclass(name=RealAlgebraicNumber, module="algebraics")]
#[derive(Clone)]
struct RealAlgebraicNumberPy {
value: Arc<RealAlgebraicNumber>,
}
impl FromPyObject<'_> for RealAlgebraicNumberPy {
fn extract(value: &PyAny) -> PyResult<Self> {
if let Ok(value) = value.downcast_ref::<RealAlgebraicNumberPy>() {
return Ok(value.clone());
}
let value = value.extract::<Option<BigInt>>()?;
let value = match value {
None => RealAlgebraicNumber::zero(),
Some(value) => RealAlgebraicNumber::from(value),
}
.into();
Ok(RealAlgebraicNumberPy { value })
}
}
#[pymethods(PyObjectProtocol, PyNumberProtocol)]
impl RealAlgebraicNumberPy {
#[new]
fn pynew(obj: &PyRawObject, value: Option<RealAlgebraicNumberPy>) {
obj.init(value.unwrap_or_else(|| RealAlgebraicNumberPy {
value: RealAlgebraicNumber::zero().into(),
}));
}
fn __trunc__(&self) -> BigInt {
let gil = Python::acquire_gil();
let py = gil.python();
py.allow_threads(|| self.value.to_integer_trunc())
}
fn __floor__(&self) -> BigInt {
let gil = Python::acquire_gil();
let py = gil.python();
py.allow_threads(|| self.value.to_integer_floor())
}
fn __ceil__(&self) -> BigInt {
let gil = Python::acquire_gil();
let py = gil.python();
py.allow_threads(|| self.value.to_integer_ceil())
}
fn to_integer(&self) -> Option<BigInt> {
self.value.to_integer()
}
fn to_rational(&self) -> Option<(BigInt, BigInt)> {
self.value.to_rational().map(Into::into)
}
#[getter]
fn minimal_polynomial(&self) -> Vec<BigInt> {
self.value.minimal_polynomial().iter().collect()
}
#[getter]
fn degree(&self) -> usize {
self.value.degree()
}
fn is_rational(&self) -> bool {
self.value.is_rational()
}
fn is_integer(&self) -> bool {
self.value.is_integer()
}
fn recip(&self) -> PyResult<RealAlgebraicNumberPy> {
Python::acquire_gil()
.python()
.allow_threads(|| {
Some(RealAlgebraicNumberPy {
value: self.value.checked_recip()?.into(),
})
})
.ok_or_else(get_div_by_zero_error)
}
/// returns `floor(log2(self))`
fn floor_log2(&self) -> PyResult<i64> {
Python::acquire_gil()
.python()
.allow_threads(|| self.value.checked_floor_log2())
.ok_or_else(get_floor_ceil_log2_error)
}
/// returns `ceil(log2(self))`
fn ceil_log2(&self) -> PyResult<i64> {
Python::acquire_gil()
.python()
.allow_threads(|| self.value.checked_ceil_log2())
.ok_or_else(get_floor_ceil_log2_error)
}
}
#[pyproto]
impl PyObjectProtocol for RealAlgebraicNumberPy {
fn __repr__(&self) -> PyResult<String> {
Ok(format!("<{:?}>", self.value))
}
fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult<bool> {
let py = other.py();
let other = other.extract::<RealAlgebraicNumberPy>()?;
Ok(py.allow_threads(|| match op {
CompareOp::Lt => self.value < other.value,
CompareOp::Le => self.value <= other.value,
CompareOp::Eq => self.value == other.value,
CompareOp::Ne => self.value != other.value,
CompareOp::Gt => self.value > other.value,
CompareOp::Ge => self.value >= other.value,
}))
}
}
fn get_div_by_zero_error() -> PyErr {
ZeroDivisionError::py_err("can't divide RealAlgebraicNumber by zero")
}
fn get_floor_ceil_log2_error() -> PyErr {
ValueError::py_err("can't extract base-2 logarithm of zero or negative RealAlgebraicNumber")
}
#[pyproto]
impl PyNumberProtocol for RealAlgebraicNumberPy {
fn __add__(lhs: &PyAny, rhs: RealAlgebraicNumberPy) -> PyResult<RealAlgebraicNumberPy> {
let py = lhs.py();
let mut lhs = lhs.extract::<RealAlgebraicNumberPy>()?;
Ok(py.allow_threads(|| {
*Arc::make_mut(&mut lhs.value) += &*rhs.value;
lhs
}))
}
fn __sub__(lhs: &PyAny, rhs: RealAlgebraicNumberPy) -> PyResult<RealAlgebraicNumberPy> {
let py = lhs.py();
let mut lhs = lhs.extract::<RealAlgebraicNumberPy>()?;
Ok(py.allow_threads(|| {
*Arc::make_mut(&mut lhs.value) -= &*rhs.value;
lhs
}))
}
fn __mul__(lhs: &PyAny, rhs: RealAlgebraicNumberPy) -> PyResult<RealAlgebraicNumberPy> {
let py = lhs.py();
let mut lhs = lhs.extract::<RealAlgebraicNumberPy>()?;
Ok(py.allow_threads(|| {
*Arc::make_mut(&mut lhs.value) *= &*rhs.value;
lhs
}))
}
fn __truediv__(lhs: &PyAny, rhs: RealAlgebraicNumberPy) -> PyResult<RealAlgebraicNumberPy> {
let py = lhs.py();
let mut lhs = lhs.extract::<RealAlgebraicNumberPy>()?;
py.allow_threads(|| -> Result<RealAlgebraicNumberPy, ()> {
Arc::make_mut(&mut lhs.value).checked_exact_div_assign(&*rhs.value)?;
Ok(lhs)
})
.map_err(|()| get_div_by_zero_error())
}
fn __pow__(
lhs: RealAlgebraicNumberPy,
rhs: RealAlgebraicNumberPy,
modulus: &PyAny,
) -> PyResult<RealAlgebraicNumberPy> {
let py = modulus.py();
if !modulus.is_none() {
return Err(TypeError::py_err(
"3 argument pow() not allowed for RealAlgebraicNumber",
));
}
py.allow_threads(|| -> Result<RealAlgebraicNumberPy, &'static str> {
if let Some(rhs) = rhs.value.to_rational() {
Ok(RealAlgebraicNumberPy {
value: lhs
.value
.checked_pow(rhs)
.ok_or("pow() failed for RealAlgebraicNumber")?
.into(),
})
} else {
Err("exponent must be rational for RealAlgebraicNumber")
}
})
.map_err(ValueError::py_err)
}
// Unary arithmetic
fn __neg__(&self) -> PyResult<RealAlgebraicNumberPy> {
Ok(Python::acquire_gil()
.python()
.allow_threads(|| RealAlgebraicNumberPy {
value: Arc::from(-&*self.value),
}))
}
fn __abs__(&self) -> PyResult<RealAlgebraicNumberPy> {
Ok(Python::acquire_gil()
.python()
.allow_threads(|| RealAlgebraicNumberPy {
value: self.value.abs().into(),
}))
}
}
#[pymodule]
fn algebraics(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<RealAlgebraicNumberPy>()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn conversion_compile_test() {
#![allow(dead_code)]
#[pyfunction]
fn identity_ref_result(v: &RealAlgebraicNumber) -> PyResult<&RealAlgebraicNumber> {
Ok(v)
}
#[pyfunction]
fn identity_result(v: RealAlgebraicNumber) -> PyResult<RealAlgebraicNumber> {
Ok(v)
}
#[pyfunction]
fn identity_ref(v: &RealAlgebraicNumber) -> &RealAlgebraicNumber {
v
}
#[pyfunction]
fn identity(v: RealAlgebraicNumber) -> RealAlgebraicNumber {
v
}
}
}
# SPDX-License-Identifier: LGPL-2.1-or-later
# See Notices.txt for copyright information
# SPDX-License-Identifier: LGPL-2.1-or-later
# See Notices.txt for copyright information
from algebraics import RealAlgebraicNumber
import unittest
import math
class TestRealAlgebraicNumber(unittest.TestCase):
def test_construct(self):
self.assertEqual(repr(RealAlgebraicNumber()),
"<RealAlgebraicNumber { minimal_polynomial: 0 + 1*X, "
"interval: DyadicFractionInterval { "
"lower_bound_numer: 0, upper_bound_numer: 0, "
"log2_denom: 0 } }>")
self.assertEqual(repr(RealAlgebraicNumber(42)),
"<RealAlgebraicNumber { "
"minimal_polynomial: -42 + 1*X, "
"interval: DyadicFractionInterval { "
"lower_bound_numer: 42, upper_bound_numer: 42, "
"log2_denom: 0 } }>")
self.assertEqual(repr(RealAlgebraicNumber(-5)),
"<RealAlgebraicNumber { "
"minimal_polynomial: 5 + 1*X, "
"interval: DyadicFractionInterval { "
"lower_bound_numer: -5, upper_bound_numer: -5, "
"log2_denom: 0 } }>")
self.assertEqual(repr(RealAlgebraicNumber(RealAlgebraicNumber(-5))),
"<RealAlgebraicNumber { "
"minimal_polynomial: 5 + 1*X, "
"interval: DyadicFractionInterval { "
"lower_bound_numer: -5, upper_bound_numer: -5, "
"log2_denom: 0 } }>")
def test_trunc(self):
self.assertEqual(math.trunc(RealAlgebraicNumber(123)), 123)
self.assertEqual(math.trunc(RealAlgebraicNumber(123) / 10), 12)
self.assertEqual(math.trunc(RealAlgebraicNumber(128) / 10), 12)
self.assertEqual(math.trunc(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2)), 11)
self.assertEqual(math.trunc(RealAlgebraicNumber(-123)), -123)
self.assertEqual(math.trunc(RealAlgebraicNumber(-123) / 10), -12)
self.assertEqual(math.trunc(RealAlgebraicNumber(-128) / 10), -12)
self.assertEqual(math.trunc(-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2))), -11)
def test_floor(self):
self.assertEqual(math.floor(RealAlgebraicNumber(123)), 123)
self.assertEqual(math.floor(RealAlgebraicNumber(123) / 10), 12)
self.assertEqual(math.floor(RealAlgebraicNumber(128) / 10), 12)
self.assertEqual(math.floor(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2)), 11)
self.assertEqual(math.floor(RealAlgebraicNumber(-123)), -123)
self.assertEqual(math.floor(RealAlgebraicNumber(-123) / 10), -13)
self.assertEqual(math.floor(RealAlgebraicNumber(-128) / 10), -13)
self.assertEqual(math.floor(-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2))), -12)
def test_ceil(self):
self.assertEqual(math.ceil(RealAlgebraicNumber(123)), 123)
self.assertEqual(math.ceil(RealAlgebraicNumber(123) / 10), 13)
self.assertEqual(math.ceil(RealAlgebraicNumber(128) / 10), 13)
self.assertEqual(math.ceil(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2)), 12)
self.assertEqual(math.ceil(RealAlgebraicNumber(-123)), -123)
self.assertEqual(math.ceil(RealAlgebraicNumber(-123) / 10), -12)
self.assertEqual(math.ceil(RealAlgebraicNumber(-128) / 10), -12)
self.assertEqual(math.ceil(-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2))), -11)
def test_to_integer(self):
self.assertEqual(RealAlgebraicNumber(123).to_integer(), 123)
self.assertEqual((RealAlgebraicNumber(123) / 10).to_integer(), None)
self.assertEqual((RealAlgebraicNumber(128) / 10).to_integer(), None)
self.assertEqual((RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2)).to_integer(), None)
self.assertEqual(RealAlgebraicNumber(-123).to_integer(), -123)
self.assertEqual((RealAlgebraicNumber(-123) / 10).to_integer(), None)
self.assertEqual((RealAlgebraicNumber(-128) / 10).to_integer(), None)
self.assertEqual((-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2))
).to_integer(), None)
def test_to_rational(self):
self.assertEqual(RealAlgebraicNumber(123).to_rational(), (123, 1))
self.assertEqual((RealAlgebraicNumber(123) / 10).to_rational(),
(123, 10))
self.assertEqual((RealAlgebraicNumber(128) / 10).to_rational(),
(64, 5))
self.assertEqual((RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2)).to_rational(), None)
self.assertEqual(RealAlgebraicNumber(-123).to_rational(), (-123, 1))
self.assertEqual((RealAlgebraicNumber(-123) / 10).to_rational(),
(-123, 10))
self.assertEqual((RealAlgebraicNumber(-128) / 10).to_rational(),
(-64, 5))
self.assertEqual((-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2))
).to_rational(), None)
def test_minimal_polynomial(self):
self.assertEqual(RealAlgebraicNumber(123).minimal_polynomial,
[-123, 1])
self.assertEqual((RealAlgebraicNumber(123) / 10).minimal_polynomial,
[-123, 10])
self.assertEqual((RealAlgebraicNumber(128) / 10).minimal_polynomial,
[-64, 5])
self.assertEqual((RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2)).minimal_polynomial,
[-123, 0, 1])
self.assertEqual(RealAlgebraicNumber(-123).minimal_polynomial,
[123, 1])
self.assertEqual((RealAlgebraicNumber(-123) / 10).minimal_polynomial,
[123, 10])
self.assertEqual((RealAlgebraicNumber(-128) / 10).minimal_polynomial,
[64, 5])
self.assertEqual((-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2))
).minimal_polynomial,
[-123, 0, 1])
def test_degree(self):
self.assertEqual(RealAlgebraicNumber(123).degree, 1)
self.assertEqual((RealAlgebraicNumber(123) / 10).degree, 1)
self.assertEqual((RealAlgebraicNumber(128) / 10).degree, 1)
self.assertEqual((RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2)).degree, 2)
self.assertEqual(RealAlgebraicNumber(-123).degree, 1)
self.assertEqual((RealAlgebraicNumber(-123) / 10).degree, 1)
self.assertEqual((RealAlgebraicNumber(-128) / 10).degree, 1)
self.assertEqual((-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2))
).degree, 2)
self.assertEqual((-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 3))
).degree, 3)
self.assertEqual((-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 4))
).degree, 4)
def test_is_integer(self):
self.assertEqual(RealAlgebraicNumber(123).is_integer(), True)
self.assertEqual((RealAlgebraicNumber(123) / 10).is_integer(), False)
self.assertEqual((RealAlgebraicNumber(128) / 10).is_integer(), False)
self.assertEqual((RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2)).is_integer(), False)
self.assertEqual(RealAlgebraicNumber(-123).is_integer(), True)
self.assertEqual((RealAlgebraicNumber(-123) / 10).is_integer(), False)
self.assertEqual((RealAlgebraicNumber(-128) / 10).is_integer(), False)
self.assertEqual((-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2))
).is_integer(), False)
def test_is_rational(self):
self.assertEqual(RealAlgebraicNumber(123).is_rational(), True)
self.assertEqual((RealAlgebraicNumber(123) / 10).is_rational(), True)
self.assertEqual((RealAlgebraicNumber(128) / 10).is_rational(), True)
self.assertEqual((RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2)).is_rational(),
False)
self.assertEqual(RealAlgebraicNumber(-123).is_rational(), True)
self.assertEqual((RealAlgebraicNumber(-123) / 10).is_rational(), True)
self.assertEqual((RealAlgebraicNumber(-128) / 10).is_rational(), True)
self.assertEqual((-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2))
).is_rational(), False)
def test_recip(self):
self.assertEqual(RealAlgebraicNumber(123).recip().minimal_polynomial,
[-1, 123])
self.assertEqual((RealAlgebraicNumber(123) / 10
).recip().minimal_polynomial,
[-10, 123])
self.assertEqual((RealAlgebraicNumber(128) / 10
).recip().minimal_polynomial,
[-5, 64])
self.assertEqual((-(RealAlgebraicNumber(123)
** (RealAlgebraicNumber(1) / 2))
).recip().minimal_polynomial,
[1, 0, -123])
def test_recip_zero(self):
with self.assertRaises(ZeroDivisionError):
RealAlgebraicNumber(0).recip()
def test_add(self):
self.assertEqual(RealAlgebraicNumber(1) + 2, 3)
self.assertEqual(1 + RealAlgebraicNumber(2), 3)
self.assertEqual(RealAlgebraicNumber(1) + RealAlgebraicNumber(2), 3)
def test_sub(self):
self.assertEqual(RealAlgebraicNumber(1) - 2, -1)
self.assertEqual(1 - RealAlgebraicNumber(2), -1)
self.assertEqual(RealAlgebraicNumber(1) - RealAlgebraicNumber(2), -1)
def test_mul(self):
self.assertEqual(RealAlgebraicNumber(1) * 2, 2)
self.assertEqual(1 * RealAlgebraicNumber(2), 2)
self.assertEqual(RealAlgebraicNumber(1) * RealAlgebraicNumber(2), 2)
def test_div(self):
self.assertEqual(RealAlgebraicNumber(1) / 2,
RealAlgebraicNumber(1) / 2)
self.assertEqual(1 / RealAlgebraicNumber(2),
RealAlgebraicNumber(1) / 2)
self.assertEqual(RealAlgebraicNumber(1) / RealAlgebraicNumber(2),
RealAlgebraicNumber(1) / 2)
def test_div_zero(self):
with self.assertRaises(ZeroDivisionError):
RealAlgebraicNumber(1) / 0
with self.assertRaises(ZeroDivisionError):
RealAlgebraicNumber(-1) / 0
with self.assertRaises(ZeroDivisionError):
RealAlgebraicNumber(0) / 0
def test_pow(self):
self.assertEqual(RealAlgebraicNumber(1) ** 2, 1)
self.assertEqual(1 ** RealAlgebraicNumber(2), 1)
self.assertEqual(RealAlgebraicNumber(1) ** RealAlgebraicNumber(2), 1)
def test_neg(self):
self.assertEqual(-RealAlgebraicNumber(1), -1)
self.assertEqual(-RealAlgebraicNumber(-2), 2)
def test_abs(self):
self.assertEqual(abs(RealAlgebraicNumber(1)), 1)
self.assertEqual(abs(RealAlgebraicNumber(-2)), 2)
def test_floor_ceil_log2(self):
with self.assertRaises(ValueError):
RealAlgebraicNumber(0).floor_log2()
with self.assertRaises(ValueError):
RealAlgebraicNumber(0).ceil_log2()
with self.assertRaises(ValueError):
RealAlgebraicNumber(-1).floor_log2()
with self.assertRaises(ValueError):
RealAlgebraicNumber(-1).ceil_log2()
self.assertEqual(RealAlgebraicNumber(1).floor_log2(), 0)
self.assertEqual(RealAlgebraicNumber(1).ceil_log2(), 0)
self.assertEqual(RealAlgebraicNumber(2).floor_log2(), 1)
self.assertEqual(RealAlgebraicNumber(2).ceil_log2(), 1)
self.assertEqual(RealAlgebraicNumber(3).floor_log2(), 1)
self.assertEqual(RealAlgebraicNumber(3).ceil_log2(), 2)
self.assertEqual(RealAlgebraicNumber(4).floor_log2(), 2)
self.assertEqual(RealAlgebraicNumber(4).ceil_log2(), 2)
self.assertEqual((RealAlgebraicNumber(1) / 4).floor_log2(), -2)
self.assertEqual((RealAlgebraicNumber(1) / 4).ceil_log2(), -2)
self.assertEqual((RealAlgebraicNumber(1) / 3).floor_log2(), -2)
self.assertEqual((RealAlgebraicNumber(1) / 3).ceil_log2(), -1)
if __name__ == '__main__':
unittest.main()