Reliable way of getting the exact decimals from any number - lua

I'm having problem returning spesific amount of decimal numbers from this function, i would like it to get that info from "dec" argument, but i'm stuck with this right now.
Edit: Made it work with the edited version bellow but isn't there a better way?
local function remove_decimal(t, dec)
if type(dec) == "number" then
for key, num in pairs(type(t) == "table" and t or {}) do
if type(num) == "number" then
local num_to_string = tostring(num)
local mod, d = math.modf(num)
-- find only decimal numbers
local num_dec = num_to_string:sub(#tostring(mod) + (mod == 0 and num < 0 and 3 or 2))
if dec <= #num_dec then
-- return amount of deciamls in the num by dec
local r = d < 0 and "-0." or "0."
local r2 = r .. num_dec:sub(1, dec)
t[key] = mod + tonumber(r2)
end
end
end
end
return t
end
By passing the function bellow i want a result like this:
result[1] > 0.12
result[2] > -0.12
result[3] > 123.45
result[4] > -1.23
local result = remove_decimal({0.123, -0.123, 123.456, -1.234}, 2)
print(result[1])
print(result[2])
print(result[3])
print(result[4])
I tried this but it seems to only work with one integer numbers and if number is 12.34 instead of 1.34 e.g, the decimal place will be removed and become 12.3. Using other methods
local d = dec + (num < 0 and 2 or 1)
local r = tonumber(num_to_string:sub(1, -#num_to_string - d)) or 0

A good approach is to find the position of the decimal point (the dot, .) and then extract a substring starting from the first character to the dot's position plus how many digits you want:
local function truncate(number, dec)
local strnum = tostring(number)
local i, j = string.find(strnum, '%.')
if not i then
return number
end
local strtrn = string.sub(strnum, 1, i+dec)
return tonumber(strtrn)
end
Call it like this:
print(truncate(123.456, 2))
print(truncate(1234567, 2))
123.45
1234567
To bulk-truncate a set of numbers:
local function truncate_all(t, dec)
for key, value in pairs(t) do
t[key] = truncate(t[key], dec)
end
return t
end
Usage:
local result = truncate_all({0.123, -0.123, 123.456, -1.234}, 2)
for key, value in pairs(result) do
print(key, value)
end
1 0.12
2 -0.12
3 123.45
4 -1.23

One could use the function string.format which is similar to the printf functions from C language. If one use the format "%.2f" the resulting string will contain 2 decimals, if one use "%.3f" the resulting string will be contain 3 decimals, etc. The idea is to dynamically create the format "%.XXXf" corresponding to the number of decimal needed by the function. Then call the function string.format with the newly created format string to generate the string "123.XXX". The last step would be to convert back the string to a number with the function tonumber.
Note that if one want the special character % to be preserved when string.format is called, you need to write %%.
function KeepDecimals (Number, DecimalCount)
local FloatFormat = string.format("%%.%df", DecimalCount)
local String = string.format(FloatFormat, Number)
return tonumber(String)
end
The behavior seems close to what the OP is looking for:
for Count = 1, 5 do
print(KeepDecimals(1.123456789, Count))
end
This code should print the following:
1.1
1.12
1.123
1.1235
1.12346
Regarding the initial code, it's quite straight-forward to integrate the provided solution. Note that I renamed the function to keep_decimal because in my understanding, the function will keep the requested number of decimals, and discard the rest.
function keep_decimal (Table, Count)
local NewTable = {}
local NewIndex = 1
for Index = 1, #Table do
NewTable[NewIndex] = KeepDecimal(Table[Index], Count)
NewIndex = NewIndex + 1
end
return NewTable
end
Obviously, the code could be tested easily, simply by copy and pasting into a Lua interpreter.
Result = keep_decimal({0.123, -0.123, 123.456, -1.234}, 2)
for Index = 1, #Result do
print(Result[Index])
end
This should print the following:
0.12
-0.12
123.46
-1.23
Edit due to the clarification of the need of truncate:
function Truncate (Number, Digits)
local Divider = Digits * 10
local TruncatedValue = math.floor(Number * Divider) / Divider
return TruncatedValue
end
On my computer, the code is working as expected:
> Truncate(123.456, 2)
123.45

Related

Lua - Analysing values in a text file

I’m writing power (watts) values to a text file in order to extract information I can use.
4.7
4.7
4.8
5.2
5.1
4.6
4.6
4.6
Currently I have the following code to give me the average, but I’d like to add to it so it tells me more such as, what was the highest value, the lowest value, the most frequent value, and if at all possible if there are any ‘0’ values recorded (* - if possible with that last one it would be good to ignore them..)
local ctr = 0
local sum = 0
for _ in io.lines"/www/EnergyPlug-b.txt" do
ctr = ctr + 1
end
print(ctr)
for line in io.lines"/www/EnergyPlug-b.txt" do
sum = sum + line
end
print(sum)
average = sum / ctr
print(average)
I did explore creating a Lua table via table.insert() off of the first io.lines section, like the following, but I’m not sure how good it is?
local rows = {}
-- go over the file line by line
for line in io.lines("/www/EnergyPlug-b.txt") do
-- if line matches ?.? then insert into table
local value = line:match("%d%p%d") -- 1.5
--print(value)
table.insert(rows, value)
end
local arraymax = math.max(unpack(rows))
local arraymin = math.min(unpack(rows))
print (arraymax) -- ?.?
print (arraymin) -- ?.?
If the above is suitable, how best should I go about identifying the items/values i mentioned at the very start ?
In the first snippet there is no reason to have a separate loop for ctr and sum. You can do it in one loop.
Your second snipped is ok. unpack is limited so this won't work for many thousand values.
You have to traverse the table anyway to get the other values so you can determin min and max in that loop too without that size limit.
local value = line:match("%d%p%d") if there are only those numbers in that file you can skip the pattern matching here.
The calculations are pretty straigt forward. I'm not sure what you're struggling with here.
local min = math.huge -- use a value that is definitely bigger than any value
local max = -math.huge -- use a value that is definitely smaller than any value
local counts = {} -- an emtpy table we'll use to count how often each value occurs
local numIngored = 0 -- how many 0 values have we ignored?
for line in io.lines(yourFileName) do
-- convert string to a number
local value = tonumber(line)
-- if we take the value into account
if value ~= 0.0 then
-- update min and max values
min = value < min and value or min
max = value > max and value or max
-- update counts
counts[value] = counts[value] and counts[value] + 1 or 1
else
-- count ignored values
numIgnored = numIgnored + 1
end
end
I'll leave it up to you to get the most frequent values from counts
Thanks to #piglet and using other SO posts, here is what I ended up with..
local min = math.huge -- use a value that is definitely bigger than any value
local max = -math.huge -- use a value that is definitely smaller than any value
local ctr = 0
local valtotal = 0
local counts = {} -- an emtpy table we'll use to count how often each value occurs
local numIngored = 0 -- how many 0 values have we ignored?
for line in io.lines("/www/EnergyPlug-b.txt") do
-- convert string to a number
local value = tonumber(line)
-- if we take the value into account
if value ~= 0.0 then
ctr = ctr + 1
valtotal = valtotal + value
-- update min and max values
min = value < min and value or min
max = value > max and value or max
-- update counts
counts[value] = counts[value] and counts[value] + 1 or 1
else
-- count ignored values
numIgnored = numIgnored + 1
end
end
print("----Table print out -----")
for k, v in pairs(counts) do
print(k,v)
end
print("---------------------------")
print("Lowest value recorded = " ..min)
print("Highest value recorded = " ..max)
print("Average value recorded = " ..round2(valtotal / ctr, 1))
print("Number of values recorded = " ..ctr)
print("---------------------------")
local max_val, key1 = -math.huge
for k, v in pairs(counts) do
if v > max_val then
max_val, key1 = v, k
end
end
local min_val, key2 = math.huge
for k1, v1 in pairs(counts) do
if v1 < min_val or v1 == min_val then
min_val, key2 = v1, k1
end
end
local min_qty, max_qty = 0, 0
local min_str, max_str = "", ""
for k2, v2 in pairs(counts) do
if v2 == min_val then
min_qty = min_qty + 1
min_str = min_str .. " " .. k2
elseif v2 == max_val then
max_qty = max_qty + 1
max_str = max_str .. " " .. k2
end
end
if min_qty == 1 then
print("Least recorded value was = " ..key2.. " recorded "..min_val.." times")
else
print("Least recorded values were = " ..min_str.. " each recorded "..min_val.." time")
end
if max_qty == 1 then
print("Most recorded value was = " ..key1.. " recorded "..max_val.." times")
else
print("Least recorded values were = " ..max_str.. " each recorded "..max_val.." time")
end
print("---------------------------")

Lua: Type of a character

I need a function
function getCharType(c)
local i = string.byte(c) -- works only for 1 byte chars
if (i > 48) and (i < 57) then return 1 end
if (i > 97) and (i < 122) then return 2 end
return 0
end
which should return
2 - if c is a letter
1 - if c is a digit
0 - if c is a symbol (anything else)
c itself will already be a lower case character: charType = getCharType(string.lower(Character)). If Unicode characters are possible, that would be fine.
With the above getCharType("ö") is 0.
To find out whether a non-ASCII character is an uppercase or lowercase letter or a number, you need Unicode data. Module:Unicode data on Wikipedia has a function like this that uses Module:Unicode data/category (data for the General Category of Unicode characters).
Here's an adaptation of the lookup_category function from Module:Unicode data. I haven't included the Unicode data (Module:Unicode data/category); you will have to copy it from the link above.
local category_data -- set this variable to the table in Module:Unicode data/category above
local floor = math.floor
local function binary_range_search(code_point, ranges)
local low, mid, high
low, high = 1, #ranges
while low <= high do
mid = floor((low + high) / 2)
local range = ranges[mid]
if code_point < range[1] then
high = mid - 1
elseif code_point <= range[2] then
return range
else
low = mid + 1
end
end
return nil
end
function get_category(code_point)
if category_data.singles[code_point] then
return category_data.singles[code_point]
else
local range = binary_range_search(code_point, category_data.ranges)
return range and range[3] or "Cn"
end
end
The function get_category takes a code point (a number) and returns the name of the General Category. I guess the categories you are interested in are Nd (number, decimal digit) and the categories that begin with L (letter).
You will need a function that converts a character to a codepoint. If the file is encoded in UTF-8 and you are using Lua 5.3, you can use the utf8.codepoint function: get_category(utf8.codepoint('ö')) will result in 'Ll'. You can convert category codes to the number value that your function above uses: function category_to_number(category) if category == "Nd" then return 1 elseif category:sub(1, 1) == "L" then return 2 else return 0 end end.
Works only with ASCII characters (not Unicode)
function getCharType(c)
return #c:rep(3):match(".%w?%a?")-1
end

PBKDF2 Lua Implementation Issue

I am trying to write a PBKDF2 implementation in pure lua. I am writing it because I want to use it in a sandboxed lua environment that does not allow outside libraries. I had a look at the standard document from the IETF and had at it. Below is the code I have come up with:
do
package.preload["pbkdf2"] = function()
local hmac = require 'hmac'
local len = string.len
local gsub = string.gsub
local format = string.format
local byte = string.byte
local char = string.char
local concat = table.concat
local ceil = math.ceil
local function toBytes(str)
local tmp = {}
for i = 1, len(str) do
tmp[i] = byte(str, i)
end
return tmp
end
local function toString(bArray)
local tmp = {}
for i = 1, #bArray do
tmp[i] = char(bArray[i])
end
tmp = concat(tmp)
return tmp
end
-- transform a string of bytes in a string of hexadecimal digits
local function asHex(s)
local h = gsub(s, ".", function(c)
return format("%02x", byte(c))
end)
return h
end
local num2string = function(l, n)
local s = {}
for i = 1, n do
local idx = (n + 1) - i
s[idx] = char(l & 255)
l = l >> 8
end
s = concat(s)
return s
end
local buildBlock = function(hFun, password, salt, c, int)
local tmp
local tmp2
for i = 1, c do
if i == 1 then
print(int)
print(salt .. int)
-- PRF(password, salt || INT_32_BE(i)
-- return result of hash as a byte string
tmp = hmac.hash(hFun, password, salt .. num2string(int, 4), true)
else
-- returns result of hash as byte string
tmp2 = hmac.hash(hFun, password, tmp, true)
-- transform to byte arrays
tmp2 = toBytes(tmp2)
tmp = toBytes(tmp)
assert(#tmp == #tmp2)
-- apply XOR over bytes in both arrays
-- save results to final array
for j = 1, #tmp do
-- perform XOR operation on both elements in the respective arrays
tmp[j] = tmp[j] ~ tmp2[j]
end
-- transform back into byte string to pass to next hash
tmp = toString(tmp)
end
end
return tmp
end
local truncate = function(str, pos)
return string.sub(str, 1, pos)
end
local deriveKey = function(hFun, message, salt, c, dLen)
local hLen = hFun.outputSize
-- the derived key cannot be larger than (2^32 * hLen)
if dLen > (2^32) * hLen then error("The derived key cannot be larger than 2^32 times the output size of the hash function.") end
-- the block size is the desired key length divided by the output size of the underlying hash function, rounded up
local blockSize = ceil(dLen/hLen)
-- to store our blocks
local final = {}
for i = 1, blockSize do
-- lets make our blocks in here
final[i] = buildBlock(hFun, message, salt, c, i)
end
local result
if #final == 1 then
result = final[1] -- we only have one block
else
result = concat(final) -- turns final into a bytestring to be outputted
end
--if #result > dLen then truncate(final, dLen) end
assert(#result == dLen)
return asHex(result) -- outputs as a hex value
end
return {deriveKey = deriveKey}
end
end
This code is not getting the correct answers. Testing this code with test vectors provided here, assuming that the underlying PRF is HMAC-SHA256, the output is below:
key: "password"
salt: "salt"
c: 1
dkLen: 32
Got: 13463842ec330934dc124494b40d8baade465b72f3fcadad741f2d0e052fd2f5
Expected: 120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b
key: "password"
salt: "salt"
c: 2
dkLen: 32
Got: 8b82aed26f503effdbc6c14bc7f0338b2b90e387f14ac1f91f9ad74e618f9558
Expected: AE4D0C95AF6B46D32D0ADFF928F06DD02A303F8EF3C251DFD6E2D85A95474C43
I believe it may have something to do with the string to byte encoding, but I cannot pinpoint what exactly is causing the issue. When I was testing my HMAC code, I had to rely on online generators because I couldn't find vectors for HMAC-SHA224 and HMAC-SHA256. Some calculators would give me completely different output values for the same key, message combination. That could be because of how they are processing the inputs, but I am not sure. I would appreciate it if someone more experienced could help me out with this.
EDIT: This problem is solved. Seems that all that was needed was to pass int as a binary string of length 4. I updated the code with the fixes.
EDIT 2: I read the standard again to realize the solution was in my face the entire time (standard says to encode i as a 32-bit big endian integer).
The solution was to convert int to a binary string of length 4. Thanks to #EgorSkriptunoff for his insight.

How can I better parse variable time stamp information in Fortran?

I am writing code in gfortran to separate a variable time stamp into its separate parts of year, month, and day. I have written this code so the user can input what the time stamp format will be (ie. YEAR/MON/DAY, DAY/MON/YEAR, etc). This creates a total of 6 possible combinations. I have written code that attempts to deal with this, but I believe it to be ugly and poorly done.
My current code uses a slew of 'if' and 'goto' statements. The user provides 'tsfo', the time stamp format. 'ts' is a character array containing the time stamp data (as many as 100,000 time stamps). 'tsdelim' is the delimiter between the year, month, and day. I must loop from 'frd' (the first time stamp) to 'nlines' (the last time stamp).
Here is the relevant code.
* Choose which case to go to.
first = INDEX(tsfo,tsdelim)
second = INDEX(tsfo(first+1:),tsdelim) + first
if (INDEX(tsfo(1:first-1),'YYYY') .ne. 0) THEN
if (INDEX(tsfo(first+1:second-1),'MM') .ne. 0) THEN
goto 1001
else
goto 1002
end if
else if (INDEX(tsfo(1:first-1),'MM') .ne. 0) THEN
if (INDEX(tsfo(first+1:second-1),'DD') .ne. 0) THEN
goto 1003
else
goto 1004
end if
else if (INDEX(tsfo(1:first-1),'DD') .ne. 0) THEN
if (INDEX(tsfo(first+1:second-1),'MM') .ne. 0) THEN
goto 1005
else
goto 1006
end if
end if
first = 0
second = 0
* Obtain the Julian Day number of each data entry.
* Acquire the year, month, and day of the time stamp.
* Find 'first' and 'second' and act accordingly.
* Case 1: YYYY/MM/DD
1001 do i = frd,nlines
first = INDEX(ts(i),tsdelim)
second = INDEX(ts(i)(first+1:),tsdelim) + first
read (ts(i)(1:first-1), '(i4)') Y
read (ts(i)(first+1:second-1), '(i2)') M
read (ts(i)(second+1:second+2), '(i2)') D
* Calculate the Julian Day number using a function.
temp1(i) = JLDYNUM(Y,M,D)
end do
goto 1200
* Case 2: YYYY/DD/MM
1002 do i = frd,nlines
first = INDEX(ts(i),tsdelim)
second = INDEX(ts(i)(first+1:),tsdelim) + first
read (ts(i)(1:first-1), '(i4)') Y
read (ts(i)(second+1:second+2), '(i2)') M
read (ts(i)(first+1:second-1), '(i2)') D
* Calculate the Julian Day number using a function.
temp1(i) = JLDYNUM(Y,M,D)
end do
goto 1200
* Onto the next part of the code
1200 blah blah blah
I believe this code will work, but I do not think it is a very good method. Is there a better way to go about this?
It is important to note that the indices 'first' and 'second' must be calculated for each time stamp as the month and day can both be represented by 1 or 2 integers. The year is always represented by 4.
With only six permutations to handle I would just build a look-up table with the whole tsfo string as the key and the positions of year, month and day (1st, 2nd or 3rd) as the values. Any unsupported formats should produce an error, which I haven't coded below. When subsequently you loop though your ts list and split an item you know which positions to cast to the year, month and day integer variables:
PROGRAM timestamp
IMPLICIT NONE
CHARACTER(len=10) :: ts1(3) = ["2000/3/4 ","2000/25/12","2000/31/07"]
CHARACTER(len=10) :: ts2(3) = ["3/4/2000 ","25/12/2000","31/07/2000"]
CALL parse("YYYY/DD/MM",ts1)
print*
CALL parse("DD/MM/YYYY",ts2)
CONTAINS
SUBROUTINE parse(tsfo,ts)
IMPLICIT NONE
CHARACTER(len=*),INTENT(in) :: tsfo, ts(:)
TYPE sti
CHARACTER(len=10) :: stamp = "1234567890"
INTEGER :: iy = -1, im = -1, id = -1
END TYPE sti
TYPE(sti),PARAMETER :: stamps(6) = [sti("YYYY/MM/DD",1,2,3), sti("YYYY/DD/MM",1,3,2),&
sti("MM/DD/YYYY",2,3,1), sti("DD/MM/YYYY",3,2,1),&
sti("MM/YYYY/DD",2,1,3), sti("DD/YYYY/MM",3,1,2)]
TYPE(sti) :: thisTsfo
INTEGER :: k, k1, k2
INTEGER :: y, m, d
CHARACTER(len=10) :: cc(3)
DO k=1,SIZE(stamps)
IF(TRIM(tsfo) == stamps(k)%stamp) THEN
thisTsfo = stamps(k)
EXIT
ENDIF
ENDDO
print*,thisTsfo
DO k=1,SIZE(ts)
k1 = INDEX(ts(k),"/")
k2 = INDEX(ts(k),"/",BACK=.TRUE.)
cc(1) = ts(k)(:k1-1)
cc(2) = ts(k)(k1+1:k2-1)
cc(3) = ts(k)(k2+1:)
READ(cc(thisTsfo%iy),'(i4)') y
READ(cc(thisTsfo%im),'(i2)') m
READ(cc(thisTsfo%id),'(i2)') d
PRINT*,ts(k),y,m,d
ENDDO
END SUBROUTINE parse
END PROGRAM timestamp
I would encode the different cases in another way, like this:
module foo
implicit none
private
public encode_datecode
contains
integer function encode_datecode(datestr, sep)
character(len=*), intent(in) :: datestr, sep
integer :: first, second
character(len=1) :: c1, c2, c3
first = index(datestr, sep)
second = index(datestr(first+1:), sep) + first
c1 = datestr(1:1)
c2 = datestr(first+1:first+1)
c3 = datestr(second+1:second+1)
foo = num(c1) + 3*num(c2) + 9*num(c3)
end function encode_datecode
integer function num(c)
character(len=1) :: c
if (c == 'Y') then
num = 0
else if (c == 'M') then
num = 1
else if (c == 'D') then
num = 2
else
stop "Illegal character"
end if
end function num
end module foo
and then handle the legal cases (21, 15, 19, 7, 11, 5) in a SELECT statement.
This takes advantage of the fact that there won't be a 'YDDY/MY/YM' format.
If you prefer better binary or decimal readability, you can also multiply by four or by 10 instead of 3.

Sum of primes in a number - Lua

I'm trying to calculate the sum of the prime numbers in a given number. For instance, for the number 123456, the result will be 10 because 2+3+5 = 10.
I tried to write a code that does that in Lua but I had some issues.
First, here is the code:
function isPrime(num)
if(num == 1 or (num ~= 2 and num%2 == 0)) then
return false
end
for i=3, math.sqrt(num), 2 do
if(num%i == 0) then
return false
end
end
return true
end
function sumOfPrimes(num)
local sum = 0
for digit in string.gmatch(num,"%d") do
local prime = isPrime(digit)
if(isPrime(digit)) then
sum = sum + digit
end
print(digit)
end
return sum
end
function main()
print(sumOfPrimes(123456))
end
main()
It returnes 9 instead of 10. Another thing I've noticed is it adds 1 also to sum, but 1 isn't a prime. What's the problem here?
string.gmatch returns a string, you need to convert it to number before doing calculations
Btw, you are doing the prime validation twice on your loop.
This is a fixed version (returns 10 as expected):
...
function sumOfPrimes(num)
local sum = 0
for digit in string.gmatch(num, "%d") do
digit = tonumber(digit) --needed conversion
local prime_digit = isPrime(digit)
if prime_digit then
sum = sum + digit
end
end
return sum
end

Resources