Log function in excel (vba) - not giving me the right answer - excel

In this code snippet below, the value of temp_int2 is 1, makes no sense to me. --> log(1000) = 3, that is log of base 10, the log function is base 'e'.
im not sure if its the "INT" function which problematic but could someone please assist.
temp_int2 = Int(Log(1000) / Log(10)) - 1
'temp_int2 = Int(Log(cap_dec) / Log(10)) - 1
MsgBox ("Value of log functuon -->" & CStr(Log(cap_dec) / Log(10)) & " value after log function " & CStr(temp_int2))

Instead of the Int function use the cLng function. While Int will cut off decimals, cLng will round to a Long.
Example Int will cut off
Int(99.2) '= 99
Int(99.5) '= 99
Int(99.8) '= 99
but cLng will round
cLng(99.2) '= 99
cLng(99.5) '= 100
cLng(99.8) '= 100
since computers and calculators calculate numeric and not algebraic there is probably a precision issue in calculation and Log(1000) / Log(10) is not exactly an algebraic 3 but a numeric 3 that is something like 2.99999999999998 which Int will cut off to 2 but cLng will round to 3.
Note that Excel is a numeric calculation program and values of type Double are not exact values. The decimals are (as with any standard calculator too) only calculated up to a defined precision.
So a Double type 3 is not a 3 but something very close to 3 like 2.99999999999998. So the Log function returns a Double and also a devision Log(1000) / Log(10) returns a Double and this is not exactly 3 but very close to 3.
Note that this is not a bug but in the nature of numeric calculations which are never exact but only precise, while algebraic calculations can be exact.
The same problem occurs when comparing values of type Double:
If DoubleA = DoubleB Then 'might not work
Here you need to use something like
If (DoubleA - DoubleB) ^ 2 < (10^ - Digits)^2
where Digits is the number of digits that need to be the same. Example
DoubleA = 0.9999999999
DoubleB = 1.0000000001
then Digits needs to be <= 9 to consider them as equal.
If you need to do that often then it comes handy to use a function for that:
Option Explicit
Public Function IsDoubleValueTheSame(DoubleA As Double, DoubleB As Double, Optional Digits As Long = 12) As Boolean
IsDoubleValueTheSame = (DoubleA - DoubleB) ^ 2 < (10 ^ -Digits) ^ 2
End Function
and call it like
Debug.Print IsDoubleValueTheSame(0.9999999999, 1.0000000001, 9) 'true
Debug.Print IsDoubleValueTheSame(0.9999999999, 1.0000000001, 10) 'false
So to come back to your initial example:
Debug.Print Log(1000) / Log(10) = 3 'false
Debug.Print IsDoubleValueTheSame(Log(1000) / Log(10), 3, 15) 'true
Debug.Print IsDoubleValueTheSame(Log(1000) / Log(10), 3, 16) 'false
which means Log(1000) / Log(10) is actually 3 precise up to 15 digits and the 16ᵗʰ digit is different.
Further information about this:
Numeric precision in Microsoft_Excel (Wikipedia)
Floating-point arithmetic may give inaccurate results in Excel (Microsoft documentation)
Compare double in VBA precision problem (Stack Overflow)

Related

Mod calculation in VBA

why vba shows answer "0" when Google shows "11.05"?
Sub test2()
Debug.Print 22.15 Mod 11.1
End Sub
Q: Is it possible to get in VBA result the same as Google provide?
UPD2:VBA's Mod operator (not function) differs significantly from Excels MOD function is a few respects. First, it handles negative values differently... second, if you use 1 as the divisor, it will return 0, not the decimal portion of the floating point number... third, it handles floating point number differently (it uses Banker's Rounding to round all floating point number to whole numbers before performing it operation on those numbers whereas Excel doesn't).
As Mod in VBA only deals with integers, you'll have to scale your values, for example with 10 ^ 3 (1000) if you have values of three decimals or less:
DecimalCount = 3
Scaling = 10 ^ DecimalCount
Debug.Print (22.15 * Scaling Mod 11.1 * Scaling) / Scaling
11.05
Debug.Print (22.15 * Scaling Mod 11.075 * Scaling) / Scaling
0
Just adjust DecimalCount to match your expected values.
An alternative to Gustav's answer that doesn't need the scaling: First make an regular division, use the integer part of the result (integer quotient) and subtract the divisor times the (integer) quotient from the dividend.
Function ModDouble(a As Double, b As Double) As Double
Dim y As Double
y = (a / b)
ModDouble = a - (b * Int(y))
End Function
Testing it:
Sub test()
Debug.Print ModDouble(5, 3)
Debug.Print ModDouble(22.15, 11.1)
End Sub
> 2
> 11.05

