blob: 0ec9b32c63e5e4379975acd82de3bda1ef418b32 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "big_integer.h"
#include "detail/bytes.h"
#include <array>
#include <cassert>
#include <memory>
#include <sstream>
namespace ignite {
big_integer::big_integer(const int8_t *val, int32_t len, int8_t sign, bool big_endian) {
assert(val != nullptr);
assert(len >= 0);
assert(sign == detail::mpi_sign::POSITIVE || sign == 0 || sign == detail::mpi_sign::NEGATIVE);
m_mpi.read(reinterpret_cast<const std::uint8_t *>(val), len, big_endian);
m_mpi.set_sign(sign >= 0 ? detail::mpi_sign::POSITIVE : detail::mpi_sign::NEGATIVE);
}
big_integer::big_integer(const std::byte *data, std::size_t size) {
auto ptr = reinterpret_cast<const std::uint8_t *>(data);
m_mpi.read(ptr, size);
if (ptr[0] & 0x80) {
mpi_t::word carry = 1;
for (auto &word : m_mpi.magnitude()) {
// Invert word and add carry if number is negative because it should be in two's complement form.
word = ~word + carry;
if (word != 0) {
carry = 0;
}
}
std::size_t extra_bytes = size % 4;
if(extra_bytes) {
mpi_t::word mask = 0xFFFFFFFF >> 8 * (4 - extra_bytes);
m_mpi.magnitude().back() &= mask;
}
m_mpi.make_negative();
}
}
void big_integer::assign_int64(int64_t val) {
if (val < 0) {
assign_uint64(val > INT64_MIN ? static_cast<uint64_t>(-val) : static_cast<uint64_t>(val));
m_mpi.make_negative();
} else {
assign_uint64(static_cast<uint64_t>(val));
}
}
void big_integer::assign_uint64(uint64_t x) {
m_mpi.reinit();
word_t val[2];
val[0] = static_cast<word_t>(x);
val[1] = static_cast<word_t>(x >> 32);
if (val[1] == 0) {
m_mpi.grow(1);
m_mpi.magnitude()[0] = val[0];
} else {
m_mpi.grow(2);
m_mpi.magnitude()[0] = val[0];
m_mpi.magnitude()[1] = val[1];
}
}
void big_integer::assign_string(const std::string &val) {
m_mpi.assign_from_string(val.c_str());
}
void big_integer::assign_string(const char *val, std::size_t len) {
assign_string({val, len});
}
std::uint32_t big_integer::magnitude_bit_length() const noexcept {
return m_mpi.magnitude_bit_length();
}
std::uint32_t big_integer::bit_length() const noexcept {
auto res = magnitude_bit_length();
if (res == 0)
return 1;
auto view = get_magnitude();
if (is_negative()) {
// Check if the magnitude is a power of 2.
auto last = view.back();
if ((last & (last - 1)) == 0) {
bool all_zero = true;
for (auto i = std::int64_t(view.size_words() - 2); i > 0; i--) {
if (view[i] != 0) {
all_zero = false;
break;
}
}
if (all_zero) {
res--;
}
}
}
return res;
}
std::size_t big_integer::byte_size() const noexcept {
// This includes a sign bit.
return bit_length() / 8u + 1u;
}
void big_integer::store_bytes(std::byte *data) const {
if (m_mpi.length() == 0) {
data[0] = std::byte{0};
return;
}
auto size = byte_size();
m_mpi.write(reinterpret_cast<std::uint8_t *>(data), size);
if (is_negative()) {
mpi_t::word carry = 1;
for (std::size_t i = size; i > 0; i--) {
data[i - 1] = std::byte(std::uint8_t(~data[i - 1]) + carry);
if (data[i - 1] != std::byte(0)) {
carry = 0;
}
}
}
}
int32_t big_integer::get_precision() const noexcept {
// See http://graphics.stanford.edu/~seander/bithacks.html
// for the details on the algorithm.
if (m_mpi.is_zero())
return 1;
auto r = int32_t(uint32_t(((static_cast<uint64_t>(magnitude_bit_length()) + 1) * 646456993) >> 31));
big_integer prec;
big_integer::get_power_of_ten(r, prec);
auto cmp = compare(prec, true);
return cmp < 0 ? r : r + 1;
}
void big_integer::pow(int32_t exp) {
mpi_t result(1);
while (exp > 0) {
if (exp & 1) {
result.multiply(m_mpi);
}
m_mpi.multiply(m_mpi);
exp >>= 1;
}
m_mpi = result;
}
void big_integer::multiply(const big_integer &other, big_integer &res) const {
res = m_mpi * other.m_mpi;
}
void big_integer::divide(const big_integer &divisor, big_integer &res) const {
divide(divisor, res, nullptr);
}
void big_integer::divide(const big_integer &divisor, big_integer &res, big_integer &rem) const {
divide(divisor, res, &rem);
}
void big_integer::add(const big_integer &other, big_integer &res) const {
res = m_mpi + other.m_mpi;
}
void big_integer::subtract(const big_integer &other, big_integer &res) const {
res = m_mpi - other.m_mpi;
}
void big_integer::add(uint64_t x) {
if (x == 0)
return;
if (is_zero()) {
assign_uint64(x);
return;
}
std::uint32_t val[2];
val[0] = static_cast<word_t>(x);
val[1] = static_cast<word_t>(x >> 32);
if (val[1] == 0) {
m_mpi.grow(1);
m_mpi.magnitude()[0] = val[0];
} else {
m_mpi.grow(2);
m_mpi.magnitude()[0] = val[0];
m_mpi.magnitude()[1] = val[1];
}
}
int big_integer::compare(const big_integer &other, bool ignore_sign) const {
return m_mpi.compare(other.m_mpi, ignore_sign);
}
int64_t big_integer::to_int64() const {
auto mag = m_mpi.magnitude();
std::uint64_t high = mag.size_words() > 1 ? mag[1] : 0;
std::uint64_t low = mag.size_words() > 0 ? mag[0] : 0;
return m_mpi.sign() * int64_t(high << 32 | low);
}
void big_integer::get_power_of_ten(std::int32_t pow, big_integer &res) {
assert(pow >= 0);
res.assign_uint64(10);
res.pow(pow);
}
void big_integer::divide(const big_integer &divisor, big_integer &res, big_integer *rem) const {
if (rem) {
res = m_mpi.div_and_mod(divisor.m_mpi, rem->m_mpi);
} else {
res = m_mpi / divisor.m_mpi;
}
}
std::string big_integer::to_string() const {
return m_mpi.to_string();
}
std::ostream &operator<<(std::ostream &os, const big_integer &val) {
return os << val.to_string();
}
std::istream &operator>>(std::istream &is, big_integer &val) {
std::istream::sentry sentry(is);
// Return zero if input failed.
val.assign_int64(0);
// Current char.
int c = is.peek();
if (!is) {
return is;
}
std::string str;
while (is && (isdigit(c) || c == '-' || c == '+')) {
str.push_back(c);
is.ignore();
c = is.peek();
}
val.m_mpi.assign_from_string(str.c_str());
return is;
}
void swap(big_integer &lhs, big_integer &rhs) {
using std::swap;
std::swap(lhs.m_mpi, rhs.m_mpi);
}
} // namespace ignite