Why is float to double conversion uniquely terrible?
-
UPDATE #2: After going back to my original test number, and decoding it (literally) bit-by-bit, in each of the formats, before and after conversion, I'm baffled. The
float
->double
conversion does exactly what I would do. This makes it even more difficult to explain the outcome of my earlier test programs. The conversion itself seems accurate, but I'm clearly missing something. So, for now, I'm abandoning this post. I'll come back and update it when I answer my own question. Though, that will probably be after I write an article explaining the ridiculous trivia of exactly what C# does with each of the floating-point formats. After which, I've really got to get a life :) UPDATE #1: After further consideration, I reassert that there is something uniquely, and inexplicably, terrible about thefloat
->double
conversion! Some have suggested that this was something inherent with how floating point numbers are stored and not something uniquely terrible aboutfloat
->double
conversions. After a little bit of convincing, I concede that my original description did not exclude this possibility. While I intentionally chose a number with six decimal digits of precision (the limits of IEEE 754 binary32), perhaps unintentional bias led me to choose numbers that were particularly susceptible to this issue. So, to disprove my original premise, I wrote a new test program (included at the end of this post). This program generated random numbers with between one and six digits of precision. To avoid bias towards any one of the three floating-point formats, it calculates the "ideal" text from an integral value using only string manipulation to format it as floating-point. It then counts the number of times theToString
for the assigned values (decimal
,double
, andfloat
) and converted values (decimal
->double
,decimal
->float
,double
->decimal
,double
->float
,float
->decimal
, andfloat
->double
) differ from this ideal value. After running the program for 1 million cycles, the results were as follows: decimal: 0 double: 0 float: 0 decimal->double: 0 decimal->float: 0 double->decimal: 0 double->float: 0 float->decimal: 0 float->double: 750741 I reassert that there is something uniquely, and inexplicably, terribl -
UPDATE #2: After going back to my original test number, and decoding it (literally) bit-by-bit, in each of the formats, before and after conversion, I'm baffled. The
float
->double
conversion does exactly what I would do. This makes it even more difficult to explain the outcome of my earlier test programs. The conversion itself seems accurate, but I'm clearly missing something. So, for now, I'm abandoning this post. I'll come back and update it when I answer my own question. Though, that will probably be after I write an article explaining the ridiculous trivia of exactly what C# does with each of the floating-point formats. After which, I've really got to get a life :) UPDATE #1: After further consideration, I reassert that there is something uniquely, and inexplicably, terrible about thefloat
->double
conversion! Some have suggested that this was something inherent with how floating point numbers are stored and not something uniquely terrible aboutfloat
->double
conversions. After a little bit of convincing, I concede that my original description did not exclude this possibility. While I intentionally chose a number with six decimal digits of precision (the limits of IEEE 754 binary32), perhaps unintentional bias led me to choose numbers that were particularly susceptible to this issue. So, to disprove my original premise, I wrote a new test program (included at the end of this post). This program generated random numbers with between one and six digits of precision. To avoid bias towards any one of the three floating-point formats, it calculates the "ideal" text from an integral value using only string manipulation to format it as floating-point. It then counts the number of times theToString
for the assigned values (decimal
,double
, andfloat
) and converted values (decimal
->double
,decimal
->float
,double
->decimal
,double
->float
,float
->decimal
, andfloat
->double
) differ from this ideal value. After running the program for 1 million cycles, the results were as follows: decimal: 0 double: 0 float: 0 decimal->double: 0 decimal->float: 0 double->decimal: 0 double->float: 0 float->decimal: 0 float->double: 750741 I reassert that there is something uniquely, and inexplicably, terriblFloat (and Double) types are not directly convertible to Decimal due to the fact they are are held as binary values. See What Every Computer Scientist Should Know About Floating-Point Arithmetic[^] for the full explanation. Unless you specifically need to use floating point types (e.g for statistical analysis etc.) then you should stay well clear of them. For financial applications always use integer or decimal types.
-
UPDATE #2: After going back to my original test number, and decoding it (literally) bit-by-bit, in each of the formats, before and after conversion, I'm baffled. The
float
->double
conversion does exactly what I would do. This makes it even more difficult to explain the outcome of my earlier test programs. The conversion itself seems accurate, but I'm clearly missing something. So, for now, I'm abandoning this post. I'll come back and update it when I answer my own question. Though, that will probably be after I write an article explaining the ridiculous trivia of exactly what C# does with each of the floating-point formats. After which, I've really got to get a life :) UPDATE #1: After further consideration, I reassert that there is something uniquely, and inexplicably, terrible about thefloat
->double
conversion! Some have suggested that this was something inherent with how floating point numbers are stored and not something uniquely terrible aboutfloat
->double
conversions. After a little bit of convincing, I concede that my original description did not exclude this possibility. While I intentionally chose a number with six decimal digits of precision (the limits of IEEE 754 binary32), perhaps unintentional bias led me to choose numbers that were particularly susceptible to this issue. So, to disprove my original premise, I wrote a new test program (included at the end of this post). This program generated random numbers with between one and six digits of precision. To avoid bias towards any one of the three floating-point formats, it calculates the "ideal" text from an integral value using only string manipulation to format it as floating-point. It then counts the number of times theToString
for the assigned values (decimal
,double
, andfloat
) and converted values (decimal
->double
,decimal
->float
,double
->decimal
,double
->float
,float
->decimal
, andfloat
->double
) differ from this ideal value. After running the program for 1 million cycles, the results were as follows: decimal: 0 double: 0 float: 0 decimal->double: 0 decimal->float: 0 double->decimal: 0 double->float: 0 float->decimal: 0 float->double: 750741 I reassert that there is something uniquely, and inexplicably, terriblTo add to Richard's comments, you might get a surprise if you print the values to more digits of precision. There are literally billions of distinct numbers which print as
123.456
to 3 decimal places.Software rusts. Simon Stephenson, ca 1994. So does this signature. me, 2012
-
Float (and Double) types are not directly convertible to Decimal due to the fact they are are held as binary values. See What Every Computer Scientist Should Know About Floating-Point Arithmetic[^] for the full explanation. Unless you specifically need to use floating point types (e.g for statistical analysis etc.) then you should stay well clear of them. For financial applications always use integer or decimal types.
UPDATE: Seems I'm simply wrong on my point below. I was misled by some less than clear wording in one part of the specification. That said, I still find it odd that
float
->double
is the only one of the six possible C# floating-point conversions that is consistently less accurate in its apparent result. I tried a bunch of different values with this same consistent outcome. Something seems wrong to me. I do understand your point. It is a great generalized warning for those unwilling to learn the specifics of the precise floating-point data type they are using. If I were using something other than an IEEE 754binary32
data type (float
), or using more than six significant decimal digits, I would completely agree with you. However, since neither of these are the case here, I respectfully and completely disagree. The behavior is simply inconsistent with the IEEE 754 specification. The specification ofbinary32
provides 23 explicit bits for the significand. This provides accuracy for a minimum of six significant decimal digits. The specification explicitly details that the representation of this number of significant decimal digits (six or less) must be exactly accurate. In the case ofbinary64
, which has 52 explicit bits for the significand, the specification requires accuracy to a minimum of 15 significant decimal digits. -
To add to Richard's comments, you might get a surprise if you print the values to more digits of precision. There are literally billions of distinct numbers which print as
123.456
to 3 decimal places.Software rusts. Simon Stephenson, ca 1994. So does this signature. me, 2012
Based on my misinterpretation of the IEEE 754 specification, which I foolishly shared in a response to Richard, it seems today is a day full of surprises for me :) That said, I would not expect converting from IEEE 754 binary32 to binary64 would make things apparently worse. I tried a bunch of different values. Of the six possible floating point conversions this one consistently yields the worst apparent outcome. If anything, I would have expected converting binary64 to binary32 to have the worst apparent outcome.
-
UPDATE: Seems I'm simply wrong on my point below. I was misled by some less than clear wording in one part of the specification. That said, I still find it odd that
float
->double
is the only one of the six possible C# floating-point conversions that is consistently less accurate in its apparent result. I tried a bunch of different values with this same consistent outcome. Something seems wrong to me. I do understand your point. It is a great generalized warning for those unwilling to learn the specifics of the precise floating-point data type they are using. If I were using something other than an IEEE 754binary32
data type (float
), or using more than six significant decimal digits, I would completely agree with you. However, since neither of these are the case here, I respectfully and completely disagree. The behavior is simply inconsistent with the IEEE 754 specification. The specification ofbinary32
provides 23 explicit bits for the significand. This provides accuracy for a minimum of six significant decimal digits. The specification explicitly details that the representation of this number of significant decimal digits (six or less) must be exactly accurate. In the case ofbinary64
, which has 52 explicit bits for the significand, the specification requires accuracy to a minimum of 15 significant decimal digits.It depends on the number you start with. Since accuracy is not guaranteed with floats and doubles, you can get inconsistencies, because the number will often be an approximation. Floating point's strength is (was) its ability to represent very large or very small numbers with reasonable, but not absolute, accuracy. For most business applications it should, as I suggested earlier, be avoided like the plague.
Eric Lynch wrote:
those unwilling to learn the specifics of the precise floating-point data type they are using.
Or in many cases (see QA) those who are still being taught to use it.
-
It depends on the number you start with. Since accuracy is not guaranteed with floats and doubles, you can get inconsistencies, because the number will often be an approximation. Floating point's strength is (was) its ability to represent very large or very small numbers with reasonable, but not absolute, accuracy. For most business applications it should, as I suggested earlier, be avoided like the plague.
Eric Lynch wrote:
those unwilling to learn the specifics of the precise floating-point data type they are using.
Or in many cases (see QA) those who are still being taught to use it.
Yeah, we cross-posted. I had already retracted my...I'll call it "point"? Regrettably, I misinterpreted the IEEE 754 specification. I can only wish that I realized my mistake before posting :) That said, I still suspect there is something less than ideal ocurring with the
float
->double
conversion. I'm working on some code to more rigorously explore the issue. I'll either prove or disprove my suspicions. I'll come back later and post whatever I find. -
Yeah, we cross-posted. I had already retracted my...I'll call it "point"? Regrettably, I misinterpreted the IEEE 754 specification. I can only wish that I realized my mistake before posting :) That said, I still suspect there is something less than ideal ocurring with the
float
->double
conversion. I'm working on some code to more rigorously explore the issue. I'll either prove or disprove my suspicions. I'll come back later and post whatever I find. -
Float (and Double) types are not directly convertible to Decimal due to the fact they are are held as binary values. See What Every Computer Scientist Should Know About Floating-Point Arithmetic[^] for the full explanation. Unless you specifically need to use floating point types (e.g for statistical analysis etc.) then you should stay well clear of them. For financial applications always use integer or decimal types.
I finished my new tests and updated my original post to include the results. I stand by my original premise. There is something uniquely, and inexplicably, terrible about
float
->double
conversions. The test randomly generated 1 million numbers with between one and six digits of precision. Of the six possible floating-point conversions, only theToString
forfloat
->double
ever differed from the "expected" text. It did so a whopping 750,741 times. I beleive this is what they call "statistically significant" :) -
To add to Richard's comments, you might get a surprise if you print the values to more digits of precision. There are literally billions of distinct numbers which print as
123.456
to 3 decimal places.Software rusts. Simon Stephenson, ca 1994. So does this signature. me, 2012
I conducted some more rigorous experiments. This explanation doesn't match the experimental evidence. I now feel somewhat certain, based on evidence from a million randomly generated numbers, that there is something uniquely, and inexplicably, terrible about
float
->double
conversions. I qualify it with "somewhat", because I refuse to be completely wrong, about the same thing, twice in a single day :) -
Float (and Double) types are not directly convertible to Decimal due to the fact they are are held as binary values. See What Every Computer Scientist Should Know About Floating-Point Arithmetic[^] for the full explanation. Unless you specifically need to use floating point types (e.g for statistical analysis etc.) then you should stay well clear of them. For financial applications always use integer or decimal types.
OK, this is sadly nearing obsessional :( I've gone through the effort of decoding every dang bit in the IEEE 754 formats. Near as I can tell, the
float
->double
conversion is doing absolutely what I would expect of it. Though, it is still yielding a result that has the appearance of being worse. I'm currently baffled...maybedouble.ToString()
is the culprit? Maybe its something else entirely? I'm going to give this a whole lot more thought tomorrow...at a decent hour. Though, since it has no practical impact on anything I'm actually doing, I should probably let it go. Regrettably, intellectual curiosity has a firm hold of me at this point :) Below you'll find the output from my latest program, where I enter the text "123.456". "Single" / "Double" are conversions from the results ofdecimal
.Parse
. "Single (direct)" / "Double (direct)" are the results offloat
.Parse
/double
.Parse
. The remainder are the indicated conversions of the results of afloat
.Parse
.Single: Sign=0, Exponent=6 (10000101), Significand=7793017 (11101101110100101111001)
123.456
Single (direct): Sign=0, Exponent=6 (10000101), Significand=7793017 (11101101110100101111001)
123.456
Double: Sign=0, Exponent=6 (10000000101), Significand=4183844053827191 (1110110111010010111100011010100111111011111001110111)
123.456
Double (direct): Sign=0, Exponent=6 (10000000101), Significand=4183844144021504 (1110110111010010111100100000000000000000000000000000)
123.456001281738
float->double: Sign=0, Exponent=6 (10000000101), Significand=4183844144021504 (1110110111010010111100100000000000000000000000000000)
123.456001281738
float->decimal->double: Sign=0, Exponent=6 (10000000101), Significand=4183844053827191 (1110110111010010111100011010100111111011111001110111)
123.456After this much effort, I guess I'll eventually be forced to write an article on every useless bit of trivia I can find about all of these formats :)
-
UPDATE #2: After going back to my original test number, and decoding it (literally) bit-by-bit, in each of the formats, before and after conversion, I'm baffled. The
float
->double
conversion does exactly what I would do. This makes it even more difficult to explain the outcome of my earlier test programs. The conversion itself seems accurate, but I'm clearly missing something. So, for now, I'm abandoning this post. I'll come back and update it when I answer my own question. Though, that will probably be after I write an article explaining the ridiculous trivia of exactly what C# does with each of the floating-point formats. After which, I've really got to get a life :) UPDATE #1: After further consideration, I reassert that there is something uniquely, and inexplicably, terrible about thefloat
->double
conversion! Some have suggested that this was something inherent with how floating point numbers are stored and not something uniquely terrible aboutfloat
->double
conversions. After a little bit of convincing, I concede that my original description did not exclude this possibility. While I intentionally chose a number with six decimal digits of precision (the limits of IEEE 754 binary32), perhaps unintentional bias led me to choose numbers that were particularly susceptible to this issue. So, to disprove my original premise, I wrote a new test program (included at the end of this post). This program generated random numbers with between one and six digits of precision. To avoid bias towards any one of the three floating-point formats, it calculates the "ideal" text from an integral value using only string manipulation to format it as floating-point. It then counts the number of times theToString
for the assigned values (decimal
,double
, andfloat
) and converted values (decimal
->double
,decimal
->float
,double
->decimal
,double
->float
,float
->decimal
, andfloat
->double
) differ from this ideal value. After running the program for 1 million cycles, the results were as follows: decimal: 0 double: 0 float: 0 decimal->double: 0 decimal->float: 0 double->decimal: 0 double->float: 0 float->decimal: 0 float->double: 750741 I reassert that there is something uniquely, and inexplicably, terriblThis discussion from 2011 looks relevant:
c# - Convert float to double loses precision but not via ToString - Stack Overflow[^]:
What you get in the more precise representation (past a certain point) is just garbage. If you were to cast it back to a float FROM a double, you would have the exact same precision as you did before.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer