Skip to content

Commit

Permalink
Refactor to_string functions, add unit tests. bitshares#144
Browse files Browse the repository at this point in the history
  • Loading branch information
abitmore committed Jan 25, 2018
1 parent d0297ee commit 25000ab
Show file tree
Hide file tree
Showing 5 changed files with 464 additions and 60 deletions.
1 change: 1 addition & 0 deletions libraries/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ file(GLOB EGENESIS_HEADERS "../egenesis/include/graphene/app/*.hpp")
add_library( graphene_app
api.cpp
application.cpp
util.cpp
database_api.cpp
impacted.cpp
plugin.cpp
Expand Down
74 changes: 14 additions & 60 deletions libraries/app/database_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/

#include <graphene/app/database_api.hpp>
#include <graphene/app/util.hpp>
#include <graphene/chain/get_config.hpp>

#include <fc/bloom_filter.hpp>
Expand Down Expand Up @@ -155,10 +156,7 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>


//private:
string uint128_amount_to_string( const fc::uint128& amount, const uint8_t precision )const;
string price_to_string( const price& _price, const asset_object& _base, const asset_object& _quote )const;
string price_diff_to_string( const price& old_price, const price& new_price,
const asset_object& _base, const asset_object& _quote )const;
static string price_to_string( const price& _price, const asset_object& _base, const asset_object& _quote );

template<typename T>
void subscribe_to_item( const T& i )const
Expand Down Expand Up @@ -1154,63 +1152,15 @@ void database_api_impl::unsubscribe_from_market(asset_id_type a, asset_id_type b
_market_subscriptions.erase(std::make_pair(a,b));
}

string database_api_impl::uint128_amount_to_string( const fc::uint128& amount, const uint8_t precision )const
string database_api_impl::price_to_string( const price& _price, const asset_object& _base, const asset_object& _quote )
{ try {
fc::uint128 scaled_precision = 1;
for( uint8_t i = 0; i < precision; ++i )
scaled_precision *= 10;
FC_ASSERT(scaled_precision > 0);

string result = string( amount / scaled_precision );
auto decimals = amount % scaled_precision;
if( decimals > 0 )
result += "." + string(scaled_precision + decimals).erase(0,1);
return result;
} FC_CAPTURE_AND_RETHROW( (amount)(precision) ) }

string database_api_impl::price_to_string( const price& _price, const asset_object& _base, const asset_object& _quote )const
{ try {
share_type base_amount = _price.base.amount;
share_type quote_amount = _price.quote.amount;
if( _price.base.asset_id != _base.id )
std::swap( base_amount, quote_amount );

// times (10**18 * 10**3) so won't overflow but always have good accuracy
fc::uint128 price128 = fc::uint128( base_amount.value ) * uint64_t(1000000000000000000ll) * uint64_t(1000) / quote_amount.value;

return uint128_amount_to_string( price128, 21 + _base.precision - _quote.precision );
} FC_CAPTURE_AND_RETHROW( (_price)(_base)(_quote) ) }

string database_api_impl::price_diff_to_string( const price& old_price, const price& new_price,
const asset_object& _base, const asset_object& _quote )const
{ try {
share_type old_base_amount = old_price.base.amount;
share_type old_quote_amount = old_price.quote.amount;
if( old_price.base.asset_id != _base.id )
std::swap( old_base_amount, old_quote_amount );

share_type new_base_amount = new_price.base.amount;
share_type new_quote_amount = new_price.quote.amount;
if( new_price.base.asset_id != _base.id )
std::swap( new_base_amount, new_quote_amount );

// change = new/old - 1 = (new_base/new_quote)/(old_base/old_quote) - 1
// = (new_base * old_quote) / (new_quote * old_base) - 1
// = (new_base * old_quote - new_quote * old_base) / (new_quote * old_base)
fc::uint128 new128 = fc::uint128( new_base_amount.value ) * old_quote_amount.value;
fc::uint128 old128 = fc::uint128( old_base_amount.value ) * new_quote_amount.value;
bool non_negative = (new128 >= old128);
fc::uint128 diff128;
if( non_negative )
diff128 = new128 - old128;
if( _price.base.asset_id == _base.id && _price.quote.asset_id == _quote.id )
return graphene::app::price_to_string( _price, _base.precision, _quote.precision );
else if( _price.base.asset_id == _quote.id && _price.quote.asset_id == _base.id )
return graphene::app::price_to_string( ~_price, _base.precision, _quote.precision );
else
diff128 = old128 - new128;
string diff_str = uint128_amount_to_string( diff128 * 10000 / old128, 2 );
if( non_negative )
return diff_str;
else
return "-" + diff_str;
} FC_CAPTURE_AND_RETHROW( (old_price)(new_price)(_base)(_quote) ) }
FC_ASSERT( !"bad parameters" );
} FC_CAPTURE_AND_RETHROW( (_price)(_base)(_quote) ) }

