Skip to content
This repository was archived by the owner on Aug 2, 2022. It is now read-only.

Reimplement asset::to_string() and related functions - develop #661

Merged
merged 2 commits into from
Sep 16, 2019
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
88 changes: 53 additions & 35 deletions libraries/eosiolib/asset.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
#warning "<eosiolib/asset.hpp> is deprecated use <eosio/asset.hpp>"

namespace eosio {
/**
* Defines C++ API for managing assets
* @addtogroup asset Asset C++ API
* @ingroup core
* @{
*/

char* write_decimal( char* begin, char* end, bool dry_run, uint64_t number, uint8_t num_decimal_places, bool negative );

/**
* Defines C++ API for managing assets
* @addtogroup asset Asset C++ API
* @ingroup core
* @{
*/

/**
* @struct Stores information for owner of asset
Expand Down Expand Up @@ -315,40 +318,49 @@ namespace eosio {
}

/**
* %asset to std::string
* Writes the asset as a string to the provided char buffer
*
* @brief %asset to std::string
*/
std::string to_string()const {
int64_t p = (int64_t)symbol.precision();
int64_t p10 = 1;
int64_t invert = 1;

while( p > 0 ) {
p10 *= 10; --p;
* @brief Writes the asset as a string to the provided char buffer
* @pre is_valid() == true
* @pre The range [begin, end) must be a valid range of memory to write to.
* @param begin - The start of the char buffer
* @param end - Just past the end of the char buffer
* @param dry_run - If true, do not actually write anything into the range.
* @return char* - Just past the end of the last character that would be written assuming dry_run == false and end was large enough to provide sufficient space. (Meaning only applies if returned pointer >= begin.)
* @post If the output string fits within the range [begin, end) and dry_run == false, the range [begin, returned pointer) contains the string representation of the asset. Nothing is written if dry_run == true or returned pointer > end (insufficient space) or if returned pointer < begin (overflow in calculating desired end).
*/
char* write_as_string( char* begin, char* end, bool dry_run = false )const {
bool negative = (amount < 0);
uint64_t abs_amount = static_cast<uint64_t>(negative ? -amount : amount);
// 0 <= abs_amount <= std::numeric_limits<int64_t>::max() < 10^19 < std::numeric_limits<uint64_t>::max()

uint8_t precision = symbol.precision();

int sufficient_size = std::max(static_cast<int>(precision), 19) + 11;
if( dry_run || (begin + sufficient_size < begin) || (begin + sufficient_size > end) ) {
char* start_of_symbol = write_decimal( begin, end, true, abs_amount, precision, negative ) + 1;
char* actual_end = symbol.code().write_as_string( start_of_symbol, end, true );
if( dry_run || (actual_end < begin) || (actual_end > end) ) return actual_end;
}
p = (int64_t)symbol.precision();

char fraction[p+1];
fraction[p] = '\0';
char* end_of_number = write_decimal( begin, end, false, abs_amount, precision, negative );
*(end_of_number) = ' ';

if (amount < 0) {
invert = -1;
}
return symbol.code().write_as_string( end_of_number + 1, end );
}

auto change = (amount % p10) * invert;
/**
* %asset to std::string
*
* @brief %asset to std::string
*/
std::string to_string()const {
int buffer_size = std::max(static_cast<int>(symbol.precision()), 19) + 11;
char buffer[buffer_size];
char* end = write_as_string( buffer, buffer + buffer_size );
check( end <= buffer + buffer_size, "insufficient space in buffer" ); // should never fail

for( int64_t i = p -1; i >= 0; --i ) {
fraction[i] = (change % 10) + '0';
change /= 10;
}
char str[p+32];
snprintf(str, sizeof(str), "%lld%s%s %s",
(int64_t)(amount/p10),
(fraction[0]) ? "." : "",
fraction,
symbol.code().to_string().c_str());
return {str};
return {buffer, end};
}

/**
Expand All @@ -357,7 +369,13 @@ namespace eosio {
* @brief %Print the asset
*/
void print()const {
eosio::print(to_string());
int buffer_size = std::max(static_cast<int>(symbol.precision()), 19) + 11;
char buffer[buffer_size];
char* end = write_as_string( buffer, buffer + buffer_size );
check( end <= buffer + buffer_size, "insufficient space in buffer" ); // should never fail

if( buffer < end )
printl( buffer, (end-buffer) );
}

EOSLIB_SERIALIZE( asset, (amount)(symbol) )
Expand Down
94 changes: 52 additions & 42 deletions libraries/eosiolib/core/eosio/asset.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
#include <limits>

namespace eosio {
/**
* @defgroup asset Asset
* @ingroup core
* @brief Defines C++ API for managing assets
*/

char* write_decimal( char* begin, char* end, bool dry_run, uint64_t number, uint8_t num_decimal_places, bool negative );

/**
* @defgroup asset Asset
* @ingroup core
* @brief Defines C++ API for managing assets
*/

/**
* Stores information for owner of asset
Expand Down Expand Up @@ -318,48 +321,49 @@ namespace eosio {
/// @endcond

/**
* %asset to std::string
* Writes the asset as a string to the provided char buffer
*
* @brief %asset to std::string
*/
std::string to_string()const {
int64_t p = (int64_t)symbol.precision();
int64_t p10 = 1;
int64_t invert = 1;
bool negative = false;

while( p > 0 ) {
p10 *= 10; --p;
* @brief Writes the asset as a string to the provided char buffer
* @pre is_valid() == true
* @pre The range [begin, end) must be a valid range of memory to write to.
* @param begin - The start of the char buffer
* @param end - Just past the end of the char buffer
* @param dry_run - If true, do not actually write anything into the range.
* @return char* - Just past the end of the last character that would be written assuming dry_run == false and end was large enough to provide sufficient space. (Meaning only applies if returned pointer >= begin.)
* @post If the output string fits within the range [begin, end) and dry_run == false, the range [begin, returned pointer) contains the string representation of the asset. Nothing is written if dry_run == true or returned pointer > end (insufficient space) or if returned pointer < begin (overflow in calculating desired end).
*/
char* write_as_string( char* begin, char* end, bool dry_run = false )const {
bool negative = (amount < 0);
uint64_t abs_amount = static_cast<uint64_t>(negative ? -amount : amount);
// 0 <= abs_amount <= std::numeric_limits<int64_t>::max() < 10^19 < std::numeric_limits<uint64_t>::max()

uint8_t precision = symbol.precision();

int sufficient_size = std::max(static_cast<int>(precision), 19) + 11;
if( dry_run || (begin + sufficient_size < begin) || (begin + sufficient_size > end) ) {
char* start_of_symbol = write_decimal( begin, end, true, abs_amount, precision, negative ) + 1;
char* actual_end = symbol.code().write_as_string( start_of_symbol, end, true );
if( dry_run || (actual_end < begin) || (actual_end > end) ) return actual_end;
}
p = (int64_t)symbol.precision();

char fraction[p+1];
fraction[p] = '\0';
char* end_of_number = write_decimal( begin, end, false, abs_amount, precision, negative );
*(end_of_number) = ' ';

if (amount < 0) {
invert = -1;
negative = true;
}

auto change = (amount % p10) * invert;

for( int64_t i = p -1; i >= 0; --i ) {
fraction[i] = (change % 10) + '0';
change /= 10;
}
char str[p+32];
return symbol.code().write_as_string( end_of_number + 1, end );
}

int64_t first = (int64_t)(amount/p10);
int64_t mask = first >> (sizeof(int64_t) * CHAR_BIT - 1);
int64_t abs = (mask ^ first) - mask;
/**
* %asset to std::string
*
* @brief %asset to std::string
*/
std::string to_string()const {
int buffer_size = std::max(static_cast<int>(symbol.precision()), 19) + 11;
char buffer[buffer_size];
char* end = write_as_string( buffer, buffer + buffer_size );
check( end <= buffer + buffer_size, "insufficient space in buffer" ); // should never fail

snprintf(str, sizeof(str), "%s%lld%s%s %s",
negative ? "-" : "",
abs,
(fraction[0]) ? "." : "",
fraction,
symbol.code().to_string().c_str());
return {str};
return {buffer, end};
}

/**
Expand All @@ -368,7 +372,13 @@ namespace eosio {
* @brief %Print the asset
*/
void print()const {
::eosio::print(to_string());
int buffer_size = std::max(static_cast<int>(symbol.precision()), 19) + 11;
char buffer[buffer_size];
char* end = write_as_string( buffer, buffer + buffer_size );
check( end <= buffer + buffer_size, "insufficient space in buffer" ); // should never fail

if( buffer < end )
printl( buffer, (end-buffer) );
}

EOSLIB_SERIALIZE( asset, (amount)(symbol) )
Expand Down
17 changes: 10 additions & 7 deletions libraries/eosiolib/core/eosio/name.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,21 +187,24 @@ namespace eosio {
/**
* Writes the %name as a string to the provided char buffer
*
* @pre Appropriate Size Precondition: (begin + 13) <= end and (begin + 13) does not overflow
* @pre Valid Memory Region Precondition: The range [begin, end) must be a valid range of memory to write to.
* @pre The range [begin, end) must be a valid range of memory to write to.
* @param begin - The start of the char buffer
* @param end - Just past the end of the char buffer
* @return char* - Just past the end of the last character written (returns begin if the Appropriate Size Precondition is not satisfied)
* @post If the Appropriate Size Precondition is satisfied, the range [begin, returned pointer) contains the string representation of the %name.
* @param dry_run - If true, do not actually write anything into the range.
* @return char* - Just past the end of the last character that would be written assuming dry_run == false and end was large enough to provide sufficient space. (Meaning only applies if returned pointer >= begin.)
* @post If the output string fits within the range [begin, end) and dry_run == false, the range [begin, returned pointer) contains the string representation of the %name. Nothing is written if dry_run == true or returned pointer > end (insufficient space) or if returned pointer < begin (overflow in calculating desired end).
*/
char* write_as_string( char* begin, char* end )const {
char* write_as_string( char* begin, char* end, bool dry_run = false )const {
static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz";
constexpr uint64_t mask = 0xF800000000000000ull;

if( (begin + 13) < begin || (begin + 13) > end ) return begin;
if( dry_run || (begin + 13 < begin) || (begin + 13 > end) ) {
char* actual_end = begin + length();
if( dry_run || (actual_end < begin) || (actual_end > end) ) return actual_end;
}

auto v = value;
for( auto i = 0; i < 13; ++i, v <<= 5 ) {
for( auto i = 0; i < 13; ++i, v <<= 5 ) {
if( v == 0 ) return begin;

auto indx = (v & mask) >> (i == 12 ? 60 : 59);
Expand Down
88 changes: 88 additions & 0 deletions libraries/eosiolib/core/eosio/powers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#pragma once

#include "check.hpp"

#include <type_traits>
#include <utility>
#include <array>

namespace eosio {

namespace detail {

template<typename Generator, std::size_t... Is>
constexpr auto generate_array_helper( Generator&& g, std::index_sequence<Is...> )
-> std::array<decltype(g(std::size_t{}, sizeof...(Is))), sizeof...(Is)>
{
return {{g(Is, sizeof...(Is))...}};
}

template<std::size_t N, typename Generator>
constexpr auto generate_array( Generator&& g ) {
return generate_array_helper( std::forward<Generator>(g), std::make_index_sequence<N>{} );
}

template<typename T, T Base, uint8_t Exponent, uint64_t Value, bool Overflow = (Value * Base < Value)>
struct largest_power_helper {
private:
static_assert( std::is_integral_v<T> && std::is_unsigned_v<T> &&!std::is_same_v<T, bool> );
static_assert( Base > 1 );
constexpr static T next_value = Value * Base;
using next = largest_power_helper<T, Base, Exponent+1, next_value>;
public:
constexpr static T value = next::value;
constexpr static uint8_t exponent = next::exponent;
};

template<typename T, T Base, uint8_t Exponent, uint64_t Value>
struct largest_power_helper<T, Base, Exponent, Value, true> {
private:
static_assert( std::is_integral_v<T> && std::is_unsigned_v<T> &&!std::is_same_v<T, bool> );
static_assert( Base > 1 );
static_assert( Exponent < 255 );
public:
constexpr static T value = Value;
constexpr static uint8_t exponent = Exponent;
};

template<typename T, T Base>
struct largest_power {
private:
using helper = largest_power_helper<T, Base, 0, 1>;
public:
constexpr static T value = helper::value;
constexpr static uint8_t exponent = helper::exponent;
};

template<typename T>
constexpr T pow( T base, uint8_t exponent ) {
if( base <= 1 ) check( false, "base must be at least 2" );
T prior = 1;
T result = prior;
for( uint8_t i = 0; i < exponent; ++i, prior = result ) {
result = prior * base;
if( result <= prior ) check( false, "overflow" );
}
return result;
}

template<typename T, T Base>
constexpr T pow_generator( std::size_t i, std::size_t ) {
return pow( Base, static_cast<uint8_t>(i) );
}

}

template<uint8_t Base, typename T = uint64_t>
inline constexpr auto powers_of_base = detail::generate_array<detail::largest_power<T, Base>::exponent + 1>( detail::pow_generator<T, Base> );

/** @returns Base^exponent */
template<uint8_t Base, typename T = uint64_t>
constexpr T pow( uint8_t exponent ) {
const auto& lookup_table = powers_of_base<Base, T>;
if( exponent >= lookup_table.size() ) check( false, "overflow" );

return lookup_table[exponent];
}

}
16 changes: 10 additions & 6 deletions libraries/eosiolib/core/eosio/symbol.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,21 @@ namespace eosio {
*
*
* @brief Writes the symbol_code as a string to the provided char buffer
* @pre Appropriate Size Precondition: (begin + 7) <= end and (begin + 7) does not overflow
* @pre Valid Memory Region Precondition: The range [begin, end) must be a valid range of memory to write to.
* @pre is_valid() == true
* @pre The range [begin, end) must be a valid range of memory to write to.
* @param begin - The start of the char buffer
* @param end - Just past the end of the char buffer
* @return char* - Just past the end of the last character written (returns begin if the Appropriate Size Precondition is not satisfied)
* @post If the Appropriate Size Precondition is satisfied, the range [begin, returned pointer) contains the string representation of the symbol_code.
* @param dry_run - If true, do not actually write anything into the range.
* @return char* - Just past the end of the last character that would be written assuming dry_run == false and end was large enough to provide sufficient space. (Meaning only applies if returned pointer >= begin.)
* @post If the output string fits within the range [begin, end) and dry_run == false, the range [begin, returned pointer) contains the string representation of the symbol_code. Nothing is written if dry_run == true or returned pointer > end (insufficient space) or if returned pointer < begin (overflow in calculating desired end).
*/
char* write_as_string( char* begin, char* end )const {
char* write_as_string( char* begin, char* end, bool dry_run = false )const {
constexpr uint64_t mask = 0xFFull;

if( (begin + 7) < begin || (begin + 7) > end ) return begin;
if( dry_run || (begin + 7 < begin) || (begin + 7 > end) ) {
char* actual_end = begin + length();
if( dry_run || (actual_end < begin) || (actual_end > end) ) return actual_end;
}

auto v = value;
for( auto i = 0; i < 7; ++i, v >>= 8 ) {
Expand Down
Loading