Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions hazelcast/include/hazelcast/client/big_decimal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2008-2022, Hazelcast, Inc. All Rights Reserved.
*
* Licensed 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.
*/
#pragma once

#include <vector>
#include <boost/multiprecision/cpp_int.hpp>
#include "hazelcast/util/export.h"

#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
#pragma warning(push)
#pragma warning(disable : 4251) // for dll export
#endif

namespace hazelcast {
namespace client {
/**
* An arbitrary precision and scale floating point number.
* unscaledValue x 10 ^ -scale
*
* For arithmetic operations support, it is suggested to use external
* libraries. An usage example with boost::multiprecision::cpp_dec_float
* could be as follows:
* <pre><code>
* hazelcast::big_decimal dec{ u, 2 };
* boost::multiprecision::cpp_dec_float<10> f(
* (dec.unscaled.str() + "e-" + std::to_string(dec.scale)).c_str());
* std::cout << f.str(100, std::ios_base::dec) << std::endl;
* </code></pre>
*/
struct HAZELCAST_API big_decimal
{
boost::multiprecision::cpp_int unscaled;
int32_t scale;
};

bool HAZELCAST_API
operator==(const big_decimal& lhs, const big_decimal& rhs);

bool HAZELCAST_API
operator<(const big_decimal& lhs, const big_decimal& rhs);
} // namespace client
} // namespace hazelcast
namespace std {
template<>
struct HAZELCAST_API hash<hazelcast::client::big_decimal>
{
std::size_t operator()(const hazelcast::client::big_decimal& f) const;
};
} // namespace std
namespace hazelcast {
namespace client {
namespace pimpl {

/**
* Takes twos complement of given array where most significant value is first.
* This method modifies the vector in place.
* @param a the array to take twos complement of
*/

void HAZELCAST_API
twos_complement(std::vector<int8_t>& a);

/**
* Creates a cpp_int from a vector of int8_t respecting the sign.
*
* boost::import_bits does not respect the sign, so we have to do it manually.
* if v represents a negative number, we take the two's complement of it,
* to get positive representation of the same number. Then we add the sign
* at the end.
* @param v int8_t array to read from
* @return cpp_int
*/
boost::multiprecision::cpp_int HAZELCAST_API
from_bytes(std::vector<int8_t> v);
/**
* Creates a twos complement byte array from cpp_int respecting the sign.
*
* boost::export_bits does not respect the sign, so we have to do it manually.
* if i is a negative number, we take the two's complement on resulting vector,
* to get negative representation of the number.
* We also add one extra byte to the end of the vector to preserve the sign if
* sign of the integer is not the same as the most significant byte's sign.
* Otherwise we don't add it to have minimum size vector to represent the value.
* @param i the number to convert to bytes
* @return the vector of int8_t representing the number
*/
std::vector<int8_t> HAZELCAST_API
to_bytes(const boost::multiprecision::cpp_int& i);
} // namespace pimpl
} // namespace client
} // namespace hazelcast
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
#pragma warning(pop)
#endif
85 changes: 84 additions & 1 deletion hazelcast/src/hazelcast/client/client_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
#include "hazelcast/logger.h"
#include "hazelcast/client/member_selectors.h"
#include "hazelcast/client/client_properties.h"

#include "hazelcast/client/big_decimal.h"
#ifndef HAZELCAST_VERSION
#define HAZELCAST_VERSION "NOT_FOUND"
#endif
Expand Down Expand Up @@ -591,6 +591,79 @@ operator<<(std::ostream& stream, const address& address)
return stream << address.to_string();
}

bool
operator==(const big_decimal& lhs, const big_decimal& rhs)
{
return lhs.unscaled == rhs.unscaled && lhs.scale == rhs.scale;
}

bool
operator<(const big_decimal& lhs, const big_decimal& rhs)
{
if (lhs.scale != rhs.scale) {
return lhs.scale < rhs.scale;
}
return lhs.unscaled < rhs.unscaled;
}

