[quoted text, click to view] >>>
http://www.pobox.com/~skeet/csharp/floatingpoint.html gives some of
>>> the details of floating point numbers, and some suggestions as to
>>> when to
>>> use what.
I address these points to Jon Skeet but, of course, any body may have some
worthy insights they wish to contribute.
[quoted text, click to view] > Questions are more than welcome - they'll suggest ways I could expand
> the article, for one thing :)
You mention "There are points to note about decimal, but this article doesn't go
into them ..." and perhaps my questioning might expand your article in that
direction? :)
Since having done a bit more reading I have decided to speak of "nonintegrals"
rather than "Generic Decimal Number"
So my definition is now:
A nonintegral: a any number with a fractional part represented with
digits. A nonintegral may be implemented with any of several
datatypes including the decimal datatype, a double, a single, a string. We can
have a nonintegral in base 10 or base 2.
You've given me an important epiphany:
1/10 or 0.1 cannot be represented, exactly, in Base 2.
So, to repeat your story in my own words:
There are many fractions that can be represented exactly in Base 10 but CANNOT
in Base 2, like 0.1. A Floating point datatype displays in Base 10 but stores
its information ultimately in Base 2. Therefore a floating point datatype
cannot, exactly, represent 0.1 (and many other fractions).
Which is why when we run (we are biased toward different languages perhaps):
' Need DoubleConverter from
http://www.yoda.arachsys.com/csharp/DoubleConverter.cs
Sub NonintegralEqualityTest()
Dim nonintegral As Double
Dim i As Integer
For i = 1 To 10
nonintegral += 0.1
Next i
Debug.WriteLine("nonintegral: " & nonintegral)
Debug.WriteLine("nonintegral Actual: " _
& DoubleConverter.ToExactString(nonintegral))
Debug.WriteLine("(nonintegral = 1): " & (nonintegral = 1))
End Sub
.... We get:
nonintegral: 1
nonintegral Actual: 0.99999999999999988897769753748434595763683319091796875
(nonintegral = 1): False
However if we Test working with a third in a slightly new procedure:
Sub NonintegralEqualityTestAThird()
Dim nonintegral As Double
Dim i As Integer
For i = 1 To 3
nonintegral += 0.1 / 0.3
Next i
Debug.WriteLine("nonintegral: " & nonintegral)
Debug.WriteLine("nonintegral Actual: " _
& DoubleConverter.ToExactString(nonintegral))
Debug.WriteLine("(nonintegral = 1): " & (nonintegral = 1))
End Sub
.... We get:
nonintegral: 1
nonintegral Actual: 1
(nonintegral = 1): True
Let's test working with a third but with a Decimal data type
Sub NonintegralEqualityTestAThird()
Dim nonintegral As Decimal
Dim i As Integer
For i = 1 To 3
' D is the literal type character for Decimal NOT double in VB.NET
nonintegral += 0.1D / 0.3D
Next i
Debug.WriteLine("nonintegral: " & nonintegral)
Debug.WriteLine("nonintegral Actual: " _
& DoubleConverter.ToExactString(nonintegral))
Debug.WriteLine("(nonintegral = 1): " & (nonintegral = 1))
End Sub
.... We Get:
nonintegral: 0.9999999999999999999999999999
nonintegral Actual: 1
(nonintegral = 1): False
These tests seem to imply that while 1/3 cannot be represented in Base 10 it can
be represented in Base 2. Can you confirm that 1/3 can be represented exactly in
Base 2? Is this it: 0.010101011?
Whether 1/3 can be represented exactly in Base 2 or not contradicts nothing
you've said and is probably unimportant as:
"Whatever base you come up with, you'll have the same problem with some
numbers - and in particular, "irrational" numbers (numbers which can't be
represented as fractions) like the mathematical constants pi and e are always
going to give trouble."
Therefore we can have this rule for when working with nonintegrals:
Never use the equal operator to test for the equality of nonintegrals (whether a
floating point or fixed point datatype, like Decimal). Instead use a custom
EqualEnough(x,y,tolerance) function (See Bellow).
Private Const mDefaultTolerance As Single = 0.000001
' Returns: Whether two floating point numbers are close enough to be
' deemed equal.
' Remarks: Never use the equality operator, =, to test for equality with
' floating point datatypes.
' Params:
' x, y
' Floating point numbers in any order.
' tolerance
' An amount that is sufficient to the numbers
' to differ by and still be considered equal . Eg 0.001
'
' Example:
' If EqualEnough(d,1) then
'
' Created: 31 Aug 2003
' John Bentley johnny_bentley@yahoo.com.au
' +61 (0)40 912 4414
Overloads Function EqualEnough(ByVal x As Double, ByVal y As Double, _
Optional ByVal tolerance As Double _
= CDbl(mDefaultTolerance)) As Boolean
Return (Math.Abs(x - y) <= tolerance)
End Function
Overloads Function EqualEnough(ByVal x As Single, ByVal y As Single, _
Optional ByVal tolerance As Single _
= mDefaultTolerance) As Boolean
Return (Math.Abs(x - y) <= tolerance)
End Function
Overloads Function EqualEnough(ByVal x As Decimal, ByVal y As Decimal, _
Optional ByVal tolerance As Single _
= CDec(mDefaultTolerance)) As Boolean
Return (Math.Abs(x - y) <= tolerance)
End Function
I'm still pursuing the question of How to determine which of the nonintegral
Datatypes to use: Floating point Datatypes versus Fixed Point (I know you say
that a decimal is really a floating point). Your suggestion, if I can represent
it oversimply, to use Floating point for Scientific apps and Fixed for Financial
apps, is a helpful one. However, I'm trying to grasp the issue a little more by
understanding the nature of a Decimal Datatype
What is this beast the Decimal Datatype? Is it that while System.Double and
System.Single are stored in Base 2, a System.Decimal is Stored, somehow, in Base
10? This would seem, to my present niave understanding, impossible as the CPU
ultimately works in machine code, that is, 0s and 1s.
For if we run
Sub NonintegralEqualityTest()
' Note this is now a Decimal rather than a Double
Dim nonintegral As Decimal
Dim i As Integer
For i = 1 To 10
' D is literal type character for Decimal in VB.NET
nonintegral += 0.1D
Next i
Debug.WriteLine("nonintegral: " & nonintegral)
Debug.WriteLine("nonintegral Actual: " _
& DoubleConverter.ToExactString(nonintegral))
Debug.WriteLine("(nonintegral = 1): " & (nonintegral = 1))
End Sub
We get:
nonintegral: 1
nonintegral Actual: 1
(nonintegral = 1): True
This shows, perhaps, an essential difference between a floating point datatype
and a fixed point datatype (can we stick with "float" V "fixed" point as a