Best way to deal with Fractions avoiding VARIANT Type
Just thinking of a way to optimise my code.
I'm currently dealing with lot's of fractions that need to be up to 20 decimal points!!!
I've managed to handle this with VB's VARIANT dataType but that has a huge hit on speed once I run a Loop with many calculations.
What alternatives would there be do accurately deal with LONGLONG/CDec64 in VB + Fractions/Decimal Points.
Re: Best way to deal with Fractions avoiding VARIANT Type
Are the values indeed with an accuracy of 20 numbers?
So like 0.123456789012334567890 or even numbers like 9876543210.123456789012334567890 ?
Re: Best way to deal with Fractions avoiding VARIANT Type
More like: 0.123456789012334567890
Re: Best way to deal with Fractions avoiding VARIANT Type
I thinks it's going to be hard to beat the variant/decimal calculation with self written floating point algoritmes.
The difference between Double vs Variant calculations:
Code:
Compiled, no optimizations:
Variant: 4897ms
Double: 1641ms
Compiled, all possible optimizations:
Variant: 4756ms
Double: 610ms
So without optimizations the difference is a factor 2.9, with optimizations a factor 7.8
Code:
Option Explicit
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Sub Command1_Click()
Dim l1 As Long, l2 As Long
l1 = pCalcVariant
l2 = pCalcDouble
MsgBox "Variant: " & CStr(l1) & "ms" & vbLf & "Double: " & CStr(l2) & "ms"
End Sub
Private Function pCalcVariant() As Long
Dim v As Variant, v1 As Variant
Dim i As Long, j As Long
Dim t As Long
Dim tLoop As Long
t = GetTickCount
For i = 0 To 9999999
Next i
tLoop = 10 * (GetTickCount - t)
v1 = CDec("1,123456789012334567890")
t = GetTickCount
For j = 1 To 10
v = v1
For i = 0 To 9999999
v = v * v
v = v / 2
Next i
Next j
pCalcVariant = GetTickCount - t - tLoop
End Function
Private Function pCalcDouble() As Long
Dim v As Double, v1 As Double
Dim i As Long, j As Long
Dim t As Long
Dim tLoop As Long
t = GetTickCount
For i = 0 To 9999999
Next i
tLoop = 10 * (GetTickCount - t)
v1 = CDbl("1,123456789012334567890")
t = GetTickCount
For j = 1 To 10
v = v1
For i = 0 To 9999999
v = v * v
v = v / 2
Next i
Next j
pCalcDouble = GetTickCount - t - tLoop
End Function
Re: Best way to deal with Fractions avoiding VARIANT Type
Ok, here's my 2-cents.
First, it's not my experience that the use of Variants is any "huge hit" on performance. However, I think we need to be more specific. If we're using Variants to hold a Decimal type, then you might get a substantial hit on performance, particularly if you're doing lots of math with these Decimals. Here's the structure of a Decimal:
Code:
Private Type DecimalStructure ' (when sitting in a Variant)
'
' Largest Decimal: +/- 79228162514264337593543950335. 2^96-1 (sign bit handled separately)
' Smallest Decimal: +/- 0.0000000000000000000000000001 Notice that both largest and smallest are same width.
'
VariantType As Integer ' Reserved, to act as the Variant Type when sitting in a 16-Byte-Variant. Equals vbDecimal(14) when it's a Decimal type.
Base10NegExp As Byte ' Base 10 exponent (0 to 28), moving decimal to right (smaller numbers) as this value goes higher. Top three bits are never used.
sign As Byte ' Sign bit only. Other bits aren't used.
Hi32 As Long ' Mantissa.
Lo32 As Long ' Mantissa.
Mid32 As Long ' Mantissa.
End Type
Now, one thing to notice is that it's got a Mixed-Endian byte order. That's just horrible for doing math, and it also tells us that there's software involved in getting any math done.
Now, another option is to use the Currency type, and possibly have an implicit understanding with yourself that you're going to treat them as if they've got more than the built-in four decimal points. It's a bit of a stretch to get 20 (base-10) decimal points with a Currency. But, we could get, say, 14 somewhat easily. We'd just multiply our input numbers by 10000000000 going in, and divide out output numbers by 10000000000 when they come back out.
Currency ranges from -922,337,203,685,477.5808 to 922,337,203,685,477.5807. So, with that suggestion, your numbers could range from -92233.72036854775808 to 92233.72036854775807. If the integer portion of your numbers was always small, you could stretch those decimal points to 18 using Currency.
Also, even if you did go the Currency route, you may have to use Variant_Decimal going in and out, so you don't lose precision in that process. However, the Currency math would be quite fast as it'd mostly be done in hardware registers (particularly + - * /).
Beyond those suggestions, you're probably going to need some library that can do IEEE-quad-precision for you.
Good Luck,
Elroy
EDIT1: Out of curiousity, I took at look at the current state of the FPU unit in modern CPUs. Apparently the Quad Precision hasn't really caught on that much, and isn't an option in most CPUs. This means that IEEE Quad math is going to be done in software, which will be a substantial slowdown. There are some add-on quad FPU hardware devices, but you'd have to be sure and utilize the software library that came with those to utilize them. If your software is to be distributed, I doubt that's a good option. So, the quad precision doesn't really look like a great option. You may do better to just stick with Variant_Decimals.
Re: Best way to deal with Fractions avoiding VARIANT Type
Regarding the use of the LongLong data type, here's a thread that discusses that. However, if you're thinking about it for floating-point fractions, there's hardly any advantage to just using Currency. Currency and LongLong are basically the same thing, with the only difference is that Currency has a decimal point "plunked" into it at the fourth decimal when it's reported. If you're going to multiply/divide by a factor when going in/out, why not just appreciate the four decimal points in the factor (as I did above).