www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Is using floating point type for money/currency a good idea?

reply Boqsc <vaidas.boqsc gmail.com> writes:
https://dlang.org/spec/float.html

I'm frozen in learning basics of D lang since I want to create a 
simple game and I really would like a clean and simple code, 
however to me floating points are magic.

https://wiki.dlang.org/Review_Queue
Since std.decimal is still work in progress and apparently its 
development is stuck for a while, should I just somehow use 
floating point to store currency or wait until Decimal package 
will be finally included into std Phobos of D lang?
May 20 2019
next sibling parent reply Josh <foo bar.com> writes:
On Monday, 20 May 2019 at 11:10:32 UTC, Boqsc wrote:
 https://dlang.org/spec/float.html

 I'm frozen in learning basics of D lang since I want to create 
 a simple game and I really would like a clean and simple code, 
 however to me floating points are magic.

 https://wiki.dlang.org/Review_Queue
 Since std.decimal is still work in progress and apparently its 
 development is stuck for a while, should I just somehow use 
 floating point to store currency or wait until Decimal package 
 will be finally included into std Phobos of D lang?
Normally I would say no, no and no. Rounding issues will kill you every time. However: import std.stdio; import std.range; void main() { foreach (i; iota(1, 1000)) { writefln("%f", cast(float) i / 100.0); } } Doesn't show any rounding issues, but you still might hit them at large values...I just remember back in the day having numbers like 0.199999999999999, but maybe that problem has been solved. What would be safer is to do "fixed point" math, i.e. use an integer and when displaying the value convert it to float and divide by 100. So if the user has 50 cents they internal value would be 50, if you give them a dollar twenty five (125) then they would have 175, and when you go to display it, dividing by 100 will display 1.75. As long as you are not multiplying money by money you will be fine. 50 cents times 5 is 2.50 (50 * 5 = 250), however 50 cents times 50 cents is 2500, which makes no sense but so does multiplying money by money...
May 20 2019
next sibling parent ag0aep6g <anonymous example.com> writes:
On 20.05.19 13:47, Josh wrote:
 Normally I would say no, no and no.  Rounding issues will kill you every 
 time.
And you'd be right.
  However:
 
 import std.stdio;
 import std.range;
 
 void main()
 {
      foreach (i; iota(1, 1000)) {
          writefln("%f", cast(float) i / 100.0);
      }
 }
 
 Doesn't show any rounding issues, but you still might hit them at large 
 values...I just remember back in the day having numbers like 
 0.199999999999999, but maybe that problem has been solved.
That "problem" hasn't been solved. It can't be solved. `1 / 100.0` might print as 0.01, but it isn't exactly 0.01. The output is just rounded. If you add more decimals (e.g. `writefln("%.20f", ...)`), those niners will show up. Aside from large and small values, you'll also see significant (accumulated) rounding error when doing many operations on your numbers. For example, if you add 0.1 ten thousand times, you might expect the result to be 1000, but with `float` it's not that: import std.stdio; void main() { float x = 0; foreach (i; 0 .. 10_000) x += 0.1; writeln(x); /* Prints "999.903". */ }
 What would be safer is to do "fixed point" math, i.e. use an integer and 
 when displaying the value convert it to float and divide by 100.  So if 
 the user has 50 cents they internal value would be 50, if you give them 
 a dollar twenty five (125) then they would have 175, and when you go to 
 display it, dividing by 100 will display 1.75.
Yup.
 As long as you are not multiplying money by money you will be fine.  50 
 cents times 5 is 2.50 (50 * 5 = 250), however 50 cents times 50 cents is 
 2500, which makes no sense but so does multiplying money by money...
50 cents times 50 cents is 2500 square cents. Makes perfect sense.
May 20 2019
prev sibling parent kdevel <kdevel vogtner.de> writes:
On Monday, 20 May 2019 at 11:47:25 UTC, Josh wrote:
 On Monday, 20 May 2019 at 11:10:32 UTC, Boqsc wrote:
 https://dlang.org/spec/float.html

 I'm frozen in learning basics of D lang since I want to create 
 a simple game and I really would like a clean and simple code, 
 however to me floating points are magic.

 https://wiki.dlang.org/Review_Queue
 Since std.decimal is still work in progress and apparently its 
 development is stuck for a while, should I just somehow use 
 floating point to store currency or wait until Decimal package 
 will be finally included into std Phobos of D lang?
Normally I would say no, no and no. Rounding issues will kill you every time.
Right. Say a chemical is sold at 3165 $/m³. A customer orders 1 ℓ. According to the "round to nearest or even" rule what is the amount invoiced (no taxes)? For the non-SI world: 1 m³ = 1000 ℓ. [...]
 		writefln("%f", cast(float) i / 100.0);
