Skip to content

Commit

Permalink
Create a rounding policy for prices in the pricedb.
Browse files Browse the repository at this point in the history
Currency-currency prices will be priced in the smaller currency so that
the price > 1 and will be rounded to 3 digits after the decimal.
Commodity-currency prices will be priced in the currency and rounded to
the currency's scu * 10000.
This affects only prices stored in the pricedb. Prices in splits will
continue to be computed from value/amount.
  • Loading branch information
jralls committed Sep 15, 2015
1 parent c7c97be commit 6b52077
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 26 deletions.
9 changes: 9 additions & 0 deletions src/engine/gnc-pricedb.h
Expand Up @@ -243,6 +243,15 @@ gboolean gnc_price_equal(const GNCPrice *p1, const GNCPrice *p2);
/** This simple function can be useful for debugging the price code */
void gnc_price_print(GNCPrice *db, FILE *f, int indent);
/** @} */
/** @name Denominator Constants Price policy: In order to avoid rounding
* problems, currency prices (often called exchange rates) are saved in terms of
* the smaller currency, so that price > 1, with a fixed denominator of
* 1/1000. Commodity prices in currency are always expressed as value per unit
* of the commodity with a fixed denominator of the pricing currency's
* SCU * 10000.
*/
#define CURRENCY_DENOM 1000
#define COMMODITY_DENOM_MULT 10000

/* ================================================================ */
/** @name GNCPrice lists
Expand Down
62 changes: 47 additions & 15 deletions src/gnome-utils/dialog-transfer.c
Expand Up @@ -55,8 +55,6 @@
#define DIALOG_TRANSFER_CM_CLASS "dialog-transfer"
#define GNC_PREFS_GROUP "dialogs.transfer"

#define PRECISION 1000000

typedef enum
{
XFER_DIALOG_FROM,
Expand Down Expand Up @@ -1003,15 +1001,16 @@ gnc_xfer_to_amount_update_cb(GtkWidget *widget, GdkEventFocus *event,
XferDialog *xferData = data;
gnc_numeric price_value;
Account *account;
int scu;

account = gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_TO);
if (account == NULL)
account = gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_FROM);

scu = xaccAccountGetCommoditySCU(account);
gnc_amount_edit_evaluate (GNC_AMOUNT_EDIT (xferData->to_amount_edit));

price_value = gnc_xfer_dialog_compute_price_value(xferData);
price_value = gnc_numeric_convert (price_value, PRECISION,
price_value = gnc_numeric_convert (price_value, scu * COMMODITY_DENOM_MULT,
GNC_HOW_RND_ROUND_HALF_UP);
gnc_amount_edit_set_amount(GNC_AMOUNT_EDIT(xferData->price_edit),
price_value);
Expand Down Expand Up @@ -1507,6 +1506,16 @@ swap_amount (gnc_commodity *from, gnc_commodity *to, gnc_numeric *value,
to_amt = tmp_amt;
*value = gnc_numeric_invert (*value);
}

static gnc_numeric
swap_commodities(gnc_commodity **from, gnc_commodity **to, gnc_numeric value)
{
gnc_commodity *tmp = *to;
*to = *from;
*from = tmp;
return gnc_numeric_invert(value);
}

static void
create_price(XferDialog *xferData, Timespec ts)
{
Expand All @@ -1516,6 +1525,7 @@ create_price(XferDialog *xferData, Timespec ts)
gnc_numeric price_value;
gnc_numeric value;
gnc_numeric from_amt, to_amt;
gboolean swap = FALSE;

/* Bail in the unlikely event that both currencies have joined the Euro. */
if (gnc_is_euro_currency (from) && gnc_is_euro_currency (to))
Expand All @@ -1530,21 +1540,17 @@ create_price(XferDialog *xferData, Timespec ts)
swap_amount (from, to, &value, &from_amt, &to_amt);
/* First see if the closest entry on the same day has an equivalent rate */
price = gnc_pricedb_lookup_day (xferData->pricedb, from, to, ts);
if (price)
{
price_value = gnc_price_get_value(price);
}
else
if (!price)
{
price = gnc_pricedb_lookup_day (xferData->pricedb, to, from, ts);
if (price)

price_value = gnc_numeric_invert(gnc_price_get_value(price));
swap = TRUE;
}

