Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations TouchToneTommy on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Number of digits after decimal point 2

Status
Not open for further replies.

Andrzejek

Programmer
Jan 10, 2006
8,502
US
Hi Gang,

Google search gave me nothing, so here it goes:

Is there a better way to find out how many digits there are after decimal point in the given number?

123 => 0
12.56 => 2
3.4 => 1 etc.

Right now I have a little Function that accepts a number and returns another number (of digits after decimal point + 1, this 1 is the decimal point itself)

Private Function MoveNumberBy(sngValue As Single) As Double

If InStr(1, CStr(sngValue), ".") > 0 Then
MoveNumberBy = (Len(CStr(sngValue)) - InStr(1, CStr(sngValue), ".")) + 1

End If

End Function

I hope there is a better way than dealing with converting the number to string and messing with it like that.

---- Andy
 
Label1.Caption = CInt(Len(Str(Text1.Text)) - InStr(1, Text1.Text, ".")) - 1


Casper

There is room for all of gods creatures, "Right Beside the Mashed Potatoes".
 
See the following links regarding the limits on floating point math.


Also you are converting sngValue to a string 2 times try this
Private Function MoveNumberBy(strValue As String) As integer
MoveNumberBy = (Len(strValue) - InStr(strValue, "."))
End Function



Two strings walk into a bar. The first string says to the bartender: 'Bartender, I'll have a beer. u.5n$x5t?*&4ru!2[sACC~ErJ'. The second string says: 'Pardon my friend, he isn't NULL terminated'.
 
Thanks, but I was hoping for something simpler.
I guess I just have to leave it as it is.

---- Andy
 
Here's an alternative that I've just come up with that doesn't use strings (but I'm not going to explain it just for now; I leave that as an exercise for the reader ...)
Code:
[blue]Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Public Function GetDecimalPlaces(ByVal dblTest As Double) As Long
    CopyMemory GetDecimalPlaces, ByVal VarPtr(CDec(dblTest)) + 2, 1
End Function[/blue]
 
Looks to me like the third byte of a decimal value is some sort of indicator. ??
 
strongm, this is very interesting. Unfortunately, I haven't found sufficient data to give an accurate answer to your exercise.

What I'm willing to surmise from what I've been able to find is:
1. Using GetDecimalPlaces as the destination argument in the CopyMemory function plugs the source argument directly into the return value for the function, rather than creating a recursive procedure call.
2. Using the Decimal type leverages the fact that the exponent is contained somewhere in the bytes that define it.

That's as far as I've been able to get. First off, I'm not sure if I understand how VarPtr works, and haven't found a clear explanation of it. Does ByRef VarPtr(SomeVar) return as a long integer the memory location of the first byte of the string of bytes in which SomeVar is stored, whereas ByVal VarPtr(SomeVar) return the actual value contained therein? If not, can you correct this or point me to some reading that explains exactly how it works?

Next, there seems to be a good deal of confusion as to how many bytes define a decimal type. Let's see...
gives 14 bytes. (A VB6 reference.)
gives 12 bytes. (A VBA reference.)
On Francesco Balena gives 12 bytes. He also does so in his book. (A .Net reference.)
gives 16 bytes. (A .Net reference.)

Irritating. And none of it explains clearly which bytes hold what stuff.

Now, your code works up to 12 decimal places. Any more, it still returns 12. Funny, because you're pulling 1 byte, so one would think that it could deal with 16. If you were pulling the byte that has the sign bit in it, one would think you'd only get 8. Perhaps you can give me a hint on that one too.

Finally, I'm unable to find enough on CopyMemory's mechanism for copying data to come up with an explanation of the proc call. It looks like you're returning the third byte in the source block, as bjd4c suggests, but as far as I can see, the third byte is part of the mantissa in a decimal type. So, either I don't understand the way a decimal type's memory block is organized, or I don't understand how CopyMemory works, or both.

If you wouldn't mind explaining, sharing proper resource doc, or dropping a few hints, I'll give it another go.

Thanks,

Bob
 
>your code works up to 12 decimal places

Works up to 28, actually. However, it is tricky to actually pass it a numeric with that many decimal places because of the limitations of VB numeric types; the best you can do with a double, which this version uses, is about 16. (change the passed parameter to a string - or a variant - instead of a double, and you should be able to pass something with more decimal places)

