I've been using a simple little script for rounding numbers to the hundredths decimal place, however I've noticed that when the number ends in zero, it rounds up to the next decimal place. I need to preserve this decimal place.
For example: 7.49908302 would be rounded to 7.5 instead of 7.50.
How can I keep the hundredths decimal with this subroutine? Should I try something Perl or Objective C, as I've been told Applescript isn't the best for this sort of thing.
Here's the call:
set finalAge514years to (roundThis(age514years, 2))
Here's my rounding subroutine:
on roundThis(n, numDecimals)
set x to 10 ^ numDecimals
(((n * x) + 0.5) div 1) / x
end roundThis
The numbers 7.5 and 7.50 are exactly the same, so Applescript truncates any unnecessary information when displaying the number. What you're really looking for is how to format that information when it is displayed. For that, you need to turn the number into a string, specifying the amount of decimal places you want to see during the conversion.
This method of formatting a number is actually considered one of the Essential sub-routines.
round_truncate(7.49908302, 2)
--> "7.50"
on round_truncate(this_number, decimal_places)
if decimal_places is 0 then
set this_number to this_number + 0.5
return number_to_text(this_number div 1)
end if
set the rounding_value to "5"
repeat decimal_places times
set the rounding_value to "0" & the rounding_value
end repeat
set the rounding_value to ("." & the rounding_value) as number
set this_number to this_number + rounding_value
set the mod_value to "1"
repeat decimal_places - 1 times
set the mod_value to "0" & the mod_value
end repeat
set the mod_value to ("." & the mod_value) as number
set second_part to (this_number mod 1) div the mod_value
if the length of (the second_part as text) is less than the decimal_places then
repeat decimal_places - (the length of (the second_part as text)) times
set second_part to ("0" & second_part) as string
end repeat
end if
set first_part to this_number div 1
set first_part to number_to_string(first_part)
set this_number to (first_part & "." & second_part)
return this_number
end round_truncate
on number_to_string(this_number)
set this_number to this_number as string
if this_number contains "E+" then
set x to the offset of "." in this_number
set y to the offset of "+" in this_number
set z to the offset of "E" in this_number
set the decimal_adjust to characters (y - (length of this_number)) thru ¬
-1 of this_number as string as number
if x is not 0 then
set the first_part to characters 1 thru (x - 1) of this_number as string
else
set the first_part to ""
end if
set the second_part to characters (x + 1) thru (z - 1) of this_number as string
set the converted_number to the first_part
repeat with i from 1 to the decimal_adjust
try
set the converted_number to ¬
the converted_number & character i of the second_part
on error
set the converted_number to the converted_number & "0"
end try
end repeat
return the converted_number
else
return this_number
end if
end number_to_string
You specifically asked for a Perl or Objective-C alternative to Applescript.
Therefore, here is a Perl solution:
use strict;
use warnings;
print round_this(7.49908302, 2), "\n";
sub round_this {
my ($n, $decimals) = #_;
sprintf '%.*f', $decimals, $n;
}
output
7.50
Well AppleScript does many things for you. Because normally 1.5 is not the same as 0.75 * 2 (or at least not stable) in a boolean expression. To make real comparison easy for you the 1.50 (the result of 0.75 * 2) will be coerced into 1.5 for you. To make it work in AppleScript you need a string representation of the real number. However coercing a real into a string, AppleScript will take the system's localization into consideration. So the decimal mark and thousand separator can be different between machines depending on it's system preferences. The rounding is done by AppleScript when coercing a real to an integer. So something like this can work for you:
set n to 7.49958302
set i to round n rounding toward zero
set f to (n - i) * 100 as integer
set f to text 1 thru 2 of (f & "00" as string)
set r to (i as string) & "." & f
When you need this string back to a real again you can easily use the run script command. The run script command is like an eval. In AppleScript code the real numbers are not localized.
run script "7.50"
Related
I'm working under MS-Visio 2010 in VBA (not an expert) and I want to generate a random number (several numbers would be even better) based on a string as seed.
I know that Rnd(seed) with seed as a negative number exists. However, I don't know about any random generator with a string as seed. Maybe some kind of hash function with a number as result ?
I'd like something like :
print function("abc")
45
print function("xyz abc-5")
86
print function("abc")
45
with spaces, symbols and numbers support when inside the seed string.
I may see a workaround by converting each character to some ascii number corresponding and somehow using this big number as seed with Rnd but it definitely feels far-fetched. Does anyone knows of a fancier way of doing so ?
Combined these examples
VBA hash string
Convert HEX string to Unsigned INT (VBA)
to:
Function hash4(txt)
' copied from the example
Dim x As Long
Dim mask, i, j, nC, crc As Integer
Dim c As String
crc = &HFFFF
For nC = 1 To Len(txt)
j = Asc(Mid(txt, nC)) ' <<<<<<< new line of code - makes all the difference
' instead of j = Val("&H" + Mid(txt, nC, 2))
crc = crc Xor j
For j = 1 To 8
mask = 0
If crc / 2 <> Int(crc / 2) Then mask = &HA001
crc = Int(crc / 2) And &H7FFF: crc = crc Xor mask
Next j
Next nC
c = Hex$(crc)
' <<<<< new section: make sure returned string is always 4 characters long >>>>>
' pad to always have length 4:
While Len(c) < 4
c = "0" & c
Wend
Dim Hex2Dbl As Double
Hex2Dbl = CDbl("&h0" & c) ' Overflow Error if more than 2 ^ 64
If Hex2Dbl < 0 Then Hex2Dbl = Hex2Dbl + 4294967296# ' 16 ^ 8 = 4294967296
hash4 = Hex2Dbl
End Function
Try in immediate (Ctrl + G in VBA editor window):
?hash4("Value 1")
31335
?hash4("Value 2")
31527
This function will:
return different number for different input strings
sometimes they will match, it is called hash-collisions
if it is critical, you can use md5, sha-1 hashes, their examples in VBA also available
return same number for same input strings
I got some help from one, and the code works perfectly fine.
What im looking for, is an explanation of the code, since my basic VBA-knowledge does not provide me with it.
Can someone explain what happens from "Function" and down?
Sub Opgave8()
For i = 2 To 18288
If Left(Worksheets("arab").Cells(i, 12), 6) = "262015" Then
Worksheets("arab").Cells(i, 3) = "18" & UniqueRandDigits(5)
End If
Next i
End Sub
Function UniqueRandDigits(x As Long) As String
Dim i As Long
Dim n As Integer
Dim s As String
Do
n = Int(Rnd() * 10)
If InStr(s, n) = 0 Then
s = s & n
i = i + 1
End If
Loop Until i = x + 1
UniqueRandDigits = s
End Function
n = Int(Rnd()*10) returns a value between 0 and 9, since Rnd returns a value between 0 and 1 and Int converts it to an integer, aka, a natural number.
Then If InStr(s, n) = 0 checks if the random number is already in your result: if not, it adds it using the string concatenation operator &.
This process loops (do) until i = x + 1 where x is your input argument, so you get a string of length x. Then the first part just fills rows with these random strings.
N.B. : I explained using the logical order of the code. Your friend function UniqRandDigits is defined after the "business logic", but it's the root of the code.
The code loops from row 2 to 18288 in Worksheet "arab". If first 6 characters in 12th column are "262015", then in 3rd column macro will fill cell with value "18" followed by result of function UniqueRandDigits(5) which generates 5 unique digits (0-9).
About the UniqueRandDigits function, the most important is that Rnd() returns a value lesser than 1 but greater than or equal to zero.
Int returns integer value, so Int(Rnd() * 10) will generate a random integer number from 0 to 9.
If InStr(s, n) = 0 Then makes sure than generated integer value doesn't exist in already generated digits of this number, because as the function name says, they must be unique.
I am attempting to program a loop into a DDEPoke call to a VBA-supported function known as OPC. This will enable me to write to a PLC (RSLogix 500) database from an excel spreadsheet.
This is the code:
Private Function Open_RsLinx()
On Error Resume Next
Open_RsLinx = DDEInitiate(RsLinx, C1)
If Err.Number <> 0 Then
MsgBox "Error Connecting to topic", vbExclamation, "Error"
OpenRSLinx = 0 'Return false if there was an error
End If
End Function
Sub CommandButton1_Click()
RsLinx = Open_RsLinx()
For i = 0 To 255
DDEPoke RsLinx, "N16:0", Cells(1 + i, 2)
Next i
DDETerminate RsLinx
End Sub
This code works and will, if there is a link set up with an OPC server (in this case through RSLinx) write data to the PLC.
The problem is that I can't get the part DDEPoke RsLinx, "N16:0", Cells(1 + i, 2) to write data, sequentially, from one excel cell to one element of the PLC's data array.
I tried to do DDEPoke RsLinx, "N16:i", Cells(1 + i, 2) and DDEPoke RsLinx, "N16:0+i", Cells(1 + i, 2) but neither has any effect and the program doesn't write anything at all.
How can I set up the code to get N16:0 to increment all the way up to N16:255 and then stop?
Break the variable i out of the string. Be careful for the implicit type conversion though, depending on which (Str() or CStr()), you'll wind up with a leading space. Thus, convert the number Str(i), then wrap with Trim() to make sure there's no extra spaces, and concatenate that result back to your "N" string:
RsLinx = Open_RsLinx()
For i = 0 To 255
DDEPoke RsLinx, "N16:" & Trim(Str(i)), Cells(1 + i, 2)
Next i
The reason the i didn't work when it's inside the string is because that in VBA, anything within a set of quotes is considered a literal string. Unlike some other languages (PHP comes to mind) where variables can be resolved within a string like that, VBA must have variables concatenated. Consider the following:
Dim s As String
s = "world"
Debug.Print "Hello s!"
This outputs the literal of Hello s! to the immediate window, because s is treated not as a variable, but as part of the literal string. The correct way is through concatenation:
Dim s As String
s = "world"
Debug.Print "Hello " & s & "!"
That outputs the expected Hello World! to the immediate window, because s is now treated as a variable and is resolved and concatenated.
If that were not the case, the following might be difficult to deal with:
Dim i As Integer
For i = 0 to 9
Debug.Print "this" & i
Next i
You would then have:
th0s0
th1s1
th2s2
th3s3
th4s4
'etc
That'd make things pretty difficult to manage in a lot of cases.
With all that said, there are some languages - notably PHP - where, when using a certain set of quotes (either "" or '' - I don't recall which offhand), in fact does resolve the variable when embedded into the string itself:
$i = 5;
echo "this is number $i";
VBA does not have this feature.
Hope it helps...
I use vba in ms access,and found that ,if my parameter greater than 0x8000
less than 0x10000, the result is minus number
eg. Val("&H8000") = -32768 Val("&HFFFF")= -1
how can i get the unsigned number?
thanks!
There's a problem right here:
?TypeName(&HFFFF)
Integer
The Integer type is 16-bit, and 65,535 overflows it. This is probably overkill, but it was fun to write:
Function ConvertHex(ByVal value As String) As Double
If Left(value, 2) = "&H" Then
value = Right(value, Len(value) - 2)
End If
Dim result As Double
Dim i As Integer, j As Integer
For i = Len(value) To 1 Step -1
Dim digit As String
digit = Mid$(value, i, 1)
result = result + (16 ^ j) * Val("&H" & digit)
j = j + 1
Next
ConvertHex = result
End Function
This function iterates each digit starting from the right, computing its value and adding it to the result as it moves to the leftmost digit.
?ConvertHex("&H10")
16
?ConvertHex("&HFF")
255
?ConvertHex("&HFFFF")
65535
?ConvertHex("&HFFFFFFFFFFFFF")
281474976710655
UPDATE
As I suspected, there's a much better way to do this. Credits to #Jonbot for this one:
Function ConvertHex(ByVal value As String) As Currency
Dim result As Currency
result = CCur(value)
If result < 0 Then
'Add two times Int32.MaxValue and another 2 for the overflow
'Because the hex value is apparently parsed as a signed Int64/Int32
result = result + &H7FFFFFFF + &H7FFFFFFF + 2
End If
ConvertHex = result
End Function
Append an ampersand to the hex literal to force conversion to a 32bit integer:
Val("&HFFFF" & "&") == 65535
Val("&H8000&") == +32768
Don't use Val. Use one of the built-in conversion functions instead:
?CCur("&HFFFF")
65535
?CDbl("&HFFFF")
65535
Prefer Currency over Double in this case, to avoid floating-point issues.
Is it possible to work in Excel with some metric suffix notation:
If I write 1000, the cell shows 1k. if I write 1000000 the cell shows 1M.
I made two functions to make a workaround but maybe there's a more suitable solution.
Function lecI(cadena) As Double
u = Right(cadena, 1)
If u = "k" Then
mult = 1000
ElseIf u = "M" Then
mult = 1000000
ElseIf u = "m" Then
mult = 0.001
End If
lecI = Val(Left(cadena, Len(cadena) - 1)) * mult
End Function
Function wriI(num) As String
If num > 1000000 Then 'M
wriI = Str(Round(num / 1000000, 2)) & "M"
ElseIf num > 1000 Then 'k
wriI = Str(Round(num / 1000, 1)) & "k"
ElseIf num < 0.01 Then 'm
wriI = Str(Round(num * 1000, 1)) & "m"
Else: wriI = Str(num)
End If
Based on the link by #Vasily, you can get the desired outcome using only Conditional Formatting. This is nice because it means that all of your values are stored as Numbers and not Text and math works like normal.
Overall steps:
Create a new conditional formatting for each block of 1000 that applies the number format for that block
Add the largest condition at the top so it formats first
Rinse and repeat to get all the ones you want
Conditional formatting used to style column C which is just random data at different powers of ten. It is the same number as column D just styled differently.
Number formats, are pretty easy since they are the same as that link, see Large Numbers section.
ones = 0 " "
thousands = 0, " k"
millions = 0,, " M"
and so on for however many you want
Automation, if you don't want to click and type all day, here is some VBA that will create all the conditional formatting for you (for current Selection). This example goes out to billions. Keep adding powers of 3 by extending the Array with more entries.
Sub CreateConditionalsForFormatting()
'add these in as powers of 3, starting at 1 = 10^0
Dim arr_markers As Variant
arr_markers = Array("", "k", "M", "B")
For i = UBound(arr_markers) To 0 Step -1
With Selection.FormatConditions.Add(xlCellValue, xlGreaterEqual, 10 ^ (3 * i))
.NumberFormat = "0" & Application.WorksheetFunction.Rept(",", i) & " "" " & arr_markers(i) & """"
.StopIfTrue = False
End With
Next
End Sub
I change the StopIfTrue value so that this does not break other conditional formatting that might exist. If the largest condition is at the top (added first) then the NumberFormat from that one holds. By default, these are created with StopIfTrue = True. This is a moot point if you do not have any other conditional formatting on these cells.