Skip to content

Commit ac039d1

Browse files
author
sancar
authored
Introduce hazelcast::client::decimal [API-1297] (#967)
* Introduce hazelcast::client::decimal This class will be used in SQL and in Compact to match with Decimal type on SQL and Compact. a.k.a BigDecimal on java. * add equality operator * rename decimal to big_decimal
1 parent 91cd97c commit ac039d1

File tree

4 files changed

+267
-1
lines changed

4 files changed

+267
-1
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright (c) 2008-2022, Hazelcast, Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
#pragma once
17+
18+
#include <vector>
19+
#include <boost/multiprecision/cpp_int.hpp>
20+
#include "hazelcast/util/export.h"
21+
22+
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
23+
#pragma warning(push)
24+
#pragma warning(disable : 4251) // for dll export
25+
#endif
26+
27+
namespace hazelcast {
28+
namespace client {
29+
/**
30+
* An arbitrary precision and scale floating point number.
31+
* unscaledValue x 10 ^ -scale
32+
*
33+
* For arithmetic operations support, it is suggested to use external
34+
* libraries. An usage example with boost::multiprecision::cpp_dec_float
35+
* could be as follows:
36+
* <pre><code>
37+
* hazelcast::big_decimal dec{ u, 2 };
38+
* boost::multiprecision::cpp_dec_float<10> f(
39+
* (dec.unscaled.str() + "e-" + std::to_string(dec.scale)).c_str());
40+
* std::cout << f.str(100, std::ios_base::dec) << std::endl;
41+
* </code></pre>
42+
*/
43+
struct HAZELCAST_API big_decimal
44+
{
45+
boost::multiprecision::cpp_int unscaled;
46+
int32_t scale;
47+
};
48+
49+
bool HAZELCAST_API
50+
operator==(const big_decimal& lhs, const big_decimal& rhs);
51+
52+
bool HAZELCAST_API
53+
operator<(const big_decimal& lhs, const big_decimal& rhs);
54+
} // namespace client
55+
} // namespace hazelcast
56+
namespace std {
57+
template<>
58+
struct HAZELCAST_API hash<hazelcast::client::big_decimal>
59+
{
60+
std::size_t operator()(const hazelcast::client::big_decimal& f) const;
61+
};
62+
} // namespace std
63+
namespace hazelcast {
64+
namespace client {
65+
namespace pimpl {
66+
67+
/**
68+
* Takes twos complement of given array where most significant value is first.
69+
* This method modifies the vector in place.
70+
* @param a the array to take twos complement of
71+
*/
72+
73+
void HAZELCAST_API
74+
twos_complement(std::vector<int8_t>& a);
75+
76+
/**
77+
* Creates a cpp_int from a vector of int8_t respecting the sign.
78+
*
79+
* boost::import_bits does not respect the sign, so we have to do it manually.
80+
* if v represents a negative number, we take the two's complement of it,
81+
* to get positive representation of the same number. Then we add the sign
82+
* at the end.
83+
* @param v int8_t array to read from
84+
* @return cpp_int
85+
*/
86+
boost::multiprecision::cpp_int HAZELCAST_API
87+
from_bytes(std::vector<int8_t> v);
88+
/**
89+
* Creates a twos complement byte array from cpp_int respecting the sign.
90+
*
91+
* boost::export_bits does not respect the sign, so we have to do it manually.
92+
* if i is a negative number, we take the two's complement on resulting vector,
93+
* to get negative representation of the number.
94+
* We also add one extra byte to the end of the vector to preserve the sign if
95+
* sign of the integer is not the same as the most significant byte's sign.
96+
* Otherwise we don't add it to have minimum size vector to represent the value.
97+
* @param i the number to convert to bytes
98+
* @return the vector of int8_t representing the number
99+
*/
100+
std::vector<int8_t> HAZELCAST_API
101+
to_bytes(const boost::multiprecision::cpp_int& i);
102+
} // namespace pimpl
103+
} // namespace client
104+
} // namespace hazelcast
105+
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
106+
#pragma warning(pop)
107+
#endif

hazelcast/src/hazelcast/client/client_impl.cpp

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
#include "hazelcast/logger.h"
4646
#include "hazelcast/client/member_selectors.h"
4747
#include "hazelcast/client/client_properties.h"
48-
48+
#include "hazelcast/client/big_decimal.h"
4949
#ifndef HAZELCAST_VERSION
5050
#define HAZELCAST_VERSION "NOT_FOUND"
5151
#endif
@@ -591,6 +591,79 @@ operator<<(std::ostream& stream, const address& address)
591591
return stream << address.to_string();
592592
}
593593

