Here is the scenario.
I am writing code for applying discounts and I am having all sorts of trouble.
Let us assume that my order has the following and as a sales rep I have decided to give the customer a discount of £23.47 for this order.
I have 3 line items, two of them costing £150.00 and one costing £10.00. The total value of order is £310.00
So, I need to calculate the %discount for each line item (because, the external interfaces cannot cope with an order level discount, but only a line item discount). Also, the external interfaces can cope with only one decimal in the Discount% field.
Logic:
I calculate the total (before discounting) of the order. It comes to £310.00.
I work out the discount amount as a percentage of the total order value, i.e., £23.47 / £310.00
The discount% works out to be 7.570967741935484%. However, this needs to be rounded off to 7.6%. If I do so, what happens is the discount amount is higher for each line item. The discount amount for each line item is higher. And the total discount I end up giving is £23.56 which is £0.08 more than what I intended to give, £23.47.
Each line item should get a discount of £11.36, £11.36 and £0.76 but I end up giving £11.40, £11.40 and £0.76.
What I need is to work out the "error" that I get in my rounding off for each line item and compensate for the error in the item which is least priced to which it can be applied so that the discount price is as close to what I intended to give (it should, however, be rounded up and NEVER rounded down).
When applying the discount to each line item, do I have to "remember" the remainder discount amount which needs to be applied?
Pseudo Code as follows.
iterating through each line item
//first line item where I have given more discount than intended. 4p more. So, the remaining discount is not £12.11 but actually £12.07
//Calculate the discount% for remaining line items, i.e.,
//And, I am confused :-)
What would be the best way of doing this?
PS: Not sure if this needs to be here. But feel free to point me in the right direction or delete it if not applicable to the SE sites.
Do not round to 7.6%, work out an exact(ish) discount for each item using the full 7.570967741935484%.
This will give you a fractional result. Round up if necessary but either way work out your fractional error.
On your next item, add the error from the previous item before deciding how to round.
Repeat.
I am afraid it's not always possible to achieve if you are constrained to the discount percentage with not more than 1 decimal place.
Imagine the following input: you are selling only one item, which costs £1000, and you want to give 1p discount. So, either you give 0.1% discount (resulting in the whole £1), or 0% (resulting in no discount at all).
In fact, the discount you may give can be expressed in 1/1000th of the smallest item price (from 1 up to 1000), possibly adding discounts from the second smallest item price etc.
So, sometimes you cannot achieve getting the amount of discount you really want -- what can be done in this case?
A simple approach would be to try combination of different discounts. Obviously, you can sell each item for P (its normal price), 999/1000 * P, 998/1000 * P, ... 0/1000 * P. So, you can try to find a coefficient for each item, which makes the total amount as close to the needed sum as possible. This simple idea would be unfortunately too slow if the number of items in the purchase is big.
(A note aside: if we were not constrained with only positive discounts, we could actually do the following trick: find the greatest common divisor of all the item prices, and having it, we could easily make up any price with the precision of GCD/1000.)
Note about negative discounts: imagine that you are allowed to give negative discounts as well. For example, you can give a discount of -1%, resulting in the customer paying 1% more for the item. This way you can achieve more accurate approximation. Example: if you've got 2 items, which cost £1000 and £990 respectively, and you want to give the total discount of 1p: You can achieve this by giving discount of 0.1% for the first item and -0.1% for the second item, so the customer pays £1000 * 99.9% = £999 for the first item and £990 * 100.1% = £990.99 for the second item. Altogether it's £1989.99, which is exactly 1p discount from £1990. One couldn't achieve such a precision with just positive discounts, you see?
If you apply a discount percentage to each line, you can't then apply the same discount percentage to the total price and get the same answer because of roundings. You need to sum the discounted prices on each line to get the total.
I have seen this done so many times on a number of systems and there is only the one way to get it to work. Each line has the correct price/value so you need to sum them up.
Related
I support a financial .net application. There is a lot of advice to use the decimal datatype for financial stuff.
Now I am stuck with this:
decimal price = 1.0m/12.0m;
decimal quantity = 2637.18m;
decimal result = price * quantity; //results in 219.76499999999999999999999991
The problem is that the correct value to charge our customer is 219.77 (round function, MidpointRounding.AwayFromZero) and not 219.76.
If I change everything to double, it seems to work:
double price = 1.0/12.0;
double quantity = 2637.18;
double result = price * quantity; //results in 219.765
Shall I change everything to double? Will there be other problems with fractions?
I think this question is different from Difference between decimal, float and double in .NET? because it does not really explain to me why the result with a more precise datatype decimal is less accurate (in the sample above) than the result with the double datatype which uses fewer bytes.
The reason decimal is recommended is that all numbers that can be represented as non-repeating decimals can be accurately represented in a decimal type. Units of money in the real world are always non-repeating decimals. Your problem as others have said is that your price is, for some reason, not representable as a non-repeating decimal. That is it is 0.083333333.... Using a double doesn't actually help in terms of accuracy - a double can not accurately represent 1/12 either. In this case the lack of accuracy is not causing a problem but in others it might.
Also more importantly the choice to use a double will mean there are many more numbers that you couldn't represent completely accurately. For example 0.01, 0.02, 0.03... Yeah, quite a lot of numbers you are likely to care about can't be accurately represented as a double.
In this case the question of where the price comes from is really the important one. Wherever you are storing that price almost certainly isn't storing 1/12 exactly. Either you are storing an approximation already or that price is actually the result of a calculations (or you are using a very unusual number storage system where you are storing rational numbers but this seems wildly unlikely).
What you really want is a price that can be represented as a double. If that is what you have but then you modify it (eg by dividing by 12 to get a monthly cost from an annual) then you need to do that division as late as possible. And quite possibly you also need to calculate the monthly cost as a division of the outstanding balance. What I mean by this last part is that if you are paying $10 a year in monthly instalments you might charge $0.83 for the first month. Then the second month you charge ($10-0.83)/11. This would be 0.83 again. On the fifth month you charge (10-0.83*4)/8 which now is 0.84 (once rounded). Then next month its (10-0.83*4-0.84)/7 and so on. This way you guarantee that the total charge is correct and don't worry about compounded errors.
At the end of the day you are the only one to judge whether you can re-architect your system to remove all rounding errors like this or whether you have to mitigate them in some way as I've suggested. Your best bet though is to read up on everything you can about floating point numbers, both decimal and binary, so that you fully understand the implications of choosing one over the other.
Usually, in financial calculations, multiplications and divisions are expected to be rounded to a certain number of decimal places and in a certain way. (Most currency systems use only base-10 amounts of money; in these systems, non-base-10 amounts of money are rare, if they ever occur.) Dividing a price by 12 without more is not always expected to result in a base 10 number; the business logic will dictate how that price will be rounded, including the number of decimal places the result will have. Depending on the business logic, such a result as 0.083333333333333333 might not be the appropriate one.
Here is a real world question: if I have a product A with a unit price of $25.8848 and I ordered 77 units of it then how I should calculate the subtotal and total?
I am actually getting confused about rounding issues. The code I have written is this:
Math.Round(item.Price, 2) * item.Qty
This should give me the correct subtotal and summing the other item will provided me correct total. Is the above code correct in terms of rounding? Or should it be done like this:
Math.Round(item.Price * item.Qty, 2)
I just want to know how the subtotal price rounding is done in real world.
Rounding should be applied per line item so the second method you have posted would be correct. Each line item subtotal should be rounded. Here is an example of it although it is coded in ruby you can get the idea etc
http://makandracards.com/makandra/1505-invoices-how-to-properly-round-and-calculate-totals
Either way is correct as far as C# is concerned. This is really a question about your company's accounting and business rules. Ideally this would be defined in the requirements document when the application was designed.
That being said, I would usually expect the 2nd option to be correct, apply the rounding AFTER the price is multiplied by the quantity.
We build software that audits fees charged by banks to merchants that accept credit and debit cards. Our customers want us to tell them if the card processor is overcharging them. Per-transaction credit card fees are calculated like this:
fee = fixed + variable*transaction_price
A "fee scheme" is the pair of (fixed, variable) used by a group of credit cards, e.g. "MasterCard business debit gold cards issued by First National Bank of Hollywood". We believe there are fewer than 10 different fee schemes in use at any time, but we aren't getting a complete nor current list of fee schemes from our partners. (yes, I know that some "fee schemes" are more complicated than the equation above because of caps and other gotchas, but our transactions are known to have only a + bx schemes in use).
Here's the problem we're trying to solve: we want to use per-transaction data about fees to derive the fee schemes in use. Then we can compare that list to the fee schemes that each customer should be using according to their bank.
The data we get about each transaction is a data tuple: (card_id, transaction_price, fee).
transaction_price and fee are in integer cents. The bank rolls over fractional cents for each transation until the cumulative is greater than one cent, and then a "rounding cent" will be attached to the fees of that transaction. We cannot predict which transaction the "rounding cent" will be attached to.
card_id identifies a group of cards that share the same fee scheme. In a typical day of 10,000 transactions, there may be several hundred unique card_id's. Multiple card_id's will share a fee scheme.
The data we get looks like this, and what we want to figure out is the last two columns.
card_id transaction_price fee fixed variable
=======================================================================
12345 200 22 ? ?
67890 300 21 ? ?
56789 150 8 ? ?
34567 150 8 ? ?
34567 150 "rounding cent"-> 9 ? ?
34567 150 8 ? ?
The end result we want is a short list like this with 10 or fewer entries showing the fee schemes that best fit our data. Like this:
fee_scheme_id fixed variable
======================================
1 22 0
2 21 0
3 ? ?
4 ? ?
...
The average fee is about 8 cents. This means the rounding cents have a huge impact and the derivation above requires a lot of data.
The average transaction is 125 cents. Transaction prices are always on 5-cent boundaries.
We want a short list of fee schemes that "fit" 98%+ of the 3,000+ transactions each customer gets each day. If that's not enough data to achieve 98% confidence, we can use multiple days' of data.
Because of the rounding cents applied somewhat arbitrarily to each transaction, this isn't a simple algebra problem. Instead, it's a kind of statistical clustering exercise that I'm not sure how to solve.
Any suggestions for how to approach this problem? The implementation can be in C# or T-SQL, whichever makes the most sense given the algorithm.
Hough transform
Consider your problem in image terms: If you would plot your input data on a diagram of price vs. fee, each scheme's entries would form a straight line (with rounding cents being noise). Consider the density map of your plot as an image, and the task is reduced to finding straight lines in an image. Which is just the job of the Hough transform.
You would essentially approach this by plotting one line for each transaction into a diagram of possible fixed fee versus possible variable fee, adding the values of lines where they cross. At the points of real fee schemes, many lines will intersect and form a large local maximum. By detecting this maximum, you find your fee scheme, and even a degree of importance for the fee scheme.
This approach will surely work, but might take some time depending on the resolution you want to achieve. If computation time proves to be an issue, remember that a Voronoi diagram of a coarse Hough space can be used as a classificator - and once you have classified your points into fee schemes, simple linear regression solves your problem.
Considering, that a processing query's storage requirements are in the same power of 2 as a day's worth of transaction data, I assume that such storage is not a problem, so:
First pass: Group the transactions for each card_id by transaction_price, keeping card_id, transaction_price and average fee. This can easily be done in SQL. This assumes, there are not outliers - but you can catch those at after this stage if so required. The resulting number of rows is guaranteed to be no higher than the number of raw data points.
Second pass: Per group walk these new data points (with a cursor or in C#) and calculate the average value of b. Again any outliers can be caught if desired after this stage.
Third pass: Per group calculate the average value of a, now that b is known. This is basic SQL. Outliers as allways
If you decide to do the second step in a cursor you can stuff all that into a stored procedure.
Different card_id groups, that use the same fee scheme can now be coalesced (Sorry of this is the wrong word, non-english native) into fee schemes by rounding a and b with a sane precision and again grouping.
The Hough transform is the most general answer, though I don't know how one would implement it in SQL (rather than pulling the data out and processing it in a general purpose language of your choice).
Alas, the naive version is known to be slow if you have a lot of input data (1000 points is kinda medium sized) and if you want high precision results (scales as size_of_the_input / (rho_precision * theta_precision)).
There is a faster approach based on 2^n-trees, but there are few implementations out on the web to just plug in. (I recently did one in C++ as a testbed for a project I'm involved in. Maybe I'll clean it up and post it somewhere.)
If there is some additional order to the data you may be able to do better (i.e. do the line segments form a piecewise function?).
Naive Hough transform
Define an accumulator in (theta,rho) space spanning [-pi,pi) and [0,max(hypotenuse(x,y)] as an 2D-array.
Foreach point in the input data
Foreach bin in theta
find the distance rho of the altitude from the origin to
a line through (a,y) and making angle theta with the horizontal
rho = x cos(theta) + y sin(theta)
and increment the bin (theta,rho) in the accumulator
Find the maximum bin in the accumulator, this
represents the most line-like structure in the data
if (theta !=0) {a = rho/sin(theta); b = -1/tan(theta);}
Reliably getting multiple lines out of a single pass takes a little more bookkeeping, but it is not significantly harder.
You can improve the result a little by smoothing the data near the candidate peaks and fitting to get sub-bin precision which should be faster than using smaller bins and should pickup the effect of the "rounding" cents fairly smoothly.
You're looking at the rounding cent as a significant source of noise in your calculations, so I'd focus on minimizing the noise due to that issue. The easiest way to do this IMO is to increase the sample size.
Instead of viewing your data as thousands of y=mx + b (+Rounding) group your data into larger subsets:
If you combine X transactions with the same and look at this as (sum of X fees) = (variable rate)*(sum of X transactions) + X(base rates) (+Rounding) your rounding number the noise will likely fall to the wayside.
Get enough groups of size 'X' and you should be able to come up with a pretty close representation of the real numbers.
How can I calculate a fixed payment amount for a loan term that has two different interest rates based on how long the loan has been open?
This gets a little ugly, so please bear with me.
Define:
g1 = Initial monthly rate (For 3%, g=0.03/12.)
g2 = Second monthly rate.
T1 = Term for the initial rate (T1 = 3 for 3 months).
T2 = Term for the subsequent rate.
u1 = 1 / (1 + g1)
u2 = 1 / (1 + g2)
Then:
payment = g1 * g2 / (g1 * u1^T1 * (1 - u2^T2) + g2 * (1 - u1^T1))
Of course, I may have made a mistake, but that seems right.
This is a pretty complicated calculation that is usually part of a company's intellectual property. So I doubt anyone is going to post code. I've been down this road and it requires huge amounts of testing depending on how far you decide to go with it.
When performing the calculations in code it is critical that you use a data type such as Decimal instead of the floating point types like double. Decimal was explicitly created for these types of money calculations. Floating point types will cause many rounding errors, making the calculated values be off by unacceptable amounts.
Next, mortgage calculators that you find online are of highly varying quality. When testing your method it will be useful to see what the online calculators come up with, but by no means consider them more accurate than yours. Generally they are good to see if you are in the right ballpark, but they could be off by as much as .1% per year of the loan term.
Final note
Consider purchasing a library from a company like Math Corp instead of rolling your own. I'm pretty sure it'll be accurate AND much cheaper than the dev / qa time to get yours right.
Loan contracts are very complex. If you don't want to dive into the complexity you have to make some simplifying assumptions. Here are some of the variables you need to consider:
What is the base rate? Does the loan float over Prime? Libor? CMT?
What is the margin above the base rate?
How often does the base rate reset?
What happens if the reset date falls on a holiday? A weekend?
Are there ceilings or floors on the base rate?
Is there an initial period at which the base rate is fixed before the first reset? How long is that period?
Is there an initial discount on the margin that is later adjusted (a teaser rate)?
What's the term of the mortgage?
Is it a negative-amortization mortgate? What's the stop period on the negative-amortizing payments?
Is it a fully-amortizing mortgage?
Is it a balloon mortgage?
Is the interest simple interest or compounded interest? If the latter, what's the compounding frequency?
As you can see, if you haven't specified enough about the problem that you are trying to solve to even begin to come up with a solution.
If you're not a domain expert on ARMs or financial products in general I strongly encourage you to find someone who is.
The pmt function is based on this math:
Payment = Loan Amount at current time / ( 1 - ( 1 / ( 1+ current rate)^numperiods remaining ) )
Figuring out the loan amount at the current time (i.e. after five years of making a payment at a different rate) is the tough part.
I am attempting to validate a range dollar amount. The value must be less than (not equal to) the input currency amount, which can be represented in multiple currencies.
How can I represent this value? Should I convert to decimal and subtract 0.01? If that doesn't work, why not? I'm using C#.
I'm possibly over-thinking an obvious solution, so an "Uhhhh do 'X' " type of response would not surprise me.
Thanks for any insight.
Seeing as how you say it can be in different currencies, Are the values you are comparing both of the same currency? If not then your range will need to be even more dynamic as some foreign currencies may result in a larger number than your initial value.
So for example if you are comparing a USD 100 to YEN which would be about 92 yen per dollar for 9200 yen (on average) for today. that would not really match up. It's best to compare same currency to same currency for ranges.
Also you never said what your range is. You are only talking about your Maximum. What's your min? Is it Zero what if you have a negative amount? If this was a trading application you could have positive and negatives.
Your best bet if you have a maximum value is to convert your entered value to whatever similar currency you have and then adjust validate that it's less than or equal to your max value. Which might be better in a codebehind validation vs a range validator.
So lets assume you are using a trading app. You have a Trade Limit say 1000 USD. The user is doing an international trade in YEN. When you go to do your checks you will have to get the FX Rate for the day, divide it into your YEN entered amount and verify that it's less than or equal to your limit. In a trading scenario you could use an ABS value so that a Buy or Sell net out to the same 1000 limit. Since this is a bit more complicated than a simple range check I would personally recommend doing your own validations either with jQuery / AJAX or just plain code behind.
May make your life much easier.