How to use bit shift in VBA

I am trying to perform some bit-shifting operations to convert 0110 0100 0000 to 1100 1000 in VBA code. This example is a decimal value of 200.
I have tried the following code but it seems like the >> is not working. Also, how do I set the data type to zero to clear all bits?
If wordmsg is a 32-bit word, How can I set bit 30 and bit 31 equal to 1?
Sub test()
'Declaring variables
Dim simvalue As Integer, wordmsg As Integer, test As Integer, resolution As Integer
simvalue= 200
resolution = 0.0625
wordmsg = simvalue / resolution
test = wordmsg >> 3
End sub
There's no bit shift operator in VBA. You can, generally speaking, multiply, divide, AND and OR your way around.
Excel exposes the Bitand, Bitlshift, Bitor, Bitrshift and Bitxor functions to VBA through Application.WorksheetFunction.
Example: test = Application.WorksheetFunction.Bitrshift(wordmsg, 3).
To manipulate 32-bit words in VBA, you can't use the Long type, because its most significant bit (MSB, bit 31) holds the sign (negative when set). Attempting to set bit 31 with e.g. myLong = 2 ^ 31 will result in an overflow. You can use the Double type as a stand-in dword, and the Application.WorksheetFunction.Bit[...] functions will work with up to 48 bits (2^48 - 1; decimal 281474976710655).
Sub BitRightShiftTest()
Dim d As Double
d = (2 ^ 31) + (2 ^ 30) + (200 / 0.0625)
Debug.Print d
d = Application.WorksheetFunction.Bitrshift(d, 3)
Debug.Print d
End Sub
Try BITRSHIFT function. http://www.excelfunctions.net/excel-bitrshift-function.html. If not available, use multiplication instead.

VBA - random number between two values

I've seen multiple answers say the following algorithm works fine to generate a random number between two values. I'm getting spurious results where I'll sometimes get a value returned that is higher than the upper bound.
Dim random as Integer
random = Int (3 - 0 + 1) * Rnd + 0
Debug.Print random
This should give values between 0 - 3 right? I'm seeing 0 to 4 when run a few times..???
This is one of the idiosyncrasies of VBA.
You can see the effect more clearly by writing
random = Int(4) * Rnd
4 * Rnd is a floating point double type, and when it gets assigned to random, The same rounding convention as for CInt is applied1; i.e. if Int(4) * Rnd is 3.5 or greater, the result is 4.
The fix is to write
random = Int(4 * Rnd)
1 The convention "round half to even" is often called Banker's Rounding. See
https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
I think it is to be expected when explicitly converting floating point number to integer, but what might not be expected is that the rounding is towards the closest even number:
Dim i As Integer
i = 3.5 ' i = 4
i = "2.5" ' i = 2

Decimal to binary conversion for large numbers in Excel