namespace pimpl {

void
twos_complement(std::vector<int8_t>& a)
{
// twos complement is calculated via flipping the bits and adding 1
// flip the bits
for (auto& item : a) {
item = ~item;
}
// add 1
int8_t carry = 1;
for (int i = a.size() - 1; i >= 0; i--) {
a[i] = a[i] + carry;
if (a[i] == 0) {
carry = 1;
} else {
break;
}
}
}

boost::multiprecision::cpp_int
from_bytes(std::vector<int8_t> v)
{
boost::multiprecision::cpp_int i;
bool is_negative = v[0] < 0;
if (is_negative) {
twos_complement(v);
}
import_bits(i, v.begin(), v.end(), 8);
if (is_negative) {
return -i;
}
return i;
}

std::vector<int8_t>
to_bytes(const boost::multiprecision::cpp_int& i)
{
std::vector<int8_t> v;
export_bits(i, std::back_inserter(v), 8);
if (i < 0) {
twos_complement(v);
if (v[0] > 0) {
// add -1 as the most significant to have a negative sign bit
v.insert(v.begin(), -1);
}
} else {
// add 0 as the most significant byte to have a positive sign bit
if (v[0] < 0) {
v.insert(v.begin(), 0);
}
}
return v;
}
} // namespace pimpl

namespace serialization {
int32_t
hz_serializer<address>::get_factory_id()
Expand Down Expand Up @@ -1152,4 +1225,14 @@ hash<hazelcast::client::address>::operator()(
boost::hash_combine(seed, address.type_);
return seed;
}

std::size_t
hash<hazelcast::client::big_decimal>::operator()(
const hazelcast::client::big_decimal& dec) const
{
std::size_t seed = 0;
boost::hash_combine(seed, dec.unscaled);
boost::hash_combine(seed, dec.scale);
return seed;
};
} // namespace std
75 changes: 75 additions & 0 deletions hazelcast/test/src/HazelcastTests2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#include <hazelcast/util/concurrent/locks/LockSupport.h>
#include <hazelcast/util/MurmurHash3.h>
#include <hazelcast/util/Util.h>
#include <hazelcast/client/big_decimal.h>

#include "ClientTest.h"
#include "HazelcastServer.h"
Expand All @@ -71,6 +72,80 @@
namespace hazelcast {
namespace client {
namespace test {

/**
* All the hardcoded values are generated via java.util.BigInteger.toByteArray()
* method
*/
class DecimalTest : public ::testing::Test
{};

void
assert_equal(const std::string& expected_string,
const std::vector<int8_t>& expected_vector)
{
using boost::multiprecision::cpp_int;
cpp_int expected_int(expected_string);

std::vector<int8_t> actual_vector = pimpl::to_bytes(expected_int);
ASSERT_EQ(expected_vector, actual_vector);
cpp_int actual_int = pimpl::from_bytes(expected_vector);
ASSERT_EQ(expected_int, actual_int);
}

TEST_F(DecimalTest, positive_test)
{
assert_equal(
"236095134049630962491764987683473058401811134068823290126231516129",
{
2, 61, -22, 92, -44, -54, -45, -9, -17, -4, -66, -5, -12, 19,
64, -5, -98, 12, -70, -24, 105, -66, -57, 61, -14, 109, -77, -31,
});
}

TEST_F(DecimalTest, negative_test)
{
assert_equal("-158058224523514071900098807210097354699988293366",
{
-28, 80, 108, -112, -19, -44, 84, -98, 96, 106,
53, -88, 77, -45, 89, 119, 109, -109, -87, 10,
});
}

TEST_F(DecimalTest, preserve_positive_sign_test)
{
assert_equal("53220513728803604", { 0, -67, 19, -58, 119, -111, -77, 20 });
}

TEST_F(DecimalTest, preserve_negative_sign_test)
{
assert_equal(
"-78097809300018214368298043748751294327036591272091714272720014418",
{
-1, 66, 39, -99, 44, 53, -23, 60, 125, 105, 65, -21, 104, -36,
-49, 79, -13, -115, 122, 57, -63, 106, 64, -39, -112, 16, -77, -82,
});
}

TEST_F(DecimalTest, carry_bit_test)
{
assert_equal(
"-4172290065390264938962604145655817690459633380799476516330728"
"71499276353298132342018230923743606150479511296",
{
-46, -123, 61, 41, -1, 115, -54, 91, -48, 79, 55, 25,
41, -90, 14, 109, -115, 68, -122, 46, 70, 90, 47, -103,
-21, -39, 126, -45, 37, 58, 60, -76, -44, 91, 97, 52,
31, -38, 23, -111, 18, -112, -109, -127, 0,
});
}

TEST_F(DecimalTest, cascading_carry_bit_test)
{
assert_equal("-1234506895138773532672",
{ -67, 19, -58, 119, -111, -77, 0, 0, 0 });
}

class AddressHelperTest : public ClientTest
{};

Expand Down
1 change: 1 addition & 0 deletions hazelcast/test/src/compact_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#endif

#include <hazelcast/client/serialization/serialization.h>
#include <hazelcast/client/big_decimal.h>

#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
#pragma warning(push)
Expand Down