if (price)
{
if (gnc_numeric_equal(value, price_value))
if (gnc_numeric_equal(swap ? gnc_numeric_invert(value) : value,
price_value))
{
PINFO("Found price for %s in %s", gnc_commodity_get_mnemonic(from),
gnc_commodity_get_mnemonic(to));
Expand All @@ -1557,10 +1563,18 @@ create_price(XferDialog *xferData, Timespec ts)
gnc_price_unref (price);
return;
}
if (!gnc_numeric_eq(price_value, gnc_price_get_value(price)))
if (swap)
{
value = gnc_numeric_invert(value);
value = gnc_numeric_convert(value, PRECISION, GNC_HOW_DENOM_REDUCE);
value = swap_commodities(&from, &to, value);
}
if (gnc_commodity_is_currency(from) && gnc_commodity_is_currency(to))
value = gnc_numeric_convert(value, CURRENCY_DENOM,
GNC_HOW_RND_ROUND_HALF_UP);
else if (gnc_commodity_is_currency(to))
{
int scu = gnc_commodity_get_fraction (to);
value = gnc_numeric_convert(value, scu * COMMODITY_DENOM_MULT,
GNC_HOW_RND_ROUND_HALF_UP);
}
gnc_price_begin_edit (price);
gnc_price_set_time (price, ts);
Expand All @@ -1572,6 +1586,24 @@ create_price(XferDialog *xferData, Timespec ts)
gnc_price_unref (price);
return;
}
if (gnc_commodity_is_currency(from) && gnc_commodity_is_currency(to))
{
if (value.num < value.denom)
{
value = swap_commodities(&from, &to, value);
}
value = gnc_numeric_convert(value, CURRENCY_DENOM,
GNC_HOW_RND_ROUND_HALF_UP);
}
else if (gnc_commodity_is_currency(from) || gnc_commodity_is_currency(to))
{
int scu;
if (gnc_commodity_is_currency(from))
value = swap_commodities(&from, &to, value);
scu = gnc_commodity_get_fraction (to);
value = gnc_numeric_convert(value, scu * COMMODITY_DENOM_MULT,
GNC_HOW_RND_ROUND_HALF_UP);
}
price = gnc_price_create (xferData->book);
gnc_price_begin_edit (price);
gnc_price_set_commodity (price, from);
Expand Down
32 changes: 21 additions & 11 deletions src/register/ledger-core/split-register.c
Expand Up @@ -64,9 +64,9 @@ static QofLogModule log_module = GNC_MOD_LEDGER;
static CursorClass copied_class = CURSOR_CLASS_NONE;
static SCM copied_item = SCM_UNDEFINED;
static GncGUID copied_leader_guid;
static const int PRECISION = 1000000;


/* A denominator representing number of digits to the right of the decimal point
* displayed in a price cell. */
static int PRICE_CELL_DENOM = 1000000;
/** static prototypes *****************************************************/

static gboolean gnc_split_register_save_to_scm (SplitRegister *reg,
Expand Down Expand Up @@ -2053,6 +2053,7 @@ record_price (SplitRegister *reg, Account *account, gnc_numeric value)
gnc_commodity *curr = xaccTransGetCurrency (trans);
GNCPrice *price;
gnc_numeric price_value;
int scu = gnc_commodity_get_fraction(curr);
Timespec ts;
BasicCell *cell = gnc_table_layout_get_cell (reg->table->layout, DATE_CELL);

Expand All @@ -2073,10 +2074,10 @@ record_price (SplitRegister *reg, Account *account, gnc_numeric value)
price = gnc_pricedb_lookup_day (pricedb, curr, comm, ts);
if (price)
{
price_value = gnc_numeric_div (gnc_numeric_create(1, 1),
gnc_price_get_value(price),
GNC_DENOM_AUTO,
GNC_HOW_DENOM_REDUCE);
/* It might be better to raise an error here: We shouldn't be creating
* currency->commodity prices.
*/
price_value = gnc_numeric_invert(gnc_price_get_value(price));
}
}
if (price)
Expand All @@ -2092,9 +2093,15 @@ record_price (SplitRegister *reg, Account *account, gnc_numeric value)
return;
}
if (!gnc_numeric_eq(price_value, gnc_price_get_value(price)))
value = gnc_numeric_div (gnc_numeric_create(1, 1), value,
PRECISION, GNC_HOW_DENOM_REDUCE);

{
value = gnc_numeric_invert(value);
scu = gnc_commodity_get_fraction(comm);
value = gnc_numeric_convert(value, scu * COMMODITY_DENOM_MULT,
GNC_HOW_RND_ROUND_HALF_UP);
}
else
value = gnc_numeric_convert(value, scu * COMMODITY_DENOM_MULT,
GNC_HOW_RND_ROUND_HALF_UP);
gnc_price_begin_edit (price);
gnc_price_set_time (price, ts);
gnc_price_set_source (price, PRICE_SOURCE_SPLIT_REG);
Expand All @@ -2104,6 +2111,8 @@ record_price (SplitRegister *reg, Account *account, gnc_numeric value)
return;
}

value = gnc_numeric_convert(value, scu * COMMODITY_DENOM_MULT,
GNC_HOW_RND_ROUND_HALF_UP);
price = gnc_price_create (book);
gnc_price_begin_edit (price);
gnc_price_set_commodity (price, comm);
Expand Down Expand Up @@ -2589,7 +2598,8 @@ gnc_split_register_config_cells (SplitRegister *reg)
/* Use 6 decimal places for prices and "exchange rates" */
gnc_price_cell_set_fraction
((PriceCell *)
gnc_table_layout_get_cell (reg->table->layout, PRIC_CELL), PRECISION);
gnc_table_layout_get_cell (reg->table->layout, PRIC_CELL),
PRICE_CELL_DENOM);

/* Initialize shares and share balance cells */
gnc_price_cell_set_print_info
Expand Down

0 comments on commit 6b52077

Please sign in to comment.