Link to home
Start Free TrialLog in
Avatar of DColin
DColinFlag for Thailand

asked on

For Next loop not counting correctly

Hi Experts,

When I execute the attached code the loop does not count from 1 to 2 in the expected way.

1
1.1
...
1.9
2.0

Instead it doe something like this

1
1.1
1.2000000000000002
...
1.9000000000000008
(never reaches 2)

I am sure it is because binary can not store the number 0.1 accurately so what is the accepted way to count this loop.

For LoopCount As Double = 1 To 2 Step 0.1
   ListBox1.Items.Add(LoopCount)
Next

Open in new window

Avatar of ladarling
ladarling
Flag of United States of America image

Your double is being cast to 'Object' in the add() method, which is causing 'widening' of your number. Assert the Double to Decimal at the last second to preserve the narrowing during the cast to object.
Try:
ListBox1.Items.Add(CDec(LoopCount))
Avatar of Mike Tomlinson
The ToString() method is being implicitly called on the resulting decimal in ladarling's above code:

    ListBox1.Items.Add(CDec(LoopCount))     ----->     ListBox1.Items.Add(CDec(LoopCount).ToString)

You could just call it directly like this:

    For LoopCount As Double = 1 To 2 Step 0.1
        ListBox1.Items.Add(LoopCount.ToString)
    Next

Another option is to use FormatNumber which also returns a string, but there are a lot of options as far as how to...well...format the number....

Also, to have 2.0 in the list, you'd actually have to step up to 2.1....

        For LoopCount As Double = 1 To 2.1 Step 0.1
            ListBox1.Items.Add(FormatNumber(LoopCount, 1))
        Next
Or go from 10 to 20 instead and display it with the formatting in ToString() itself:

        For LoopCount As Integer = 10 To 20
            ListBox1.Items.Add(CDbl(LoopCount / 10).ToString("0.0"))
        Next
Idle_Mind said: The ToString() method is being implicitly called on the resulting decimal in ladarling's above code.
Why makes you say that? Casting to the 'Object' type does not imply a conversion of any kind, since all types widen to object. And conversion from double to decimal types is explicit and narrowing within the numeric types, and also does not imply a string conversion.
http://msdn.microsoft.com/en-us/library/k1e94s7e(VS.80).aspx

ListBox items are type Object, so I am not confused by your assertion...
And by not confused, I me 'confused", lol. Confusing, eh?
The act of converting to decimal with CDec() doesn't call ToString()...on that you are correct.  =)

It is the ListBox itself that is calling ToString() on your Decimal object when it displays it on the GUI for you.

See: http://msdn.microsoft.com/en-us/library/system.windows.forms.listbox.objectcollection.add.aspx

    "When an object is added to the collection, the ListBox first checks to see if the DisplayMember property of the ListControl class has the name of a member from the object specified to reference when obtaining the item text. If the DisplayMember property does not have a member specified, the ListBox then calls the ToString method of the object to obtain the text to display in the list."

It is the ToString() call (that is automatically being done for you) that actually causes the proper display of the value...not the CDec() conversion.

This is evidenced by the snippet I posted:

    For LoopCount As Double = 1 To 2 Step 0.1
        ListBox1.Items.Add(LoopCount.ToString)
    Next

It resulted in the output below.  Note that NO conversion to Decimal was necessary at all...  =)


ListBox.jpg
ASKER CERTIFIED SOLUTION
Avatar of ladarling
ladarling
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
A perplexing problem...    *** knocks head on table ***

The ListBox.Items.Add() method expects Objects...thus we get a widening conversion on the Double to Object when it is stored in the ListBox.

It is still a Double though as the code below shows.

(Immediate Window Output)
Double : 1
Double : 1.1
Double : 1.2
Double : 1.3
Double : 1.4
Double : 1.5
Double : 1.6
Double : 1.7
Double : 1.8
Double : 1.9

But the ListBox shows it differently so as you said, "...there seems to be something else going on with the listbox control.".

To be honest, I really don't know why the ListBox's ToString() call results in different output than calling ToString() directly on the Double...or even iterating the Items() collection manually calling ToString() on each item.

I tried many combinations, converting to Object and then back to Double...I even tried displaying the values using ToString() with System.Globalization.CultureInfo.CurrentCulture and System.Globalization.CultureInfo.InvariantCulture but still couldn't reproduce the ListBoxes output.

I did find that setting the ListBox.FormatString() property to "N1" results in the desired output...not really an answer to the underlying question...just found it interesting.

So please enlighten us with a TECHNICAL answer on why the ListBox output is different.

I love learning new things.  ;)


Public Class Form1
 
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        For LoopCount As Double = 1 To 2 Step 0.1
            ListBox1.Items.Add(LoopCount)
        Next
    End Sub
 
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        For Each O As Object In ListBox1.Items
            Debug.Print(TypeName(O) & " : " & O)
        Next
    End Sub
 
End Class

Open in new window

ListBox.jpg
Avatar of DColin

ASKER

Hi Experts,

My problem is not with the list box display but the fact that this loop will not count to 2. I thought this is because binary can not store the number 0.1 so the loop does not count
1
1.1
1.2
...
1.9
2.0
But instead counts as closely to binary as the double's precision will allow. This means that the last two counts should be.
1.9
2.0
But due to the precision limitations of binary are actually.
1.900000000000008
2.000000000000009
And therefore the loop count will not go past 2 and so halts at 1.9000000000008. If this is the case how do I get around this problem using the accepted methods. VB.Net does not have a Binary Coded Decimal number type.
In ladarlings last comment he stated:

    "I would point out to the author, by the way, that if you use a Decimal for LoopCount in the first place, all of our discussion here is moot. It works like a champ (and even actually displays 2.0)."

So change your variable from Double to Decimal.
Avatar of DColin

ASKER

Thank you Idle Mind I missed ladarling's Decimal advice. It would seem VB.Net has a BCD data type after all.
Idle: So please enlighten us with a TECHNICAL answer on why the ListBox output is different.

Chris: I have no Idea! I was hoping you would...lol. I did the same kind of thing you did, experimenting with debug output, etc, and could not figure it out. My *theory* is that the Listbox's double>object conversion operator is busted. I guess I could start digging through the ListBox code with the MSIL disassembler, but that sounds like tedious, tedious work (to which I am strongly opposed :-)
 But, I will post it for further exploration. Maybe someone out there has the technical solution, not just a theory. I dont. :-)