market_ticker database_api::get_ticker( const string& base, const string& quote )const
{
Expand Down Expand Up @@ -1246,12 +1196,16 @@ market_ticker database_api_impl::get_ticker( const string& base, const string& q
if( itr != ticker_idx.end() )
{
price latest_price = asset( itr->latest_base, itr->base ) / asset( itr->latest_quote, itr->quote );
if( itr->base != assets[0]->id )
latest_price = ~latest_price;
result.latest = price_to_string( latest_price, *assets[0], *assets[1] );
if( itr->last_day_base != 0 && itr->last_day_quote != 0 // has trade data before 24 hours
&& ( itr->last_day_base != itr->latest_base || itr->last_day_quote != itr->latest_quote ) ) // price changed
{
price last_day_price = asset( itr->last_day_base, itr->base ) / asset( itr->last_day_quote, itr->quote );
result.percent_change = price_diff_to_string( last_day_price, latest_price, *assets[0], *assets[1] );
if( itr->base != assets[0]->id )
last_day_price = ~last_day_price;
result.percent_change = price_diff_percent_string( last_day_price, latest_price );
}
if( assets[0]->id == itr->base )
{
Expand Down
43 changes: 43 additions & 0 deletions libraries/app/include/graphene/app/util.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2018 Abit More, and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once

#include <boost/multiprecision/cpp_int.hpp>

#include <fc/uint128.hpp>

#include <graphene/chain/protocol/asset.hpp>

namespace graphene { namespace app {
using namespace graphene::chain;

typedef boost::multiprecision::uint256_t u256;

u256 to256( const fc::uint128& t );
fc::uint128 to_capped128( const u256& t );
string uint128_amount_to_string( const fc::uint128& amount, const uint8_t precision );
string price_to_string( const price& _price, const uint8_t base_precision, const uint8_t quote_precision);
string price_diff_percent_string( const price& old_price, const price& new_price );

} }
161 changes: 161 additions & 0 deletions libraries/app/util.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright (c) 2018 Abit More, and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#include <graphene/app/util.hpp>


namespace graphene { namespace app {

u256 to256( const fc::uint128& t )
{
u256 v(t.hi);
v <<= 64;
v += t.lo;
return v;
}

fc::uint128 to_capped128( const u256& t )
{
static u256 max128 = to256( fc::uint128::max_value() );
if( t >= max128 )
return fc::uint128::max_value();
fc::uint128 result;
u256 hi(t);
hi >>= 64;
result.hi = static_cast< uint64_t >( hi );
u256 lo(t);
hi <<= 64;
lo -= hi;
result.lo = static_cast< uint64_t >( lo );
return result;
}

string uint128_amount_to_string( const fc::uint128& amount, const uint8_t precision )
{ try {
string s = string( amount );
if( precision == 0 || amount == 0 )
return s;

std::stringstream ss;
uint8_t pos = s.find_last_not_of( '0' ); // should be >= 0
uint8_t len = s.size();
if( len > precision )
{
uint8_t left_len = len - precision;
ss << s.substr( 0, left_len );
if( pos >= left_len )
ss << '.' << s.substr( left_len, pos - left_len + 1 );
}
else
{
ss << "0.";
for( uint8_t i = precision - len; i > 0; --i )
ss << '0';
ss << s.substr( 0, pos + 1 );
}
return ss.str();
} FC_CAPTURE_AND_RETHROW( (amount)(precision) ) }

string price_to_string( const price& _price, const uint8_t base_precision, const uint8_t quote_precision )
{ try {
if( _price.base.amount == 0 )
return "0";
FC_ASSERT( _price.base.amount >= 0 );
FC_ASSERT( _price.quote.amount >= 0 );
FC_ASSERT( base_precision <= 19 );
FC_ASSERT( quote_precision <= 19 );
price new_price = _price;
if( new_price.quote.amount == 0 )
{
new_price.base.amount = std::numeric_limits<int64_t>::max();
new_price.quote.amount = 1;
}

// times (10**19) so won't overflow but have good accuracy
fc::uint128 price128 = fc::uint128( new_price.base.amount.value ) * uint64_t(10000000000000000000ULL)
/ new_price.quote.amount.value;

return uint128_amount_to_string( price128, 19 + base_precision - quote_precision );
} FC_CAPTURE_AND_RETHROW( (_price)(base_precision)(quote_precision) ) }

string price_diff_percent_string( const price& old_price, const price& new_price )
{ try {
FC_ASSERT( old_price.base.asset_id == new_price.base.asset_id );
FC_ASSERT( old_price.quote.asset_id == new_price.quote.asset_id );
FC_ASSERT( old_price.base.amount >= 0 );
FC_ASSERT( old_price.quote.amount >= 0 );
FC_ASSERT( new_price.base.amount >= 0 );
FC_ASSERT( new_price.quote.amount >= 0 );
price old_price1 = old_price;
if( old_price.base.amount == 0 )
{
old_price1.base.amount = 1;
old_price1.quote.amount = std::numeric_limits<int64_t>::max();
}
else if( old_price.quote.amount == 0 )
{
old_price1.base.amount = std::numeric_limits<int64_t>::max();
old_price1.quote.amount = 1;
}
price new_price1 = new_price;
if( new_price.base.amount == 0 )
{
new_price1.base.amount = 1;
new_price1.quote.amount = std::numeric_limits<int64_t>::max();
}
else if( new_price.quote.amount == 0 )
{
new_price1.base.amount = std::numeric_limits<int64_t>::max();
new_price1.quote.amount = 1;
}

// change = new/old - 1 = (new_base/new_quote)/(old_base/old_quote) - 1
// = (new_base * old_quote) / (new_quote * old_base) - 1
// = (new_base * old_quote - new_quote * old_base) / (new_quote * old_base)
fc::uint128 new128 = fc::uint128( new_price1.base.amount.value ) * old_price1.quote.amount.value;
fc::uint128 old128 = fc::uint128( old_price1.base.amount.value ) * new_price1.quote.amount.value;
bool non_negative = (new128 >= old128);
fc::uint128 diff128;
if( non_negative )
diff128 = new128 - old128;
else
diff128 = old128 - new128;
static fc::uint128 max = fc::uint128::max_value() / 10000;
if( diff128 <= max )
diff128 = diff128 * 10000 / old128;
else
{
u256 diff256 = to256( diff128 );
diff256 *= 10000;
diff256 /= to256( old128 );
diff128 = to_capped128( diff256 );
}
string diff_str = uint128_amount_to_string( diff128, 2 ); // at most 2 decimal digits
if( non_negative || diff_str == "0" )
return diff_str;
else
return "-" + diff_str;
} FC_CAPTURE_AND_RETHROW( (old_price)(new_price) ) }

} } // graphene::app
Loading

0 comments on commit 25000ab

Please sign in to comment.