25 Jun, 2014, Oliver wrote in the 1st comment:
Votes: 0
I'm at a bit of a loss. I've converted all costs to doubles in my game using a similar format to the way we notate dollars and cents ($10.31 is 10 dollars and 31 cents). As such I've written a function, cost_string(), which accepts a double and prints its string.

//cost in gold, gives string in monies
void cost_string(char *string, double cost)
{
log_f("FLOAT: %g",cost);
double gold = 0, silver = 0;
gold = (int)cost;
silver = (int)((cost - gold) *100);

if (gold>0&&silver>0)
sprintf(string,"%g gold and %g silver",gold,silver);
else if (gold>0&&silver<=0)
sprintf(string,"%g gold",gold);
else if (gold<=0&&silver>0)
sprintf(string,"%g silver",silver);
else sprintf(string,"free");

return;
}


This function works okay most of the time.

Quote
> 20 health 100 move
cost 10.32
Cost set to 10 gold and 32 silver (10.32).


But sometimes does this:

Quote
> 20 health 100 move
cost 10.54
Cost set to 10 gold and 53 silver (10.54).


Here's some more examples:

Quote
> 20 health 100 move
cost 10.51
Cost set to 10 gold and 50 silver (10.51).


> 20 health 100 move
cost 10.52
Cost set to 10 gold and 51 silver (10.52).


> 20 health 100 move
cost 10.53
Cost set to 10 gold and 52 silver (10.53).


> 20 health 100 move
cost 10.54
Cost set to 10 gold and 53 silver (10.54).


> 20 health 100 move
cost 10.55
Cost set to 10 gold and 55 silver (10.55).


So I do a little bit of toying around, and after watching the variable value, I find that in the function cost_string(), at line 7, this cast from double to int is decrementing its value by 1. I have absolutely no idea why. If I leave the function as thus:

//cost in gold, gives string in moneys
void cost_string(char *string, double cost)
{
log_f("FLOAT: %g",cost);
double gold = 0, silver = 0;
gold = (int)cost;
silver = ((cost - gold) *100);

if (gold>0&&silver>0)
sprintf(string,"%g gold and %g silver",gold,silver);
else if (gold>0&&silver<=0)
sprintf(string,"%g gold",gold);
else if (gold<=0&&silver>0)
sprintf(string,"%g silver",silver);
else sprintf(string,"free");

return;
}


There are no problems. This seems incredibly odd. I originally wrote this function with the "gold" and "silver" variables themselves as integers, and performed operations that were cast in-line to doubles, allowing the value-set of the "gold/silver" variables to implicitly cast to int. I had this same problem, so I rewrote the function using all double variables, but used the integer cast (because silver is the game's smallest denomination and there's no need for decimals).

The function works sans integer cast, but it just seems to me like there's something I'm missing here. Casts shouldn't work that way. Any of you folks seeing what I apparently can't?

For reference, the function being called when the cost is set is:
OEDIT( oedit_cost )
{
OBJ_INDEX_DATA *pObj;

EDIT_OBJ(ch, pObj);

if (!*argument)
{
send_to_char( "Syntax: cost [number (decimals for silver)]\r\n", ch );
return FALSE;
}

pObj->cost = atof( argument );
char buf[MSL];
cost_string(buf,atof(argument));
printf_to_char( ch, "Cost set to %s (%g).\r\n", buf, atof(argument));
return TRUE;
}
25 Jun, 2014, Kaz wrote in the 2nd comment:
Votes: 0
You have a problem. You add floating point. Now you have 2.00000001 problems.

The short version:
10.539999999999 printed to two decimal places will print 10.54.
10.539999999999 multiplied by 100 and then cast to an int will *truncate* it to 1053.

The usual solution is to add 0.5 just before casting to int.
25 Jun, 2014, Oliver wrote in the 3rd comment:
Votes: 0
Kaz said:
You have a problem. You add floating point. Now you have 2.00000001 problems.

The short version:
10.539999999999 printed to two decimal places will print 10.54.
10.539999999999 multiplied by 100 and then cast to an int will *truncate* it to 1053.

The usual solution is to add 0.5 just before casting to int.


I'm not entirely clear on why GCC would treat atof("10.54") as a double-value 10.5399999999 though? Why not 10.5400000? And why wouldn't the usual solution, if this is a common problem, be to do what I've done: use truncated doubles?
26 Jun, 2014, Tyche wrote in the 4th comment:
Votes: 0
You will find trove of posts on every language forum from Python to Ruby to C to Java asking why floating point doesn't work, complaining that a particular language is broken, etc.
Maybe this article will help:
What Every Computer Scientist Should Kno...

I've made frequent use of Mike Cowlingshaw's decNUmber library in my code.
26 Jun, 2014, quixadhal wrote in the 5th comment:
Votes: 0
Short answer… if you don't want to deal with roundoff errors, dont' use floating point. If your language supports it, use fixed point. If not, multiply by 10 enough times to not need decimal points. :)

