6

Can some more experienced guys explain this strange error I found today? I was getting strange amounts when I was loading table data with my C# script.

As it turns out the problem is a different output from similar functions:

string amount_in_string = "1234567,15";
double amount_in_double = double.Parse(amount_in_string);
float amount_in_float = float.Parse(amount_in_string);

//amount_in_double = 1234567.15;
//amount_in_float = 1234567.13;

Why do I get such a different result when float and double are similar types (floating point). Can the precision make a difference with small amounts like these?

7
  • 6
    You should be using decimal for this, not double or float. Commented Aug 23, 2018 at 14:55
  • 2
    @Fildor @ron "amount" is just a quantity not necessarily a currency. OP hasn't indicated currencies. Also, be nice, no need to use ! Commented Aug 23, 2018 at 14:56
  • @Fildor you should provide an explanation as to why he shouldn't floating point types Commented Aug 23, 2018 at 15:02
  • @MickyD You are correct, "amount" could also be non-monetary. Comment retracted. Sidenote: I am not that sensitive about "!". Where I come from they (when used in singular "!", not "!!!!!!") do not cause offense. Commented Aug 23, 2018 at 15:07
  • 2
    JonSkeet has a very good explanation on this subject, basically double and float are binary point types (for example, a double or a float can never be equal to 0.1 as is) and decimal is a decimal (base 10) point type. If you're looking for point accuracy, you should be using decimal instead! Commented Aug 23, 2018 at 15:16

3 Answers 3

8

When “1234567.15” is converted to double, the result is the closest value representable in double, which is 1234567.1499999999068677425384521484375. Although you report in the question that the value is 1234567.15, the actual value is 1234567.1499999999068677425384521484375. “1234567.15” would be displayed when the value is displayed with a limited number of decimal digits.

When “1234567.15” is converted to float, the result is the closet value representable in float, which is 1234567.125. Although you report the value is 1234567.13, the actual value is 1234567.125. “1234567.13” may be displayed when the value is displayed with a limited number of decimal digits.


Observe that 1234567 exceeds 1,048,576, which is 220. The 32-bit floating-point format used for float uses 24 bits for the significand (fraction portion of the number). If the high bit of the significand represents 220, the low bit represents 220−23 = 2−3 = ⅛. This is why you see “1234567.15” converted to a value rounded to the nearest eighth.

Sign up to request clarification or add additional context in comments.

6 Comments

This is interesting! Nice example, I think I finally get it, thanks. So when parsing float my "1234567.15" to float it gets "1234567" + nearest 1/8 so its "1234567.125". And why did it cut to 2 decimals? I didn't specify that..:)
@IvanBosmansky: You did not show us how you got “1234567.13”; it merely appears in your question as a comment. So we do not know if you displayed it with some formatter, if you set some format/conversion specifications, or what. You would have to show complete code for that to be explained.
@MickyD: For float, round to the nearest ⅛, as stated. For double, round to the nearest 2^(20-52) = 2^-32. The easiest way to do this rounding is to convert the number with double.Parse and then print it with 32 or more decimal digits after the decimal point. (I do not know whether C# will print so many digits accurately. Microsoft has not always provided good conversion routines. I used Apple’s developer tools for this, which do convert accurately. I used C: printf(%.99g\n", 1234567.15);.) If one wanted to look at the math in more detail, extended-precision software could help.
Yeah I tried experimenting with the numbers as well as printing them out in different formats and this is definitely correct - it behaves like you describe. Many thanks to you for great explanation. Sorry guys for not getting it sooner, I just needed to see the numbers I guess
Just a fancy way of saying "loss of precision" hahah. +1
|
1

Floating point numbers are never exact, they are representations of numbers. An example commonly used is think of

1/3 + 1/3 = 2/3

...so the answer in floating point numbers, .33333 + .33333, is not 2/3rds exactly, it is .66666.

Long story short, the more precise fraction you take that can't be converted to an exact binary is going to always have a rounding number. The more precise the more likely it will have rounding errors.

Keep in mind if you do multiple different fractions, you can even have multiple different rounding errors that either make the number accidentally correct, or even further off.

6 Comments

A double is 64 bit, a floating is 32 bit. More precision means different rounding.
Not sure OP's question is about rational numbers though
I'm not sure why I'm being downvoted here. @MickyD that may be, but he's parsing a string to a double and a float based on the same string. The point is there is a least significant bit in any of these float-based number types that will cause rounding errors. I have a feeling if Op multiplied their result by a couple of factors of 10 then their answer would be equal.
Here's Microsoft's article explaining about how IEEE floating-point format works with rounding: learn.microsoft.com/en-us/cpp/build/reference/…
@obizues Not the downvoter, but the problem is with the , (culture), not exactly with the types used. Besides, multiplication can't be an answer: what will happen in the case of string ammount_in_string = 34028229999999999999999999999999999999.15? Multiplying by 10 will become an OverflowException
|
-2

A fiddle you can see the results (there is a culture problem here too)

https://dotnetfiddle.net/Lnv1q7

    string amount_in_string = "1234567,15"; // NOTE THE COMMA in original
    string amount_in_string = "1234567.15"; //my correct culture
    double amount_in_double = double.Parse(amount_in_string);
    float amount_in_float = float.Parse(amount_in_string);

    Console.WriteLine(amount_in_string);
    Console.WriteLine(amount_in_double);
    Console.WriteLine(amount_in_float);

Results (parsing in the incorrect culture!)

1234567,15
123456715
1.234567E+08

Results (parsing in the correct culture!)

1234567.15
1234567.15
1234567

Another one that demonstrates the loss of precision with float

float flt = 1F/3;
double dbl = 1D/3;
decimal dcm = 1M/3;
Console.WriteLine("float: {0} double: {1} decimal: {2}", flt, dbl, dcm);

Result

float: 0.3333333  
double: 0.333333333333333  
decimal: 0.3333333333333333333333333333

floats should only be used in cases were some loss of precision is not extremely valuable. This is because floats are 32bit where decimal is 128 bit floats are mainly used in pixel coordinates, and loss of precision doesn't matter because a the consumer translates locations to more precise coordinates any way.

in .NET floats "should" be actively avoided unless you don't care about loss of (some) precision. Which is probably never now a days (unless you writting games)

This is where the banking problem came from when 100th's of a penny/cent across transactions where lost, seemingly invisible per transaction but amounting to large amounts of "missing" money.

Use decimal

14 Comments

Why the downvotes? I demonstrated loss of precision twice by using standard, simple c# code. Plus an example here used from another SO answer with 1k upvotes. I hate drive by downvotes.. nobody learns anything. code of conduct should force comments on down votes
When you empathize in .NET floats should never be used (and leave "unless you don't care about loss of (some) precision." out) my mind only reads the bold part. Maybe you can change it to just Using floats in .NET may (or will, depending on the accuracy required) cause loss of precision.
hm, mind that you never got "1234567.13" as OP is claiming to have.
How can 1234567.13 be caused by a culture problem? If the locale is mismatched, the parser ought to generate an exception or take the comma as a thousands separator. Neither of these would produce 1234567.13, nor would accepting and converting the input up to the invalid character. Accepting and converting the entire string to 32-bit floating-point, with the comma or period taken as the decimal point, would produce 1234567.125, which could be displayed as 1234567.13.
The code at the link you posted in the answer produces 1234567.125 for float.Parse. It is merely displayed as “1234567” because apparently the default formatting limits the digits shown. Printing more digits would show “1234567.13”. So OP’s result is not due to a locale/culture mismatch but is due to floating-point precision.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.