I have some large numbers in an Excel sheet and I want to convert them to binary.
e.g.
12345678
965321458
-12457896
If we are talking positive number between 0 and 2^32-1 you can use this formula:
=DEC2BIN(MOD(QUOTIENT($A$1,256^3),256),8)&DEC2BIN(MOD(QUOTIENT($A$1,256^2),256),8)&DEC2BIN(MOD(QUOTIENT($A$1,256^1),256),8)&DEC2BIN(MOD(QUOTIENT($A$1,256^0),256),8)
NOTE: =DEC2BIN() function cannot handle numbers larger than 511 so as you see my formula breaks your number into four 8-bit chunks, converts them to binary format and then concatenates the results.
Well, theoretically you can extend this formula up to six 8-bit chunks. Maximum precision you can get in Excel is 15 (fifteen) decimal digits. When exceeded, only the most significant 15 digits remain, the rest is rounded. I.e. if you type 12345678901234567 Excel will store it as 12345678901234500. So since 2^48-1 is 15 decimal digits long the number won't get rounded.
Perhaps a simpler option:
For positive numbers only, just use BASE (as in BASE2) for numbers between 0 to 2^53 in Excel. Here are some examples:
=BASE(3,2) # returns 11
=BASE(11,2) # returns 1011
Credit for answer goes here:
https://ask.libreoffice.org/en/question/69797/why-is-dec2bin-limited-to-82bits-in-an-32-and-64-bits-world/
Negative numbers: Come to think of it, negative numbers could be handled as well by building upon howy61's answer. He shifts everything by a power of two (2^31 in his case) to use the 2's complement:
=BASE(2^31+MyNum, 2)
so (using 2^8 for only 8 bits):
=BASE(2^8+(-1),2) # returns 11111111
=BASE(2^8+(-3),2) # returns 11111101
The numbers given by the OP requires more bits, so I'll use 2^31 (could go up to 2^53):
=BASE(2^31+(-12457896),2) # returns 11111111010000011110100001011000
For either positive or negative, both formulas could be coupled in a single IF formula. Here are two ways you could do it that give the same answer, where MyNum is the decimal number you start with:
=IF(MyNum<0, BASE(2^31+MyNum,2), BASE(MyNum, 2))
or
=BASE(IF(MyNum<0, MyNum+2^32, MyNum), 2)
See VBA posted here
' The DecimalIn argument is limited to 79228162514264337593543950245
' (approximately 96-bits) - large numerical values must be entered
' as a String value to prevent conversion to scientific notation. Then
' optional NumberOfBits allows you to zero-fill the front of smaller
' values in order to return values up to a desired bit level.
Function DecToBin(ByVal DecimalIn As Variant, Optional NumberOfBits As Variant) As String
DecToBin = ""
DecimalIn = CDec(DecimalIn)
Do While DecimalIn <> 0
DecToBin = Trim$(Str$(DecimalIn - 2 * Int(DecimalIn / 2))) & DecToBin
DecimalIn = Int(DecimalIn / 2)
Loop
If Not IsMissing(NumberOfBits) Then
If Len(DecToBin) > NumberOfBits Then
DecToBin = "Error - Number too large for bit size"
Else
DecToBin = Right$(String$(NumberOfBits, "0") & _
DecToBin, NumberOfBits)
End If
End If
End Function
I just tried the formula above, and found that Microsoft screwed up the DEC2BIN function in another way that keeps the formula from working correctly with negative numbers. Internally, DEC2BIN uses a ten bit result; leading zeroes are dropped from the text result, unless the optional length parameter is used, in which case the required number of leading zeroes are left in the string. But here's the rub: a negative number always starts with a one, so there are no leading zeroes to drop, so DEC2BIN will always show all ten bits! Thus, DEC2BIN(-1,8), which should show 11111111 (eight ones) will instead show 1111111111 (ten ones.)
To fix this, use RIGHT to trim each eight bit chunk to eight bits, dumb as that sounds.
=RIGHT(DEC2BIN(QUOTIENT(A1,256^3),8),8) & RIGHT(...
(I read through the VBA, and it does not have the same problem, but it doesn't look like it will handle negatives at all.)
To add easier to read formatting to Taosique's great answer, you can also break it up into chunks of 4 bits with spaces in between, although the formula grows to be a monster:
=DEC2BIN(MOD(QUOTIENT($A$1,16^7),16),4)&" "&DEC2BIN(MOD(QUOTIENT($A$1,16^6),16),4)&" "&DEC2BIN(MOD(QUOTIENT($A$1,16^5),16),4)&" "&DEC2BIN(MOD(QUOTIENT($A$1,16^4),16),4)&" "&DEC2BIN(MOD(QUOTIENT($A$1,16^3),16),4)&" "&DEC2BIN(MOD(QUOTIENT($A$1,16^2),16),4)&" "&DEC2BIN(MOD(QUOTIENT($A$1,16^1),16),4)&" "&DEC2BIN(MOD(QUOTIENT($A$1,16^0),16),4)
1101 0100 1111 0110 0011 0001 0000 0001
Of course, you can just use the right half of it, if you're just interested in 16 bit numbers:
=DEC2BIN(MOD(QUOTIENT($A$1,16^3),16),4)&" "&DEC2BIN(MOD(QUOTIENT($A$1,16^2),16),4)&" "&DEC2BIN(MOD(QUOTIENT($A$1,16^1),16),4)&" "&DEC2BIN(MOD(QUOTIENT($A$1,16^0),16),4)
0011 0001 0000 0001
While I didn't write this for negatives or decimals, it should be relatively easy to modify. This VBA will convert any super large (or not so large if you want, but that wasn't the point) decimal up to the converted binary result containing up to 32767 digits (maximum string length in VBA).
Enter decimal in cell "A1" as a string, result will be in "B1" as a string.
Dim NBN As String
Dim Bin As String
5 Big = Range("A1")
AA = Len(Big)
For XX = 1 To AA
L1 = Mid(Big, XX, 1) + CRY
CRY = 0
If L1 = 0 Then
FN = "0"
GoTo 10
End If
If Int(L1 / 2) = L1 / 2 Then
FN = L1 / 2
GoTo 10
End If
If Int(L1 / 2) <> L1 / 2 Then
FN = Int(L1 / 2)
CRY = 10
GoTo 10
End If
10 NBN = NBN & FN
Next XX
If Left(NBN, 1) = "0" Then
NBN = Right(NBN, (Len(NBN) - 1))
End If
If CRY = 10 Then Bin = "1" & Bin Else Bin = "0" & Bin
Range("A1") = NBN
Range("A2") = Bin
If Len(NBN) > 0 Then
NBN = ""
CRY = 0
GoTo 5
End If
Someone can find binary shift operations more clear and relevant here
=DEC2BIN(BITRSHIFT($A$1,24),8) & DEC2BIN(MOD(BITRSHIFT($A$1,16),256),8) & DEC2BIN(MOD(BITRSHIFT($A$1,8),256),8) & DEC2BIN(MOD($A$1,256),8)
This formula is for 32-bit values
This vba function solves the problem of binary conversion of numbers greater than 511 that can not be done with WorksheetFunction.dec2bin.
The code takes advantage of the WorksheetFunction.dec2bin function by applying it in pieces.
Function decimal2binary(ByVal decimal2convert As Long) As String
Dim rest As Long
If decimal2convert = 0 Then
decimal2binary = "0"
Exit Function
End If
Do While decimal2convert > 0
rest = decimal2convert Mod 512
decimal2binary = Right("000000000" + WorksheetFunction.Dec2Bin(rest), 9) + decimal2binary
decimal2convert = (decimal2convert - rest) / 512
Loop
decimal2binary = Abs(decimal2binary)
End Function
=IF(Decimal>-1,BASE(Decimal,2,32),BASE(2^32+(Decimal),2))
Does both positive and negative numbers.
Took a bit LOL. Tech pun.
You're welcome.
Here's another way. It's not with a single formula, but I have tried and converted up to the number 2,099,999,999,999. My first intention was to build a 51 bit counter, but somehow it does not work with numbers beyond the one I mentioned. Download from
http://www.excelexperto.com/content/macros-production/contador-binario-de-51-bits/
I hope it's useful. Regards.
Without VBA and working with negative numbers as well (here: sint16), however, taking much more space:
You can download the excel file here: (sorry, didn't know where to put the file)
int16 bits to decimal.xlsx
or alternatively follow these steps (if your Excel is not in English, use Excel Translator to "translate" the formula into your MS Office language):
Enter the binary number in 4-bit nibbles (A4 = most significant to D4 = least significant) like shown in the screenshot. Enter all 4 digits (even if starting with 0) and format them as "text"
Enter formula in F4:
=IF(NUMBERVALUE(A4)>=1000,TRUE,FALSE)
Enter the letter "A" in G2-J2, "B" in K2-N2, "C" in O2-R2, "D" in S2-V2
Enter "1" in G3, K3, O3 and S3; "2" in H3, L3, P3 and T3; "3" in I3, M3, Q3 and U3; "4" in J3, N3, R3 and V3
In G4, enter:
=MID(INDIRECT(G$2&ROW()),G$3,1)
Copy the formula to H4-V4
In X4, enter:
=IF(G4="1",0,1)
Copy X4 to Y4-AM4
In BD3 enter "1"
In BC4, enter:
=IF((AM$4+BD3)=2,1,0)
IN BD4, enter:
=IF((AM$4+BD3)=2,0,IF((AM$4+BD3)=1,1,0))
Copy BD4 and BD4 and insert it 15 times diagonally one row further down and one column further left (like in the screenshot), i.e. insert it to BB5 and BC5, then BA6 and BB6, ..., AN19 and AO19.
In AO20, enter "=AO19"; in AP20, enter "=AP18" and so on until BD20 ("=BD4") - i.e. bring down the numbers into one line as seen in the screenshot
In BE20, enter (this is your result):
=IF(F4=FALSE,BIN2DEC(A4&B4)*2^8+BIN2DEC(C4&D4),-1*(BIN2DEC(AO20&AP20&AQ20&AR20&AS20&AT20&AU20&AV20)*2^8+BIN2DEC(AW20&AX20&AY20&AZ20&BA20&BB20&BC20&BD20)))
There maybe a simple solution. I have several 4.2 billion cells that are actually a negative Two's Complement and this works to get the correct value:
=SUM(2^31-(A1-2^31))

Rounding error when using INT function

I have user input in two cells, named "UpperRangeHigh" and "UpperRangeLow". I have the following code:
dRangeUpper = [UpperRangeHigh] - [UpperRangeLow]
lLines = Int(dRangeUpper * 100 / lInterval)
The user inputs 120.3 and 120 into the input cells respectively. lInterval has the value 10. VBA produces the result of 2 for lLines, instead of 3.
I can overcome this problem by adding 0.000000001 to dRangeUpper, but I'm wondering if there is a known reason for this behaviour?
This appears to be a problem with Excel's calculation and significant digits. If you do:
=120.3 - 120 and format the cell to display 15 decimal places, the result appears as:
0.2999999999999970
Here is a brief overview which explains how Excel uses binary arithmetic and that this may result in results divergent from what you would expect:
http://excel.tips.net/T008143_Avoiding_Rounding_Errors_in_Formula_Results.html
You can overcome this by forcing a rounded precision, e.g., to 10 decimal places:
lLines = Int(Round(dRangeUpper, 10) * 100 / lInterval
Kindly use single or double when working with decimals to get more accurate results.
Sub sample()
Dim dRangeUpper As Double
dRangeUpper = CDbl("120.3") - CDbl("120")
lLines = Round(CDbl(dRangeUpper * 100 / 10), 4)
End Sub
output = 3
This is a known Floating point issue within Excel
http://support.microsoft.com/kb/78113
From MSDN:
To minimize any effects of floating point arithmetic storage
inaccuracy, use the Round() function to round numbers to the number of
decimal places that is required by your calculation. For example, if
you are working with currency, you would likely round to 2 decimal
places:
=ROUND(1*(0.5-0.4-0.1),2)
In your case, using round() instead of INT should do the trick using 0 rather than 2

Resources