Skip to content

Commit

Permalink
revise decimal encoding from takatori::decimal::triple to ::jogasaki:…
Browse files Browse the repository at this point in the history
…:proto::sql::common::Decimal
  • Loading branch information
t-horikawa committed Jan 29, 2024
1 parent 67f7aaf commit 98337cc
Show file tree
Hide file tree
Showing 4 changed files with 278 additions and 72 deletions.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ file(GLOB SOURCES
"ogawayama/stub/*.cpp"
"tateyama/utils/*.cpp"
"jogasaki/serializer/*.cpp"
"jogasaki/utils/*.cpp"
)

set_source_files_properties(
Expand Down
197 changes: 197 additions & 0 deletions src/jogasaki/utils/decimal.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* Copyright 2018-2023 Project Tsurugi.
*
* 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.
*/
#include "decimal.h"

namespace jogasaki::utils {

constexpr static std::size_t npos = static_cast<std::size_t>(-1);

std::pair<std::size_t, bool> most_significant_non_zero_offset(std::uint64_t v, std::uint64_t zero) {
for (std::size_t offset = 0; offset < sizeof(std::uint64_t); ++offset) {
std::uint64_t octet = (v >> ((sizeof(std::uint64_t) - offset - 1U) * 8U)) & 0xffU;
if (octet != zero) {
if ((octet & 0x80U) != 0) {
return {offset, true};
}
return {offset, false};
}
}
return {npos, false};
}

std::tuple<std::uint64_t, std::uint64_t, std::size_t> make_signed_coefficient_full(takatori::decimal::triple value) {
std::uint64_t c_hi = value.coefficient_high();
std::uint64_t c_lo = value.coefficient_low();

if (value.sign() >= 0) {
if(auto [offset, expanded] = most_significant_non_zero_offset(c_hi, 0); offset != npos) {
std::size_t size { sizeof(std::uint64_t) * 2 - offset };
if(expanded) {
++size;
}
return { c_hi, c_lo, size };
}
if(auto [offset, expanded] = most_significant_non_zero_offset(c_lo, 0); offset != npos) {
std::size_t size { sizeof(std::uint64_t) - offset };
if(expanded) {
++size;
}
return { c_hi, c_lo, size };
}
// all values zero
return { c_hi, c_lo, 1 };
}

// for negative numbers

if (value.sign() < 0) {
c_lo = ~c_lo + 1;
c_hi = ~c_hi;
if (c_lo == 0) {
c_hi += 1; // carry up
}
}

if(auto [offset, expanded] = most_significant_non_zero_offset(c_hi, 0xffU); offset != npos) {
std::size_t size { sizeof(std::uint64_t) * 2 - offset };
if(expanded) {
++size;
}
return { c_hi, c_lo, size };
}
if(auto [offset, expanded] = most_significant_non_zero_offset(c_lo, 0xffU); offset != npos) {
std::size_t size { sizeof(std::uint64_t) - offset };
if(expanded) {
++size;
}
return { c_hi, c_lo, size };
}
return { c_hi, c_lo, 1 };
}

constexpr std::size_t max_decimal_coefficient_size = sizeof(std::uint64_t) * 2 + 1;

bool validate_decimal_coefficient(
std::string_view buf
) {
if(buf.size() < max_decimal_coefficient_size) {
return true;
}
auto first = static_cast<std::uint8_t>(buf[0]);
// positive is OK because coefficient is [0, 2^128)
if (first == 0) {
return true;
}

if (first == 0xffU) {
// check negative value to avoid -2^128 (0xff 0x00.. 0x00)
auto const* found = std::find_if(
buf.begin() + 1,
buf.end(),
[](auto c) { return c != '\0'; });
if (found != buf.end()) {
return true;
}
}
return false;
}

takatori::decimal::triple read_decimal(std::string_view data, std::size_t scale) {
// extract lower 8-octets of coefficient
std::uint64_t c_lo{};
std::uint64_t shift{};
for (std::size_t offset = 0;
offset < data.size() && offset < sizeof(std::uint64_t);
++offset) {
auto pos = data.size() - offset - 1;
std::uint64_t octet { static_cast<std::uint8_t>(data[pos]) };
c_lo |= octet << shift;
shift += 8;
}

// extract upper 8-octets of coefficient
std::uint64_t c_hi {};
shift = 0;
for (
std::size_t offset = sizeof(std::uint64_t);
offset < data.size() && offset < sizeof(std::uint64_t) * 2;
++offset) {
auto pos = data.size() - offset - 1;
std::uint64_t octet { static_cast<std::uint8_t>(data[pos]) };
c_hi |= octet << shift;
shift += 8;
}

bool negative = (static_cast<std::uint8_t>(data[0]) & 0x80U) != 0;

if (negative) {
// sign extension
if (data.size() < sizeof(std::uint64_t) * 2) {
auto mask = std::numeric_limits<std::uint64_t>::max(); // 0xfff.....
if(data.size() < sizeof(std::uint64_t)) {
std::size_t rest = data.size() * 8U;
c_lo |= mask << rest;
c_hi = mask;
} else {
std::size_t rest = (data.size() - sizeof(std::uint64_t)) * 8U;
c_hi |= mask << rest;
}
}

c_lo = ~c_lo + 1;
c_hi = ~c_hi;
if (c_lo == 0) {
c_hi += 1; // carry up
}
}

return takatori::decimal::triple{
negative ? -1 : +1,
c_hi,
c_lo,
-static_cast<std::int32_t>(scale),
};
}

void create_decimal(
std::int8_t sign,
std::uint64_t lo,
std::uint64_t hi,
std::size_t sz,
decimal_buffer& out
) {
auto base_ = out.data();
std::size_t pos_ = 0;

if (sz > sizeof(std::uint64_t) * 2) {
// write sign bit
*(base_ + pos_) = sign >= 0 ? '\x00' : '\xFF'; // NOLINT
++pos_;
--sz;
}

for (std::size_t offset = 0, n = std::min(sz, sizeof(std::uint64_t)); offset < n; ++offset) {
*(base_ + pos_ + sz - offset - 1) = static_cast<char>(lo >> (offset * 8U)); //NOLINT
}
if (sz > sizeof(std::uint64_t)) {
for (std::size_t offset = 0, n = std::min(sz - sizeof(std::uint64_t), sizeof(std::uint64_t)); offset < n; ++offset) {
*(base_ + pos_ + sz - offset - sizeof(std::uint64_t) - 1) = static_cast<char>(hi >> (offset * 8U)); //NOLINT
}
}
}

}

72 changes: 72 additions & 0 deletions src/jogasaki/utils/decimal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2018-2023 Project Tsurugi.
*
* 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 <initializer_list>
#include <tuple>
#include <string_view>

#include <takatori/value/decimal.h>

namespace jogasaki::utils {

/**
* @brief create signed coefficient from triple that has unsigned components
* @param value source triple
* @return `hi`, `lo` and `sz` return values:
* hi - more significant 64-bit
* lo - less significant 64 bit
* sz - min size in bytes to represent the signed coefficient. The `sz` bytes from the least significant byte in
* result 128-bit (concatenated `hi` with `lo`) represents the result. For valid input, `sz` ranges from 1 to 17.
* The `sz` being 17 is the special case where most significant byte (not part of `hi` or `lo`) is 0x00 or 0xFF
* to represent only sign.
*/
std::tuple<std::uint64_t, std::uint64_t, std::size_t> make_signed_coefficient_full(takatori::decimal::triple value);

/**
* @brief validate the decimal data in the buffer
* @param buf the data to validate
* @return true if buffer has valid decimal coefficient
* @return false otherwise
*/
bool validate_decimal_coefficient(std::string_view buf);

/**
* @brief read decimal from the buffer and return triple
* @param data the decimal data to read
* @param scale the scale of the result decimal
* @return the triple to represent the decimal data
*/
takatori::decimal::triple read_decimal(std::string_view data, std::size_t scale);

constexpr std::size_t max_decimal_length = sizeof(std::uint64_t) * 2 + 1;

using decimal_buffer = std::array<std::uint8_t, max_decimal_length>;

/**
* @brief write decimal to the buffer
* @param triple the decimal triple to write to the output buffer
* @param out the output buffer
*/
void create_decimal(
std::int8_t sign,
std::uint64_t lo,
std::uint64_t hi,
std::size_t sz,
decimal_buffer& out
);

}
80 changes: 8 additions & 72 deletions src/ogawayama/stub/transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

#include "jogasaki/utils/decimal.h"
#include "prepared_statementImpl.h"
#include "transactionImpl.h"

Expand Down Expand Up @@ -112,86 +113,21 @@ class parameter {
v->set_nano_adjustment(data.first.subsecond().count());
return parameter_;
}

::jogasaki::proto::sql::request::Parameter operator()(const decimal_type& triple) {
auto* value = &parameter_;
auto [c_hi, c_lo, c_size] = make_signed_coefficient_full(triple);
auto [hi, lo, sz] = jogasaki::utils::make_signed_coefficient_full(triple);
jogasaki::utils::decimal_buffer out{};
jogasaki::utils::create_decimal(triple.sign(), lo, hi, sz, out);

// the following part is taken from https://github.com/project-tsurugi/jogasaki/blob/206d07d700adc25d19c031bc1df9a08aee16555c/src/jogasaki/api/kvsservice/serializer.cpp#L219-L245 with using make_signed_coefficient_full()
{
std::string buf{};
const auto buflen = sizeof(c_lo) + sizeof(c_hi);
buf.reserve(buflen);
auto v = c_lo;
for (int i = 0; i < 8; i++) {
buf[15 - i] = static_cast<char>(v & 0xffU);
v >>= 8U;
}
v = c_hi;
for (int i = 0; i < 8; i++) {
buf[7 - i] = static_cast<char>(v & 0xffU);
v >>= 8U;
}
// skip zero-padding
std::size_t start = 0;
while (start < buflen - 1) {
if (buf[start] != 0) {
break;
}
start++;
}
auto *decimal = value->mutable_decimal_value();
// NOTE: buf.size() returns 0, not 16
decimal->set_unscaled_value(std::addressof(buf[start]), buflen - start);
decimal->set_exponent(triple.exponent());
}
auto *decimal = value->mutable_decimal_value();
decimal->set_unscaled_value(out.data(), sz);
decimal->set_exponent(triple.exponent());
return parameter_;
}

private:
::jogasaki::proto::sql::request::Parameter parameter_{};

// the following method is the copy of https://github.com/project-tsurugi/jogasaki/blob/2a404cdaf6e5599d1b2b8742d3568de58b3ee670/src/jogasaki/serializer/value_output.cpp#L165-L205
std::tuple<std::uint64_t, std::uint64_t, std::size_t> make_signed_coefficient_full(takatori::decimal::triple value) {
std::uint64_t c_hi = value.coefficient_high();
std::uint64_t c_lo = value.coefficient_low();

if (value.sign() >= 0) {
for (std::size_t offset = 0; offset < sizeof(std::uint64_t); ++offset) {
std::uint64_t octet = (c_hi >> ((sizeof(std::uint64_t) - offset - 1U) * 8U)) & 0xffU;
if (octet != 0) {
std::size_t size { sizeof(std::uint64_t) * 2 - offset };
if ((octet & 0x80U) != 0) {
++size;
}
return { c_hi, c_lo, size };
}
}
return { c_hi, c_lo, sizeof(std::uint64_t) + 1 };
}

// for negative numbers

if (value.sign() < 0) {
c_lo = ~c_lo + 1;
c_hi = ~c_hi;
if (c_lo == 0) {
c_hi += 1; // carry up
}
}

for (std::size_t offset = 0; offset < sizeof(std::uint64_t); ++offset) {
std::uint64_t octet = (c_hi >> ((sizeof(std::uint64_t) - offset - 1U) * 8U)) & 0xffU;
if (octet != 0xffU) {
std::size_t size { sizeof(std::uint64_t) * 2 - offset };
if ((octet & 0x80U) == 0) {
++size;
}
return { c_hi, c_lo, size };
}
}
return { c_hi, c_lo, sizeof(std::uint64_t) + 1 };
}

};

/**
Expand Down

1 comment on commit 98337cc

@t-horikawa
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.