Annoying fault with text and double; strtod()

Discussion in 'Programmer's Corner' started by THE_RB, Nov 17, 2011.

  1. THE_RB

    Thread Starter AAC Fanatic!

    Feb 11, 2008
    5,435
    1,305
    String to double function in Borland C is giving me some headaches.

    I have a text string txt[] confirmed as "16.90"
    double blah;
    blah = strtod(txt,&endptr);

    display blah = 16.90, all OK

    blah = (blah * 100);

    display blah = 1690, all OK, that's an integer.

    signed long x;
    x = blah;

    display x = 1689 (what the???)

    More testing shows this rounding down fault happens on many numbers... Positive and negative. I assume it's from the compiler assigning x = blah. I tried the usual typecasting kludges etc to no avail.

    Can anyone suggest an accurate way to extract a number with decimal point from a string that WON'T fail? I'm not going to trust double or strtod() any further... :(
     
  2. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    What happens when you try the following?:

    double blah;
    signed long x;

    blah = 16.90;
    blah = blah * 100;
    x = blah;
     
  3. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,394
    1,606
    If I follow you you are doing the following:

    double blah = 16.90;
    blah *= 100;
    signed long x;
    x = blah;

    Then you get the unexpected:

    display x = 1689 (what the???)


    This is due to blah not being an integer but a double, and it isn't exactly equal to 1690, but something like 1689.9999999999999999. When assigning a double to an integer your compiler is just taking the integer portion (which is acceptable by the ANSII C standard). To force a rounding up behavior just add 0.5:

    double blah = 16.90;
    blah *= 100;
    signed long x;
    x = blah + 0.5;

    Then you get the expected:

    display x = 1690
     
  4. THE_RB

    Thread Starter AAC Fanatic!

    Feb 11, 2008
    5,435
    1,305
    MrChips- Thanks for the suggestion but I have re coded and debugged it all now so it would be hard to test (I needed to get the software working). :)

    ErnieM- Thanks for your input, yes I think you are very right on the rounding issue;
    " it isn't exactly equal to 1690, but something like 1689.9999999999999999."

    What annoys me is the strtod() gets a string of "16.90" so there is no need for the double to have .9 repeating. Then displaying the double displays "16.90".
    (The display routine happily shows ten decimal places if the double is using them).

    Equally annoying when the double later becomes an integer it displays as "1690". If the double was really 1689.99999999 then it should display as such, as other double values with lots of decimal places display fine.

    Everything I tested pointed to a bug in the way the double is stored or the assignment; x = blah, as all the display code to display any double value worked when tested. :(

    Thanks too for the rounding up suggestion, I didn't want to force rounding up as I needed to retain the exact data as specified in the text file.

    The solution I chose was to write a function to manually extract the text number, and faithfully multiply it up to become an integer. My main code uses integers for the coords anyway, so the double was just a way to easily extract a floating point value from the text. Not so easy apparently. I added manual handling of the decimal fraction and now it is 100% accurate.

    Beware the double, it's doing something sneaky and not telling you. ;)
     
  5. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    I am guessing that the two statements

    blah = 16.90;

    and

    blah = strtod(txt,&endptr);

    are evaluated differently. The first is coverted into double by the compiler. The second is executed at runtime by a library routine. Hence it is possible that you might see different results.

    In either case, the number is stored in floating-point representation and that can be the source of the problem.

    This is a known fact about computers that more people need to know. Floating-point numbers are NOT precise. Integers are precise.

    Google "the pitfalls of floating-point arithmetic".

    Here are some examples:

    http://en.wikipedia.org/wiki/Floating_point

    http://www.codeproject.com/KB/recipes/float_point.aspx

    What Every Computer Scientist Should Know About Floating-Point Arithmetic
     
    Last edited: Nov 19, 2011
  6. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    I looked at the problem using Turbo C++ Ver 3.0.
    ErnieM is correct. Either conversion, from a literal or a string to double will give you 16.8999999999999986

    So instead of typecasting to integer you must round. You can use the round( ) function.

    As ErnieM suggests, this is not a bug. This is the definition of the int( ) function.

    Adding 0.5 before converting to int only works for positive numbers. You will get the wrong answer if the number is negative. For negative numbers you have to add -0.5.
     
    Last edited: Nov 19, 2011
  7. THE_RB

    Thread Starter AAC Fanatic!

    Feb 11, 2008
    5,435
    1,305
    Cool, thanks for the info.

    The compiler bug then is in the display code for displaying longs, as it displays "16.90" for a stored long value of "16.8999999999 etc". That's the kind of bug that happens when you add rounding (especially invisibly in the compiler). ;)
     
  8. t06afre

    AAC Fanatic!

    May 11, 2009
    5,939
    1,222
    The "bug" is well known problem then using floating point notation. In all program languages. The floating point notation is not exact like inter values. That is why you should be very careful then doing operations like compering two floating point numbers. At some point an operation that should give the as result the number 16. May instead give something like 15.99999999998 or 16.0000000000001. And none of these number are equal t0 16. In some cases this may be very hard to debug, if you are not aware of it
     
  9. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    Again, I don't think this is a compiler bug. I can only assume that your runtime display routine printf( ) or sprintf( ) is applying an epsilon check before displaying the value.

    t06afre, I know what you mean. But just in case others are not aware, the value 16.0 in floating-point format would be stored as an exact value with no error.
     
  10. t06afre

    AAC Fanatic!

    May 11, 2009
    5,939
    1,222
    Indeed the number 16 stored as a floating point will be correct. What I meant was if you do some calculations and this is compared to the latter number. You may end up with a result that is slightly off as I showed and hence the compare will fail. Maybe not the best example but but the principal is there
     
  11. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    Yes. That is why as a programmer you must not test for equality (==). You should use >= or <=.
     
  12. THE_RB

    Thread Starter AAC Fanatic!

    Feb 11, 2008
    5,435
    1,305
    Exactly. Since I can load the double with 16.8999999999 and the display function displays "16.8999999999" then when it displays "16.90" I expect the double value to be 16.90. And if the actual value in the double is really 16.8999999999 then I expect it to display "16.8999999999".
     
  13. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    But do you understand that the number 16.90 does not exist in floating-point format?
     
  14. t06afre

    AAC Fanatic!

    May 11, 2009
    5,939
    1,222
    Yes that is quite correct. The closest number is +16.89999961853 using 8 bytes format. Check out this link. And note the links at the page bottom
    http://steve.hollasch.net/cgindex/coding/ieeefloat.html
     
  15. THE_RB

    Thread Starter AAC Fanatic!

    Feb 11, 2008
    5,435
    1,305
    Yes, I've been a programmer for 20+ years. I understand that float and double are binary. ;)

    The compiler was capable of assigning 16.90 to the double; blah = 16.90;
    which displayed without fault and multiplied and displayed without fault.

    So if the compiler can assign a value to the double that is fully functional as 16.90 in all future operations, why is it that strtod("16.90") cannot? Obviously the compiler assignment is rounding up to the closest possible value above 16.90 (as would be expected) but strtod() is rounding down (which is essentially a fault as it produces disfunction on all subsequent operations).

    It is the user's expectation that translating "16.90" would produce the best value in the double in just the same way as assigning 16.90 would expect the compiler to assign the best value into the double.
     
  16. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    Neither the compiler nor the strtod( ) library function is at fault. Both are expected to represent 16.90 in floating-point format as best as they could. If they are both using the same rules they will both end up with the same results. The problem is 16.90 exactly does not exist in floating-point format.

    What is happening is that your printf( ) or sprintf( ) function is applying an epsilon test and assumes that you would rather see 16.90 displayed instead of 16.899999999.

    The solution around the problem is to use round( ) before you typecast to an integer.
     
  17. THE_RB

    Thread Starter AAC Fanatic!

    Feb 11, 2008
    5,435
    1,305
    Yes rounding up the result of an imperfect transformation is one solution for sure. :)

    The better solution was to do what I did, spend a few minutes writing a function that transforms the text number to a decimal number without error, as the primary objective was to faithfully extract a decimal coordinate from a text file and no error or adjustment or rounding was required.

    Of course if strtod() had transformed the text to the closest binary number *above* 16.90 than all subsequent operations (many of which may include integer truncation) would have worked without fault.
     
Loading...