[...] Why cast the integer to float and then device by a double? writefln("%f", i / 100.); accomplishes the same with less reading. Back to the invoice: I would like to promote decimal here: import std.stdio; import std.math; import std.conv; import decimal; void calculate (T) () { writefln ("\n%s", T.stringof); T price_per_m3 = 3165; T quantity = "0.001".to!T; T amount = price_per_m3 * quantity; writefln ("%.2f %.24f", amount, amount); } void main () { calculate!decimal32; calculate!decimal64; calculate!decimal128; calculate!float; calculate!double; calculate!real; }
May 20 2019
prev sibling next sibling parent reply Dennis <dkorpel gmail.com> writes:
On Monday, 20 May 2019 at 11:10:32 UTC, Boqsc wrote:
 I'm frozen in learning basics of D lang since I want to create 
 a simple game and I really would like a clean and simple code,
For a simple game, I think it's the easiest to just store an integer of cents (or the lowest amount of currency possible). Then storing, adding and comparing just works. Only when you want to show it to the user you need some special logic: ``` void printMoney(int cents) { writeln("You have $", cents / 100, ".", cents % 100); } ``` If currency is very prevalent or you like to abstract it, you can take a look at dub packages aimed at money/decimal types (or write your own if existing libraries don't suit your needs). http://code.dlang.org/packages/money http://code.dlang.org/packages/stdxdecimal http://code.dlang.org/packages/decimal http://code.dlang.org/packages/fixed
May 20 2019
parent Era Scarecrow <rtcvb32 yahoo.com> writes:
On Monday, 20 May 2019 at 11:50:57 UTC, Dennis wrote:
 For a simple game, I think it's the easiest to just store an 
 integer of cents (or the lowest amount of currency possible).
Back in 2003 i did this very thing for when creating my C program for suggesting and helping with credit card payments, so it could make suggestions of which ones to pay off in what amounts, as i found using float to be too unreliable. Another option could be to use floats to a very limited state, say after calculations (cay calculating interest) you could convert to an int then back to float. (money = cast(float) (cast(int)money*100) /100)) to get as clean/close to the proper value as you can. (Though you may still end up off by a fraction of a penny). Josh's suggestion is close: writefln("%f", cast(float) i / 100.0); The format should be "%f.2", which should do fine for your purposes as it would round to the correct value (unless you're working with numbers over say 16Million, then float will start giving issues, and doubles might be needed). A third option could be to make your own type, a fixed precision int, which would be a struct that you determine precision by some arbitrary means (bits, digits, etc) and then do your tostring method to handle output appropriately. Not the best, but it would also work. I suppose the last method would be to make a BCD (Binary Coded Decimal) type. This is used in calculators (and 8bit BASIC) where it's a 6 byte value where every nibble (4bits) is a digit. The first byte is the exponent (+/- 128) and 5 bytes store 10 digits of data allowing VERY precise values. But the overhead and setup seems a bit impractical outside of emulation. The easiest i would think is just consider the currency a whole number, and if you are working with fractions, only do the conversion when printing/outputting.
May 20 2019
prev sibling next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Monday, 20 May 2019 at 11:10:32 UTC, Boqsc wrote:
 https://dlang.org/spec/float.html

 I'm frozen in learning basics of D lang since I want to create 
 a simple game and I really would like a clean and simple code, 
 however to me floating points are magic.

 https://wiki.dlang.org/Review_Queue
 Since std.decimal is still work in progress and apparently its 
 development is stuck for a while, should I just somehow use 
 floating point to store currency or wait until Decimal package 
 will be finally included into std Phobos of D lang?
For a simple game storing money in cents, as an int or long, is perfectly fine. If you're worried that $92_233_720_368_547_758.07 (long.max) is not enough money for your game, I'd note that the entire current world economy is about a thousandth of that. Even so, there's std.bigint.BigInt, which has no set limit, and can in theory represent every whole number up to about 256^(2^64), or about 4 quintillion digits. You will encounter other problems before this limit becomes an issue. Depending on the scale and required accuracy, you could also perfectly well use doubles. Doubles can accurately represent up to about half the world economy in cents. There are some caveats when using doubles, as floating-point math is not always equal to regular math. For instance, if you had the entire world economy in one variable, you could subtract a cent at a time without the variable decreasing. If these cents were simultaneously added to a different variable (to represent a transaction), this would be an infinite source of money. In the same vein, it's possible for (a+b) != ((a-x)+(b+x)) if a and b are wildly different in size. For a game, this is very unlikely to be a problem, but would certainly be worth considering for a real-world application. For almost every application, a long representing the number of cents should cover almost every situation, and is very easy to reason about, so I would definitely recommend that. This decision could cause overflow issues in some rare cases, and calculating interest on extreme values could be slightly inaccurate depending on how you do it, but those situations are extreme enough that you can reasonably expect them to never happen. The one case where floating-point almost invariably will enter the picture is foreign exchange - the value of one unit of one currency in some other currency will only very rarely be a whole number. -- Simen
May 20 2019
next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 20 May 2019 at 12:50:29 UTC, Simen Kjærås wrote:
 There are some caveats when using doubles, as floating-point 
 math is not always equal to regular math. For instance, if you 
 had the entire world economy in one variable, you could 
 subtract a cent at a time without the variable decreasing. If 
 these cents were simultaneously added to a different variable 
 (to represent a transaction), this would be an infinite source 
 of money.
