1.

Solve : VB6 number formatting?

Answer»

If I run the following code:

Code: [Select]Dim i As DOUBLE

For i = 0 To 5 Step 0.001
    If i = 2 Then MsgBox "bla"
Next i

It never happens because i is never exactly 2 for some reason. It is something like 2.000000754268376. I can fix this using the following. . .

Code: [Select]Dim i As Double
Dim j As Double

For i = 0 To 5 Step 0.001
    j = Val(Format(i, "#.###"))
    If j = 2 Then MsgBox "bla"
Next i

My question is: Is there a better/more efficient way of doing this? I have a massive loop and I don't want it slowed down anymore. This is not a new issue. I was fixed ages ago.
I depends on what your really want to do.
Why would you want to loop in fractional increments anyway?
Somewhere this was documented, but I don't remember whee. You are just supposed to know it. Microsoft basic does not do math in base ten.
If you want to count things the have integer values, like counting sheep, you use whole numbers. Never fractions.

In applications where a fraction is needed, but a fraction that represents division of an integer, you need to rethink what you want to do.

By definition real numbers do not have exact values. When you count by 0.5 you are not saying exactly 0.5, but a number very close to it.  So by that definition 0.5 times 4 is not exactly 2, but a value very close to two, but it can not be two as a integer number.

In Visual Basic, as well as other things like it, you have to be very careful about looking for an exact natch when using real numbers. If you do get a match, the numbers are not just equal, they are identical. In engineering with real numbers you never expect an exact march

Please come back and explain why you  step in fractional values.

It would help if you could explain the practical value of what you want to do. There are special functions can can take care of this problem, when you really need to find two real numbers that are very, very close.

Sorry if  I sound dogmatic. Some versions of Microsoft Basic with 'fix' this problem automatically. But it really is the responsibility of the programmer to use the right data types.

EDIT: This link is better that how I said it. It is about .NETm but the ideas represent most earlyir versions of Microsoft Basic.

From: extremeoptimization.com
Floating Point in .NET part 1: Concepts and Formats Quote from: Geek-9pm on March 19, 2012, 12:55:50 AM

By definition real numbers do not have exact values. When you count by 0.5 you are not saying exactly 0.5, but a number very close to it.  So by that definition 0.5 times 4 is not exactly 2, but a value very close to two, but it can not be two as a integer number.

I suppose I know what you're getting at, but this is nonsense. Some non-integer real numbers can be represented exactly in a number base, some cannot. 0.5 is a rational number which can be represented exactly in base 10, but not in BINARY (hence the problem).

The OP needs to read a good programming book, with particular reference to data types, binary representation, floating point arithmetic, and rounding errors.

Salmon Trout, My remark was in the context of Microsoft basic.

The NOTATION "0.5" could indicate a value result of a division of an integer. But it might be a real number. In Microsoft Basic it will be cast as a real number and it no longer sis the result of a division of an integer. Unless the programmer did something to force it to another type.

There are formats used in MS Basic the keep numbers in a form that is related to an integer. The currency type is such. It is actually a long integer. But when printed or displayed, it is shown as a decimal fraction.

Once a number as been converted to a real floating point number, it loses its virginity forever. It no longer is an exact value. By definition real numbers are not exact values, even if they are. In other words, a real number has a component that is not quantized.  If the number is quantized, it could be a fixed-point number in either binary or an hybrid number system.

Anyway, he can resolve the issue by user either INT(), which truncates, or ROUND(), which rounds off numbers to a given number of decimal places.

Code: [Select]If i = 2 Then MsgBox "bla"Could be rewritten as:
Code: [Select]If int(i+0.1) = 2 Then MsgBox "bla"Good night. Quote from: Linux711 on March 18, 2012, 10:07:38 PM
If I run the following code:

Code: [Select]Dim i As Double

For i = 0 To 5 Step 0.001
    If i = 2 Then MsgBox "bla"
Next i

It never happens because i is never exactly 2 for some reason. It is something like 2.000000754268376. I can fix this using the following. . .

Code: [Select]Dim i As Double
Dim j As Double

For i = 0 To 5 Step 0.001
    j = Val(Format(i, "#.###"))
    If j = 2 Then MsgBox "bla"
Next i

My question is: Is there a better/more efficient way of doing this? I have a massive loop and I don't want it slowed down anymore.

Code: [Select]Dim i As Integer
Dim j As Double
for i = 0 to 5000
    j = CDbl(i) / 1000#
    if j = 2 Then MsgBox "Blah"
Next

P.S: it's never exactly 2 in the original because 0.001 cannot be accurately represented using floating point. This version attempts to avoid that problem by not incrementing using a INEXACT floating point number. Instead, each iteration increments using an integral value, and that value is used in a division. This is pretty much a scaled integer approach. The problem you were seeing was because of the accumulated imprecision from adding an inprecise floating point representation to itself.


Here's another version that might work, but it will probably be slower, because it uses the Decimal subtype of Variant. (It's not so much slow because of the Variant type but because all the Decimal operations are dealt with through software)

Code: [Select]Dim i As Variant
i=CDec(0)
for i = 0 to 5 step CDec(0.001)
     If i = 2 Then MsgBox "bla"
Next i




Quote
Once a number as been converted to a real floating point number, it loses its virginity forever. It no longer is an exact value.
As Salmon Trout just said, some numbers can be represented perfectly in floating point. Most whole numbers are simple to represent perfectly in floating point, as long as the number doesn't require a exponent to represent. Any number that fits in the mantissa will be a perfect representation of that value.

The issue here is not that a single floating point value was inaccurate; the Visual Basic runtime works with this- 0.1*2 will equal 0.2 even though the actual values are not equal, because VB intrinsically uses a tolerance value. The problem is in the use of floating point numbers that cannot be accurately represented as accumulators; 0.001, for example- because this compounds the ERROR to the point where the accumulated error becomes significant.

Running my older Expression Evaluator  (written in VB6) on the expression SEQ(X,X,1,5,0.01) (which creates a sequence of values from 1 to 5 in steps of 0.01) there is no visible error in the output until 1.6, which is output as 1.69999999998 (or something similar) That is the point at which the floating point error "escapes" the built in tolerances of the floating point library in use.

Of course, even so, comparing two floating point numbers for equality is typically not a good idea because you cannot be sure of the processes that generated those numbers. Usually, the test could merely take the difference and assert that it is smaller than a given tolerance:

Code: [Select]Const tolerance As Double-0.001

Public Function TestEqual(ByRef Value1 As Double,ByRef Value2 As Double) As Boolean
    TestEqual = (Abs(Value1-Value2) < tolerance)
End Function


Discussion

No Comment Found