From a695d0f52b92b74accd9dbb6284552e2f64f0599 Mon Sep 17 00:00:00 2001 From: lukealonso Date: Thu, 28 Dec 2017 21:31:28 -0800 Subject: [PATCH] Improve balance formatting in the UI. --- rai/core_test/uint256_union.cpp | 37 ++++++++++ rai/lib/numbers.cpp | 116 ++++++++++++++++++++++++++++++++ rai/lib/numbers.hpp | 2 + rai/qt/qt.cpp | 37 ++++++---- rai/qt/qt.hpp | 5 +- rai/qt_test/qt.cpp | 12 +++- 6 files changed, 193 insertions(+), 16 deletions(-) diff --git a/rai/core_test/uint256_union.cpp b/rai/core_test/uint256_union.cpp index eb3f233c..2684721f 100644 --- a/rai/core_test/uint256_union.cpp +++ b/rai/core_test/uint256_union.cpp @@ -13,6 +13,43 @@ TEST (uint128_union, decode_dec) ASSERT_EQ (16, value.bytes [15]); } +struct test_punct : std::moneypunct { + pattern do_pos_format () const { return { {value, none, none, none} }; } + int do_frac_digits () const { return 0; } + char_type do_decimal_point () const { return '+'; } + char_type do_thousands_sep () const { return '-'; } + string_type do_grouping () const { return "\3\4"; } +}; + +TEST (uint128_union, balance_format) +{ + ASSERT_EQ ("0", rai::amount (rai::uint128_t ("0")).format_balance (rai::Mxrb_ratio, 0, false)); + ASSERT_EQ ("0", rai::amount (rai::uint128_t ("0")).format_balance (rai::Mxrb_ratio, 2, true)); + ASSERT_EQ ("340,282,366", rai::amount (rai::uint128_t ("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")).format_balance (rai::Mxrb_ratio, 0, true)); + ASSERT_EQ ("340,282,366.920938463463374607431768211455", rai::amount (rai::uint128_t ("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")).format_balance (rai::Mxrb_ratio, 64, true)); + ASSERT_EQ ("340,282,366,920,938,463,463,374,607,431,768,211,455", rai::amount (rai::uint128_t ("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")).format_balance (1, 4, true)); + ASSERT_EQ ("340,282,366", rai::amount (rai::uint128_t ("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")).format_balance (rai::Mxrb_ratio, 0, true)); + ASSERT_EQ ("340,282,366.920938463463374607431768211454", rai::amount (rai::uint128_t ("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")).format_balance (rai::Mxrb_ratio, 64, true)); + ASSERT_EQ ("340282366920938463463374607431768211454", rai::amount (rai::uint128_t ("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")).format_balance (1, 4, false)); + ASSERT_EQ ("170,141,183", rai::amount (rai::uint128_t ("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")).format_balance (rai::Mxrb_ratio, 0, true)); + ASSERT_EQ ("170,141,183.460469231731687303715884105726", rai::amount (rai::uint128_t ("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")).format_balance (rai::Mxrb_ratio, 64, true)); + ASSERT_EQ ("170141183460469231731687303715884105726", rai::amount (rai::uint128_t ("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")).format_balance (1, 4, false)); + ASSERT_EQ ("1", rai::amount (rai::uint128_t ("1000000000000000000000000000000")).format_balance (rai::Mxrb_ratio, 2, true)); + ASSERT_EQ ("1.2", rai::amount (rai::uint128_t ("1200000000000000000000000000000")).format_balance (rai::Mxrb_ratio, 2, true)); + ASSERT_EQ ("1.23", rai::amount (rai::uint128_t ("1230000000000000000000000000000")).format_balance (rai::Mxrb_ratio, 2, true)); + ASSERT_EQ ("1.2", rai::amount (rai::uint128_t ("1230000000000000000000000000000")).format_balance (rai::Mxrb_ratio, 1, true)); + ASSERT_EQ ("1", rai::amount (rai::uint128_t ("1230000000000000000000000000000")).format_balance (rai::Mxrb_ratio, 0, true)); + ASSERT_EQ ("< 0.01", rai::amount (rai::xrb_ratio * 10).format_balance (rai::Mxrb_ratio, 2, true)); + ASSERT_EQ ("< 0.1", rai::amount (rai::xrb_ratio * 10).format_balance (rai::Mxrb_ratio, 1, true)); + ASSERT_EQ ("< 1", rai::amount (rai::xrb_ratio * 10).format_balance (rai::Mxrb_ratio, 0, true)); + ASSERT_EQ ("< 0.01", rai::amount (rai::xrb_ratio * 9999).format_balance (rai::Mxrb_ratio, 2, true)); + ASSERT_EQ ("0.01", rai::amount (rai::xrb_ratio * 10000).format_balance (rai::Mxrb_ratio, 2, true)); + ASSERT_EQ ("123456789", rai::amount (rai::Mxrb_ratio * 123456789).format_balance (rai::Mxrb_ratio, 2, false)); + ASSERT_EQ ("123,456,789", rai::amount (rai::Mxrb_ratio * 123456789).format_balance (rai::Mxrb_ratio, 2, true)); + ASSERT_EQ ("123,456,789.12", rai::amount (rai::Mxrb_ratio * 123456789 + rai::kxrb_ratio * 123).format_balance (rai::Mxrb_ratio, 2, true)); + ASSERT_EQ ("12-3456-789+123", rai::amount (rai::Mxrb_ratio * 123456789 + rai::kxrb_ratio * 123).format_balance (rai::Mxrb_ratio, 4, true, std::locale (std::cout.getloc (), new test_punct))); +} + TEST (unions, identity) { ASSERT_EQ (1, rai::uint128_union (1).number ().convert_to ()); diff --git a/rai/lib/numbers.cpp b/rai/lib/numbers.cpp index 4fa0866a..a453925d 100644 --- a/rai/lib/numbers.cpp +++ b/rai/lib/numbers.cpp @@ -311,6 +311,10 @@ bool rai::uint256_union::decode_dec (std::string const & text) { stream >> number_l; *this = number_l; + if (!stream.eof ()) + { + result = true; + } } catch (std::runtime_error &) { @@ -570,6 +574,10 @@ bool rai::uint128_union::decode_dec (std::string const & text) { stream >> number_l; *this = number_l; + if (!stream.eof ()) + { + result = true; + } } catch (std::runtime_error &) { @@ -579,6 +587,114 @@ bool rai::uint128_union::decode_dec (std::string const & text) return result; } +void format_frac(std::ostringstream & stream, rai::uint128_t value, rai::uint128_t scale, int precision) { + auto reduce = scale; + auto rem = value; + while (reduce > 1 && rem > 0 && precision > 0) { + reduce /= 10; + auto val = rem / reduce; + rem -= val * reduce; + stream << val; + precision--; + } +} + + +void format_dec(std::ostringstream & stream, rai::uint128_t value, char group_sep, const std::string & groupings) { + auto largestPow10 = rai::uint256_t (1); + int dec_count = 1; + while (1) { + auto next = largestPow10 * 10; + if (next > value) { + break; + } + largestPow10 = next; + dec_count++; + } + + if (dec_count > 39) { + // Impossible. + return; + } + + // This could be cached per-locale. + bool emit_group[39]; + if (group_sep != 0) { + int group_index = 0; + int group_count = 0; + for (int i = 0; i < dec_count; i++) { + int groupMax = groupings [group_index]; + group_count++; + if (group_count > groupings [group_index]) { + group_index = std::min (group_index + 1, (int)groupings.length() - 1); + group_count = 1; + emit_group [i] = true; + } else { + emit_group [i] = false; + } + } + } + + auto reduce = rai::uint128_t(largestPow10); + rai::uint128_t rem = value; + while (reduce > 0) { + auto val = rem / reduce; + rem -= val * reduce; + stream << val; + dec_count--; + if (group_sep != 0 && emit_group [dec_count] && reduce > 1) { + stream << group_sep; + } + reduce /= 10; + } +} + +std::string format_balance (rai::uint128_t balance, rai::uint128_t scale, int precision, bool group_digits, char thousands_sep, char decimal_point, std::string & grouping) +{ + std::ostringstream stream; + auto int_part = balance / scale; + auto frac_part = balance % scale; + auto prec_scale = scale; + for (int i = 0; i < precision; i++) { + prec_scale /= 10; + } + if (int_part == 0 && frac_part > 0 && frac_part / prec_scale == 0) { + // Display e.g. "< 0.01" rather than 0. + stream << "< "; + if (precision > 0) { + stream << "0"; + stream << decimal_point; + for (int i = 0; i < precision - 1; i++) { + stream << "0"; + } + } + stream << "1"; + } else { + format_dec (stream, int_part, group_digits && grouping.length () > 0 ? thousands_sep : 0, grouping); + if (precision > 0 && frac_part > 0) { + stream << decimal_point; + format_frac (stream, frac_part, scale, precision); + } + } + return stream.str(); +} + +std::string rai::uint128_union::format_balance(rai::uint128_t scale, int precision, bool group_digits) +{ + auto thousands_sep = std::use_facet< std::numpunct >(std::locale ()).thousands_sep (); + auto decimal_point = std::use_facet< std::numpunct >(std::locale ()).decimal_point (); + std::string grouping = "\3"; + return ::format_balance (number (), scale, precision, group_digits, thousands_sep, decimal_point, grouping); +} + +std::string rai::uint128_union::format_balance (rai::uint128_t scale, int precision, bool group_digits, const std::locale & locale) +{ + auto thousands_sep = std::use_facet< std::moneypunct >(locale).thousands_sep (); + auto decimal_point = std::use_facet< std::moneypunct >(locale).decimal_point (); + std::string grouping = std::use_facet< std::moneypunct >(locale).grouping (); + return ::format_balance (number (), scale, precision, group_digits, thousands_sep, decimal_point, grouping); +} + void rai::uint128_union::clear () { qwords.fill (0); diff --git a/rai/lib/numbers.hpp b/rai/lib/numbers.hpp index d9f3e1ed..42648ec4 100644 --- a/rai/lib/numbers.hpp +++ b/rai/lib/numbers.hpp @@ -35,6 +35,8 @@ public: bool decode_hex (std::string const &); void encode_dec (std::string &) const; bool decode_dec (std::string const &); + std::string format_balance (rai::uint128_t scale, int precision, bool group_digits); + std::string format_balance (rai::uint128_t scale, int precision, bool group_digits, const std::locale & locale); rai::uint128_t number () const; void clear (); bool is_zero () const; diff --git a/rai/qt/qt.cpp b/rai/qt/qt.cpp index 4e4f0e28..504488a1 100755 --- a/rai/qt/qt.cpp +++ b/rai/qt/qt.cpp @@ -104,10 +104,10 @@ wallet (wallet_a) void rai_qt::self_pane::refresh_balance () { auto balance (wallet.node.balance_pending (wallet.account)); - auto final_text (std::string ("Balance (XRB): ") + (balance.first / wallet.rendering_ratio).convert_to ()); + auto final_text (std::string ("Balance: ") + wallet.format_balance (balance.first)); if (!balance.second.is_zero ()) { - final_text += "\nPending: " + (balance.second / wallet.rendering_ratio).convert_to (); + final_text += "\nPending: " + wallet.format_balance (balance.second); } wallet.self.balance_label->setText (QString (final_text.c_str ())); } @@ -253,10 +253,10 @@ void rai_qt::accounts::refresh_wallet_balance () balance = balance + (this->wallet.node.ledger.account_balance (transaction, key)); pending = pending + (this->wallet.node.ledger.account_pending (transaction, key)); } - auto final_text (std::string ("Wallet balance (XRB): ") + (balance / this->wallet.rendering_ratio).convert_to ()); + auto final_text (std::string ("Wallet balance (XRB): ") + wallet.format_balance (balance)); if (!pending.is_zero ()) { - final_text += "\nWallet pending: " + (pending / this->wallet.rendering_ratio).convert_to (); + final_text += "\nWallet pending: " + wallet.format_balance (pending); } wallet_balance_label->setText (QString (final_text.c_str ())); this->wallet.node.alarm.add (std::chrono::system_clock::now () + std::chrono::seconds (60), [this] () @@ -295,8 +295,7 @@ void rai_qt::accounts::refresh () if (display) { QList items; - std::string balance; - rai::amount (balance_amount / wallet.rendering_ratio).encode_dec (balance); + std::string balance = wallet.format_balance (balance_amount); items.push_back (new QStandardItem (balance.c_str ())); auto account (new QStandardItem (QString (key.to_account ().c_str ()))); account->setForeground (brush); @@ -467,7 +466,7 @@ wallet (wallet_a) }); } -rai_qt::history::history (rai::ledger & ledger_a, rai::account const & account_a, rai::uint128_t const & rendering_ratio_a) : +rai_qt::history::history (rai::ledger & ledger_a, rai::account const & account_a, rai_qt::wallet & wallet_a) : window (new QWidget), layout (new QVBoxLayout), model (new QStandardItemModel), @@ -478,7 +477,7 @@ tx_label (new QLabel ("Account history count:")), tx_count (new QSpinBox), ledger (ledger_a), account (account_a), -rendering_ratio (rendering_ratio_a) +wallet (wallet_a) {/* tx_count->setRange (1, 256); tx_layout->addWidget (tx_label); @@ -564,7 +563,7 @@ void rai_qt::history::refresh () block->visit (visitor); items.push_back (new QStandardItem (QString (visitor.type.c_str ()))); items.push_back (new QStandardItem (QString (visitor.account.to_account ().c_str ()))); - items.push_back (new QStandardItem (QString (rai::amount (visitor.amount / rendering_ratio).to_string_dec ().c_str ()))); + items.push_back (new QStandardItem (QString (wallet.format_balance (visitor.amount).c_str()))); items.push_back (new QStandardItem (QString (hash.to_string ().c_str ()))); hash = block->previous (); model->appendRow (items); @@ -678,7 +677,7 @@ refresh (new QPushButton ("Refresh")), balance_window (new QWidget), balance_layout (new QHBoxLayout), balance_label (new QLabel), -history (wallet_a.wallet_m->node.ledger, account, wallet_a.rendering_ratio), +history (wallet_a.wallet_m->node.ledger, account, wallet_a), back (new QPushButton ("Back")), account (wallet_a.account), wallet (wallet_a) @@ -706,10 +705,10 @@ wallet (wallet_a) show_line_ok (*account_line); this->history.refresh (); auto balance (this->wallet.node.balance_pending (account)); - auto final_text (std::string ("Balance (XRB): ") + (balance.first / this->wallet.rendering_ratio).convert_to ()); + auto final_text (std::string ("Balance (XRB): ") + wallet.format_balance (balance.first)); if (!balance.second.is_zero ()) { - final_text += "\nPending: " + (balance.second / this->wallet.rendering_ratio).convert_to (); + final_text += "\nPending: " + wallet.format_balance (balance.second); } balance_label->setText (QString (final_text.c_str ())); } @@ -840,7 +839,7 @@ node (node_a), wallet_m (wallet_a), account (account_a), processor (processor_a), -history (node.ledger, account, rendering_ratio), +history (node.ledger, account, *this), accounts (*this), self (*this, account_a), settings (*this), @@ -1257,6 +1256,18 @@ void rai_qt::wallet::change_rendering_ratio (rai::uint128_t const & rendering_ra })); } +std::string rai_qt::wallet::format_balance (rai::uint128_t const & balance) const +{ + auto balance_str = rai::amount (balance).format_balance (rendering_ratio, 2, true, std::locale ("")); + auto unit = std::string ("XRB"); + if (rendering_ratio == rai::kxrb_ratio) { + unit = std::string ("kxrb"); + } else if (rendering_ratio == rai::xrb_ratio) { + unit = std::string ("xrb"); + } + return balance_str + " " + unit; +} + void rai_qt::wallet::push_main_stack (QWidget * widget_a) { main_stack->addWidget (widget_a); diff --git a/rai/qt/qt.hpp b/rai/qt/qt.hpp index f2f33ff0..cca12358 100644 --- a/rai/qt/qt.hpp +++ b/rai/qt/qt.hpp @@ -207,7 +207,7 @@ namespace rai_qt { class history { public: - history (rai::ledger &, rai::account const &, rai::uint128_t const &); + history (rai::ledger &, rai::account const &, rai_qt::wallet &); void refresh (); QWidget * window; QVBoxLayout * layout; @@ -219,7 +219,7 @@ namespace rai_qt { QSpinBox * tx_count; rai::ledger & ledger; rai::account const & account; - rai::uint128_t const & rendering_ratio; + rai_qt::wallet & wallet; }; class block_viewer { @@ -288,6 +288,7 @@ namespace rai_qt { void update_connected (); void empty_password (); void change_rendering_ratio (rai::uint128_t const &); + std::string format_balance (rai::uint128_t const &) const; rai::uint128_t rendering_ratio; rai::node & node; std::shared_ptr wallet_m; diff --git a/rai/qt_test/qt.cpp b/rai/qt_test/qt.cpp index 25c688c2..3f15c9f0 100644 --- a/rai/qt_test/qt.cpp +++ b/rai/qt_test/qt.cpp @@ -436,6 +436,16 @@ TEST (wallet, create_change) TEST (history, short_text) { bool init; + rai_qt::eventloop_processor processor; + rai::keypair key; + rai::system system (24000, 1); + system.wallet (0)->insert_adhoc (key.prv); + rai::account account; + { + rai::transaction transaction (system.nodes [0]->store.environment, nullptr, false); + account = system.account (transaction, 0); + } + auto wallet (std::make_shared (*test_application, processor, *system.nodes [0], system.wallet (0), account)); rai::block_store store (init, rai::unique_path ()); ASSERT_TRUE (!init); rai::genesis genesis; @@ -451,7 +461,7 @@ TEST (history, short_text) rai::change_block change (receive.hash (), key.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, change).code); } - rai_qt::history history (ledger, rai::test_genesis_key.pub, rai::Gxrb_ratio); + rai_qt::history history (ledger, rai::test_genesis_key.pub, *wallet); history.refresh (); ASSERT_EQ (4, history.model->rowCount ()); }