Yes, but it is fine until 2^53 which is the number 9007199254740992. So better than 31 bit ints and only slightly worse than 63 bit (signed). Doubles behave like like integers from 0 up to 2^53 for addition, so if you do cents in double then you also get accurate representation of cents while also getting decent fractional representation when converting to another currency. If it is important for the game to not leak resources then I guess you would have to recalibrate the system from time to time. It might be hard to find where money leak. Integers have the same problem, however. If it is a game, you might want to use the same storage-type and system for all countable resources, though. Then you could do something more advanced and memory efficient, either one storage object per owner, or as a global database object. So if you have lots of (unlimited) resource-pools (accounts, bottles, containers etc) to track you could to create your own "bank" or "wallet" as an in-memory database which uses variable length data records with encapsulated memory management on a local heap. So if you have 200 units it uses 1 byte more than an empty account, if you have a billion units it uses 4 bytes, a trillion units uses 5 bytes, 1000 trillion units use 7 bytes more… One advantage of using a "bank" is that you can ensure that resources don't leak as you have to set up a "transaction" between "accounts". (the same amount of money/resources are both added or removed at single point). Less error-prone. If it is a simulation game then it would also be easier to create logs that are useful for debugging/balancing if you have a mandatory "transaction" concept (you get a single point where all a transactions have to pass, and there you can log it). Anyway, lots of options. Have fun!
May 20 2019
prev sibling parent Era Scarecrow <rtcvb32 yahoo.com> writes:
On Monday, 20 May 2019 at 12:50:29 UTC, Simen Kjærås wrote:
 If you're worried that $92_233_720_368_547_758.07 (long.max) is 
 not enough money for your game, I'd note that the entire 
 current world economy is about a thousandth of that. Even so, 
 there's std.bigint.BigInt, which has no set limit, and can in 
 theory represent every whole number up to about 256^(2^64), or 
 about 4 quintillion digits. You will encounter other problems 
 before this limit becomes an issue.
Yes at that point BigInt would be a better solution. I made a NoGC fixed int type that would allow you to have any sized int (once defined, Cent anyone?) and only use stack data/space for calculations; It did fairly decently performance-wise (still need to do the special assembly instructions for supported 128/256 cryto stuff for faster speed on supported hardware), but otherwise it worked. Truthfully working around with the registers and carry and other details can be a chore, though only on divide, everything else is straightforward. Hmmmm going with numbers very very very very large would probably be more idle games more than anything else. Though I'm not sure if many need actual precision (as after you're over say E+10 over what you used to have, what you buy isn't really relevant til it starts getting only e+4 off; So even rounding errors wouldn't matter much. Not sure what format those games use. Tempted to believe it's a two pair int (one for exponent and one for currency, letting you get E+4billion, would be easy to code and do calculations overall, i don't see BigInts being used everywhere)
May 20 2019
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, May 20, 2019 at 11:10:32AM +0000, Boqsc via Digitalmars-d-learn wrote:
 https://dlang.org/spec/float.html
 
 I'm frozen in learning basics of D lang since I want to create a
 simple game and I really would like a clean and simple code, however
 to me floating points are magic.
Read this: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
 https://wiki.dlang.org/Review_Queue
 Since std.decimal is still work in progress and apparently its
 development is stuck for a while, should I just somehow use floating
 point to store currency or wait until Decimal package will be finally
 included into std Phobos of D lang?
You do *not* want to use floating-point for money/currency (unless your currency is in base 2). This is because certain decimal fractions cannot be represented exactly in binary, and sooner or later you will run into unexpected roundoff errors. Just use a plain old integer with a fixed decimal point. I.e., 10.25 is represented as 1025 with an implicit division by 100. I wouldn't even bother with BigInt unless you expect to be dealing with sub-cent money amounts or extremely large amounts of money. BigInt allows arbitrary precision, but at the cost of slower operations. For a game, it's overkill. Just use int (or long) with fixed-point operations. T -- PNP = Plug 'N' Pray
May 20 2019
parent Steven Lee <StevenLee46Ite yahoo.com> writes:
Me and my financial adviser think not. Unfortunately, this is not 
the best way..
Aug 20 2020
prev sibling parent rikki cattermole <rikki cattermole.co.nz> writes:
On 20/05/2019 11:10 PM, Boqsc wrote:
 https://dlang.org/spec/float.html
 
 I'm frozen in learning basics of D lang since I want to create a simple 
 game and I really would like a clean and simple code, however to me 
 floating points are magic.
 
 https://wiki.dlang.org/Review_Queue
 Since std.decimal is still work in progress and apparently its 
 development is stuck for a while, should I just somehow use floating 
 point to store currency or wait until Decimal package will be finally 
 included into std Phobos of D lang?
While other posters have explained the best practice for currency in the real world, for games even AAA uses floats. Don't fret it, it doesn't matter. You won't loose actual money if its off by a few cents here or there.
May 20 2019