This is why many MUD's use copper for all their calculations, stored as an integer. Now that 64-bit integers are easily available, if your players accumulate more than 2^63 copper pieces of wealth, you have other issues.
26 Jun, 2014, Oliver wrote in the 6th comment:
Votes: 0
Tyche said:
You will find trove of posts on every language forum from Python to Ruby to C to Java asking why floating point doesn't work, complaining that a particular language is broken, etc.
Maybe this article will help:
What Every Computer Scientist Should Kno...

I've made frequent use of Mike Cowlingshaw's decNUmber library in my code.


Ah. See, this is the answer. That's interesting and in years of writing C/C++ code I've never really encountered an issue like this. I suppose that's a result of rarely needing to cast between integer and floats.


quixadhal said:
Short answer… if you don't want to deal with roundoff errors, dont' use floating point. If your language supports it, use fixed point. If not, multiply by 10 enough times to not need decimal points. :)

This is why many MUD's use copper for all their calculations, stored as an integer. Now that 64-bit integers are easily available, if your players accumulate more than 2^63 copper pieces of wealth, you have other issues.


What? This isn't why they use copper for calculations really.

I don't think you read the snippets I posted; the exact process underwent in the code was "multiplying by [10] enough times to not need decimal points," yet the issue was that the rounding didn't play out as expected. Unless, of course, you mean to use gold and silver as separate integers and multiply units of gold by ten, but this isn't exactly an efficient strategy (and there's no real reason to do it this way).
26 Jun, 2014, quixadhal wrote in the 7th comment:
Votes: 0
In cases where it matters, I've always used the base currency for ALL calculations and then used division to display it in multiple coinage denominations.

So, if you had 10 copper to the silver and 20 silver to the gold.

cp = money
gp = cp / 200 # integer floor division.
cp -= (gp * 200)
sp = cp / 10
cp -= (sp * 10)

printf "You have %d gold, %d silver, and %d copper", gp, sp, cp


453cp == 2 gold, 5 silver, 3 copper.

If you're going to use floating point values for some reason, you have to ALSO use the math library floor() function whenever you do divisions, or you will end up with roundoff errors at some point. Extra if you have that one series of Pentium CPU's that had the FDIV bug. :)
26 Jun, 2014, Twisol wrote in the 8th comment:
Votes: 0
Oliver said:
I'm not entirely clear on why GCC would treat atof("10.54") as a double-value 10.5399999999 though? Why not 10.5400000? And why wouldn't the usual solution, if this is a common problem, be to do what I've done: use truncated doubles?


Because 10.54 has no finite representation in base 2, just as 1/7th (0.142857…) has no finite representation in base 10. If we try base 7, of course, we can write it as 0.1, which is very much finite.

Play around with this some: http://www.exploringbinary.com/binary-co...

You'll notice that 10.54 produces "1010.10001010001111010111000010100011110101110000101000111101011100…" That can't be stored within a single double - it's infinite, after all. So we approximate as closely as possible within 64 bits: "1010.100010100011110101110000101000111101011100001010001111010111". Since we're truncating the rest of the number, we're only approximating 10.54, so we really have something like 10.5399999999.


When you care about the exact value of a real number, never use floating point. If you're working with rational numbers, use fixed point or (at worst) store the numerator and denominator separately. Fixed point just means you identify the least common denominator for all possible values (if you're working with US dollars, this would be 100) and multiply all values by this number so that everything is integral. Thus, you would store $50.41 as (50.41 * 100) = 5041.
26 Jun, 2014, alteraeon wrote in the 9th comment:
Votes: 0
It should be noted that the standards for financial transaction processing in the RL financial system explicitly prohibit the use of floating point math. The stuff I've been involved with used BCD for all financial data, explicitly to prevent rounding and representational issues.

Alter Aeon MUD
http://www.alteraeon.com
26 Jun, 2014, Rarva.Riendf wrote in the 10th comment:
Votes: 0
Alteraeon: indeed, and you can find many librairies in the scientific domain that even use strings to make their calculation when absolute precision matter.
27 Jun, 2014, Oliver wrote in the 11th comment:
Votes: 0
Twisol said:
Oliver said:
I'm not entirely clear on why GCC would treat atof("10.54") as a double-value 10.5399999999 though? Why not 10.5400000? And why wouldn't the usual solution, if this is a common problem, be to do what I've done: use truncated doubles?


Because 10.54 has no finite representation in base 2, just as 1/7th (0.142857…) has no finite representation in base 10. If we try base 7, of course, we can write it as 0.1, which is very much finite.


Yeah, I realized this after glancing over the link that Tyche sent me. It's an elementary mistake I suppose, but something I haven't run into in the past because you don't usually have to do anything bitwise with floats.


quixadhal said:
In cases where it matters, I've always used the base currency for ALL calculations and then used division to display it in multiple coinage denominations…


And yeah– I'm aware of this method, seeing as how it's almost always done that way; for several reasons it can't work like this on my MUD (or could, but to the detriment of my sanity).
0.0/11