>14 ... 12 ... 16
> either I don't understand the way a decimal type's memory block

Okey dokey ... The basic VB documentation suggest that a decimal is held as a 12-byte signed integer - which is why some people will claim that a decimal is 12 bytes. What is missed is the fact that the documentation also says that this is also scaled to a power of 10 to give the decimal places. This scaling factor needs to be stored somewhere, so that's another byte (which takes us to 13). And the documentation is misleading when it talks about a signed integer as, in fact, the sign bit is held in its own byte. So altogether that gives us 14 bytes

However, there is no such thing in VB as a native decimal type; it can only be held in a variant - and a variant takes 16 bytes to hold a numeric type.

So here is how a decimal is held in a variant

Bytes 0 and 1 : variant type info (vbVarType)
byte 2 thru 16: decimal, where
byte 2: Scaling factor (number of decimal places)
byte 3: sign byte (0 or 128)
bytes 4 thru 7: Integer high word
bytes 8 thru 15: Integer low dword

So what my code is actually doing is looking at the first byte of the decimal(which is the third byte of the variant that the decimal is actually stored in)
 
So why is it limitted to 28 instead of 255?

Is it because the 12 byte integer can only hold a 28 digit number?
 
Well, it can hold a 29 digit number (79228162514264337593543950335) which it can scale by a maximum of 28 places
 
>varptr

Sorry, Bob, missed that bit in your post. Here's Microsoft's explantion:
As for CopyMemory, there's a fair amount of documentation around for that. The main thing to point out is that in basic terms if you pass a VB variable to the API ByRef, that is the equivalent of passing the VarPtr of that variable ByVal (there are some complictions when considering arrays and strings) ...
 
<if you pass a VB variable to the API ByRef, that is the equivalent of passing the VarPtr of that variable ByVal
Oh, of course. That's what I was missing there. Thanks.

Now. The construction of the decimal type makes perfect sense when you explain it, and I can see that most of the doc is right in its own way. (I'm suspicious of the assertion that the .Net decimal really has 16 bytes, though, given that it doesn't derive from a variant.) But where on earth do you FIND these arcana? Is there anywhere, for example, that explains that the decimal variable takes an entire byte for the sign bit? And where is it said that the scaling factor is the first byte after the two that the variant uses for type info, rather than the last?

So, did you read this somewhere, or did you write some code to deconstruct the type?

Thanks for your explanation, and have another star.

Bob
 
I am curious as to what that code looks like. Care to share?
 
It isn't that exciting ... a form with a command button and a textbox
Code:
[blue]Option Explicit

Private Type memblock
    mybytes(1 To 16) As Byte
End Type

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Public Sub GetDec(ByVal dblTest As Variant)
    Dim test As memblock

    Dim jim As Variant
    'jim = fred
    jim = CDec(dblTest) ' ensure we have a decimal

    CopyMemory test.mybytes(1), jim, 16
'    test.mybytes(5) = 0
'    test.mybytes(6) = 0
'    test.mybytes(7) = 0
'    test.mybytes(8) = 0
'    test.mybytes(9) = 0
'    test.mybytes(10) = 0
'    test.mybytes(11) = 0
'    test.mybytes(12) = 0
'    test.mybytes(13) = 0
'    test.mybytes(14) = 0
'    test.mybytes(15) = 0
'    test.mybytes(16) = 0
    
'    CopyMemory jim, test.mybytes(1), 16
'    jim = jim * 256
'    CopyMemory test.mybytes(1), jim, 16
    Beep ' Put a break here and examine Locals
End Sub

Private Sub Command1_Click()
    GetDec Text1.Text
End Sub[/blue]
 
What really blows my buffer is how you even thought to look at this in the first place!

I think I would have just read the docs and thought, "Oh, 12 bytes, great!"

But you had to see through that and figure the extra byte for the sign and another for the scaling to get to 14... and from there decide "well, no, that can't be true... it must be in a 16 byte variant..."

Its like Detective Goren on Law & Order Criminal Intent.
 
Is there a particular reason why you used a type instead of just using the byte array itself? I tested it both ways and it seems to give the same results.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top