594+
bool
595+
operator==(const big_decimal& lhs, const big_decimal& rhs)
596+
{
597+
return lhs.unscaled == rhs.unscaled && lhs.scale == rhs.scale;
598+
}
599+
600+
bool
601+
operator<(const big_decimal& lhs, const big_decimal& rhs)
602+
{
603+
if (lhs.scale != rhs.scale) {
604+
return lhs.scale < rhs.scale;
605+
}
606+
return lhs.unscaled < rhs.unscaled;
607+
}
608+
609+
namespace pimpl {
610+
611+
void
612+
twos_complement(std::vector<int8_t>& a)
613+
{
614+
// twos complement is calculated via flipping the bits and adding 1
615+
// flip the bits
616+
for (auto& item : a) {
617+
item = ~item;
618+
}
619+
// add 1
620+
int8_t carry = 1;
621+
for (int i = a.size() - 1; i >= 0; i--) {
622+
a[i] = a[i] + carry;
623+
if (a[i] == 0) {
624+
carry = 1;
625+
} else {
626+
break;
627+
}
628+
}
629+
}
630+
631+
boost::multiprecision::cpp_int
632+
from_bytes(std::vector<int8_t> v)
633+
{
634+
boost::multiprecision::cpp_int i;
635+
bool is_negative = v[0] < 0;
636+
if (is_negative) {
637+
twos_complement(v);
638+
}
639+
import_bits(i, v.begin(), v.end(), 8);
640+
if (is_negative) {
641+
return -i;
642+
}
643+
return i;
644+
}
645+
646+
std::vector<int8_t>
647+
to_bytes(const boost::multiprecision::cpp_int& i)
648+
{
649+
std::vector<int8_t> v;
650+
export_bits(i, std::back_inserter(v), 8);
651+
if (i < 0) {
652+
twos_complement(v);
653+
if (v[0] > 0) {
654+
// add -1 as the most significant to have a negative sign bit
655+
v.insert(v.begin(), -1);
656+
}
657+
} else {
658+
// add 0 as the most significant byte to have a positive sign bit
659+
if (v[0] < 0) {
660+
v.insert(v.begin(), 0);
661+
}
662+
}
663+
return v;
664+
}
665+
} // namespace pimpl
666+
594667
namespace serialization {
595668
int32_t
596669
hz_serializer<address>::get_factory_id()
@@ -1152,4 +1225,14 @@ hash<hazelcast::client::address>::operator()(
11521225
boost::hash_combine(seed, address.type_);
11531226
return seed;
11541227
}
1228+
1229+
std::size_t
1230+
hash<hazelcast::client::big_decimal>::operator()(
1231+
const hazelcast::client::big_decimal& dec) const
1232+
{
1233+
std::size_t seed = 0;
1234+
boost::hash_combine(seed, dec.unscaled);
1235+
boost::hash_combine(seed, dec.scale);
1236+
return seed;
1237+
};
11551238
} // namespace std

hazelcast/test/src/HazelcastTests2.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include <hazelcast/util/concurrent/locks/LockSupport.h>
5656
#include <hazelcast/util/MurmurHash3.h>
5757
#include <hazelcast/util/Util.h>
58+
#include <hazelcast/client/big_decimal.h>
5859

5960
#include "ClientTest.h"
6061
#include "HazelcastServer.h"
@@ -71,6 +72,80 @@
7172
namespace hazelcast {
7273
namespace client {
7374
namespace test {
75+
76+
/**
77+
* All the hardcoded values are generated via java.util.BigInteger.toByteArray()
78+
* method
79+
*/
80+
class DecimalTest : public ::testing::Test
81+
{};
82+
83+
void
84+
assert_equal(const std::string& expected_string,
85+
const std::vector<int8_t>& expected_vector)
86+
{
87+
using boost::multiprecision::cpp_int;
88+
cpp_int expected_int(expected_string);
89+
90+
std::vector<int8_t> actual_vector = pimpl::to_bytes(expected_int);
91+
ASSERT_EQ(expected_vector, actual_vector);
92+
cpp_int actual_int = pimpl::from_bytes(expected_vector);
93+
ASSERT_EQ(expected_int, actual_int);
94+
}
95+
96+
TEST_F(DecimalTest, positive_test)
97+
{
98+
assert_equal(
99+
"236095134049630962491764987683473058401811134068823290126231516129",
100+
{
101+
2, 61, -22, 92, -44, -54, -45, -9, -17, -4, -66, -5, -12, 19,
102+
64, -5, -98, 12, -70, -24, 105, -66, -57, 61, -14, 109, -77, -31,
103+
});
104+
}
105+
106+
TEST_F(DecimalTest, negative_test)
107+
{
108+
assert_equal("-158058224523514071900098807210097354699988293366",
109+
{
110+
-28, 80, 108, -112, -19, -44, 84, -98, 96, 106,
111+
53, -88, 77, -45, 89, 119, 109, -109, -87, 10,
112+
});
113+
}
114+
115+
TEST_F(DecimalTest, preserve_positive_sign_test)
116+
{
117+
assert_equal("53220513728803604", { 0, -67, 19, -58, 119, -111, -77, 20 });
118+
}
119+
120+
TEST_F(DecimalTest, preserve_negative_sign_test)
121+
{
122+
assert_equal(
123+
"-78097809300018214368298043748751294327036591272091714272720014418",
124+
{
125+
-1, 66, 39, -99, 44, 53, -23, 60, 125, 105, 65, -21, 104, -36,
126+
-49, 79, -13, -115, 122, 57, -63, 106, 64, -39, -112, 16, -77, -82,
127+
});
128+
}
129+
130+
TEST_F(DecimalTest, carry_bit_test)
131+
{
132+
assert_equal(
133+
"-4172290065390264938962604145655817690459633380799476516330728"
134+
"71499276353298132342018230923743606150479511296",
135+
{
136+
-46, -123, 61, 41, -1, 115, -54, 91, -48, 79, 55, 25,
137+
41, -90, 14, 109, -115, 68, -122, 46, 70, 90, 47, -103,
138+
-21, -39, 126, -45, 37, 58, 60, -76, -44, 91, 97, 52,
139+
31, -38, 23, -111, 18, -112, -109, -127, 0,
140+
});
141+
}
142+
143+
TEST_F(DecimalTest, cascading_carry_bit_test)
144+
{
145+
assert_equal("-1234506895138773532672",
146+
{ -67, 19, -58, 119, -111, -77, 0, 0, 0 });
147+
}
148+
74149
class AddressHelperTest : public ClientTest
75150
{};
76151

hazelcast/test/src/compact_test.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#endif
2626

2727
#include <hazelcast/client/serialization/serialization.h>
28+
#include <hazelcast/client/big_decimal.h>
2829

2930
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
3031
#pragma warning(push)

0 commit comments

Comments
 (0)