When doing math that may have problems, I've traditionally resorted to Variants and returned a Null or Empty when things didn't go correctly. However, that's never felt totally clean. Lately, I've been relying on the NaN of an IEEE Double (and forgoing any use of Variants).
Basically, to summarize, I can think of five different "states" an IEEE Double may be in:
And, there's also the sign-bit. However, the way IEEE Doubles are specified, the sign-bit is independent of all five of those "states". In other words, we can have -NaN or +NaN, -Inf, or +Inf. We can even have -0 or +0.
Also, just to quickly define them, the sub-normal numbers are numbers very close to zero. With the typical 11-bit exponent, this exponent can range from approximately 10+308 to 10-308. However, with a bit of trickery (i.e., using the mantissa as more exponent, and sacrificing mantissa precision), we can push on the negative exponent side, making it go to approximately 10-324 (the sub-normals). These sub-normal numbers are always very close to zero. I don't do anything special with these sub-normal numbers herein, but I just wanted to be complete.
Also, I list "Zero" separately from "A typical floating point number". This is because Zero is not handled (i.e., binary coded) the same way as other numbers. Zero just has all the bits off (with the possible exception of the sign bit).
Now, NaN is a special value that means "not-a-number". It's what you get when you try to divide 0#/0# (with error trapping turned on so you don't crash). There are also other ways to get it.
Infinity (or just Inf) is another one of these special values. You can get it by dividing any non-zero number by zero, such as 1#/0# (again, with error trapping).
There's a good Wikipedia page about these IEEE Doubles (which is just a Double type in VB6).
It's mostly these NaN and Inf values about which I post this entry. I've begun using them (instead of Variant) to handle special situations, and I thought I'd share. Also, the way I did things, there's no need for error trapping, which should keep things very fast.
Here's the code (possibly best in a BAS module):
Just as an example of one place you may use these ... let's say you want to average a set of numbers. However, there may be cases where there are no numbers to average. What do you return? It's a problem, but returning a NaN can solve it so long as we remember to test for NaN before using it.
The following isn't complete code, but it's an example of where I'm using it. The caller then uses the IsNaN() function:
Also, I suppose I could have also done all this for IEEE Singles, but I don't currently have the need.
Enjoy,
Elroy
Basically, to summarize, I can think of five different "states" an IEEE Double may be in:
- Zero
- A typical floating point number.
- A sub-normal floating point number.
- A NaN
- Infinity
And, there's also the sign-bit. However, the way IEEE Doubles are specified, the sign-bit is independent of all five of those "states". In other words, we can have -NaN or +NaN, -Inf, or +Inf. We can even have -0 or +0.
Also, just to quickly define them, the sub-normal numbers are numbers very close to zero. With the typical 11-bit exponent, this exponent can range from approximately 10+308 to 10-308. However, with a bit of trickery (i.e., using the mantissa as more exponent, and sacrificing mantissa precision), we can push on the negative exponent side, making it go to approximately 10-324 (the sub-normals). These sub-normal numbers are always very close to zero. I don't do anything special with these sub-normal numbers herein, but I just wanted to be complete.
Also, I list "Zero" separately from "A typical floating point number". This is because Zero is not handled (i.e., binary coded) the same way as other numbers. Zero just has all the bits off (with the possible exception of the sign bit).
Now, NaN is a special value that means "not-a-number". It's what you get when you try to divide 0#/0# (with error trapping turned on so you don't crash). There are also other ways to get it.
Infinity (or just Inf) is another one of these special values. You can get it by dividing any non-zero number by zero, such as 1#/0# (again, with error trapping).
There's a good Wikipedia page about these IEEE Doubles (which is just a Double type in VB6).
It's mostly these NaN and Inf values about which I post this entry. I've begun using them (instead of Variant) to handle special situations, and I thought I'd share. Also, the way I did things, there's no need for error trapping, which should keep things very fast.
Here's the code (possibly best in a BAS module):
Code:
Option Explicit
'
Public Declare Function GetMem2 Lib "msvbvm60.dll" (ByRef Source As Any, ByRef Dest As Any) As Long ' Always ignore the returned value, it's useless.
Public Declare Function GetMem4 Lib "msvbvm60.dll" (ByRef Source As Any, ByRef Dest As Any) As Long ' Always ignore the returned value, it's useless.
Public Declare Function GetMem8 Lib "msvbvm60.dll" (ByRef Source As Any, ByRef Dest As Any) As Long ' Always ignore the returned value, it's useless.
'
Public Function NaN() As Double
' Math (add, subtract, multiply, divide) can be done on these, but nothing changes.
' They can NOT be used in "if NaN = NaN Then", or an overflow will result. Use IsNaN().
' Also, most math-with-functions (Sin(), Round(), etc) causes overflow error.
'
GetMem2 &HFFF8, ByVal PtrAdd(VarPtr(NaN), 6&)
End Function
Public Function Inf() As Double
GetMem2 &HFFF0, ByVal PtrAdd(VarPtr(Inf), 6&)
End Function
Public Function IsNaN(d As Double) As Boolean
IsNaN = IsNanOrInf(d) And Not IsInf(d)
End Function
Public Function IsInf(d As Double) As Boolean
Const ii As Integer = &H7FF0 ' High 4 bits of byte #7 (F0), Low 7 bits of byte #8 (7F). If all on, it's NaN (or Inf if all other non-sign bits are zero).
Static i(1 To 4) As Integer
GetMem8 d, i(1)
IsInf = (i(4) And ii) = ii And i(1) = &H0 And i(2) = &H0 And i(3) = &H0 And (i(4) And &HF) = &H0
End Function
Public Function IsNeg(d As Double) As Boolean
' This works even on NaN and Inf.
Static i(1 To 4) As Integer
GetMem8 d, i(1)
IsNeg = i(4) < 0 ' The sign bit will be the same sign bit for i(4).
End Function
Public Function IsNanOrInf(d As Double) As Boolean
Const ii As Integer = &H7FF0 ' High 4 bits of byte #7 (F0), Low 7 bits of byte #8 (7F). If all on, it's NaN (or Inf if all other non-sign bits are zero).
Static i(1 To 4) As Integer
GetMem8 d, i(1)
IsNanOrInf = (i(4) And ii) = ii
End Function
Public Function PtrAdd(ByVal Pointer As Long, ByVal Offset As Long) As Long
' For adding (or subtracting) a small number from a pointer.
' Use PtrAddEx for adding (or subtracting) large numbers from a pointer.
Const SIGN_BIT As Long = &H80000000
PtrAdd = (Pointer Xor SIGN_BIT) + Offset Xor SIGN_BIT
End Function
The following isn't complete code, but it's an example of where I'm using it. The caller then uses the IsNaN() function:
Code:
Private Function ParamSideAvg(iRow As Long, sSideLetter As String) As Double
' Returns NaN if nothing to average.
Dim n As Double
Dim iCnt As Long
Dim iCol As Long
'
Select Case sSideLetter
Case "L": iCol = ColNumberFromLetter("H") ' This is the MEAN column. Subtractions are made to get cycle data.
Case "R": iCol = ColNumberFromLetter("N") ' This is the MEAN column. Subtractions are made to get cycle data.
Case Else: Exit Function
End Select
'
If Len(Trim$(wsh.Cells(iRow, iCol - 3))) > 0 Then n = n + val(wsh.Cells(iRow, iCol - 3)): iCnt = iCnt + 1
If Len(Trim$(wsh.Cells(iRow, iCol - 2))) > 0 Then n = n + val(wsh.Cells(iRow, iCol - 2)): iCnt = iCnt + 1
If Len(Trim$(wsh.Cells(iRow, iCol - 1))) > 0 Then n = n + val(wsh.Cells(iRow, iCol - 1)): iCnt = iCnt + 1
If iCnt > 0 Then
ParamSideAvg = n / iCnt
Else
ParamSideAvg = NaN
End If
End Function
Enjoy,
Elroy