tcl formatting floating point with fixed precision - tcl

# the unit of period is picosecond
set period 625000.0
set period_sec [format %3.6g [expr $period * 1e-12]]
puts $period_sec
result: 6.25e-07
Is there a way to force tcl to get results like 625e-09

Assuming that you want to format it to the nearest exponent, you could use a proc which formats it like this:
proc fix_sci {n} {
# Not a sci-fmt number with negative exponent
if {![string match "*e-*" $n]} {return $n}
# The set of exponents
set a 9
set b 12
# Grab the number (I called it 'front') and the exponent (called 'exp')
regexp -- {(-?[0-9.]+)e-0*([0-9]+)} $n - front exp
# Check which set of exponent is closer to the exponent of the number
if {[expr {abs($exp-$a)}] < [expr {abs($exp-$b)}]} {
# If it's the first, get the difference and adjust 'front'
set dif [expr {$exp-$a}]
set front [expr {$front/(10.0**$dif)}]
set exp $a
} else {
# If it's the first, get the difference and adjust 'front'
set dif [expr {$exp-$b}]
set front [expr {$front/(10.0**$dif)}]
set exp $b
}
# Return the formatted numbers, front in max 3 digits and exponent in 2 digits
return [format %3ge-%.2d $front $exp]
}
Note that your original code returns 6.25e-007 (3 digits in the exponent).
If you need to change the rule or rounding the exponent, you will have to change the if part (i.e. [expr {abs($exp-$a)}] < [expr {abs($exp-$b)}]). For example $exp >= $a could be used to format if the exponent is 9 or below.
ideone demo of above code for 'closest' exponent.
For Tcl versions before 8.5, use pow(10.0,$dif) instead of 10.0**$dif

I do not think there is anything in the format command that will help you directly. However, if you consider a slight variation on the format code, then it may be a lot easier to get what you want (with a bit of string manipulation):
format %#3.6g $number
gives a number like: 6.25000e-007
This can be parsed more easily:
Extract the exponent
Determine the number of positions to shift the decimal point
Shift it and replace the exponent
It is not entirely straightforward, I am afraid, but it should be doable. Wiki page http://wiki.tcl.tk/5000 may give you some inspiration.

Related

multidimensional array search in tcl

I am using multidimensional associative array in tcl ,from fourth column to N column I am storing values and i will compare each of those with second column and store the result in third column for each of row's values.Sometimes it searches the perfectly sometimes it skips the values and gives out error.Is there any better way to search in multidimensional array in tcl ? What is the problem with this code?
for {set index 1} {$index < $count} {incr index} {
for {set val 3} {$val < $d1 } {incr val} {
if {$flag1 != 1} {
if {$asarr($index,2)== $asarr($index,$val)} {
set asarr($index,1) 50
} else {
set asarr($index,1) 100
set flag1 1
break
}
}
}
set flag1 0
}
There is a discrepancy between your code and the problem explanation in your question.
From your question I summarise these requirements:
data is stored in 2 dimensional array
each row stores one set of data
within each row, data is stored in columns where:
1st column is not mention in the original question
2nd column contains a value against which all 'data columns' will be compared
3rd column is where the result of the comparison will be stored (looking at the code, result is either a 50 or a 100)
4th to Nth column hold the data that needs to be compared against value of 2nd column
And this is what your code actually does:
# start iterating array by rows
# first row has index 1
for {set index 1} {$index < $count} {incr index} {
# start iterating over columns
# NOTE: either column indexes are assumed to start at 0
# or we, in fact, start with the 3rd column instead of 4th
for {set val 3} {$val < $d1 } {incr val} {
# check the special flag named 'flag1'
# NOTE: it would be better to give it a meaningful name
# such as 'found' since it signals that we found
# an element equal to the one in 2nd column
if {$flag1 != 1} {
if {$asarr($index,2)== $asarr($index,$val)} {
# if the element at current position ($index,$val)
# is equal to the element at ($index,2)
# write '50' at ($index,1)
# NOTE: result is stored in 1st column,
# not in 3rd as the question would assume
set asarr($index,1) 50
} else {
# if the current element is not equal to the one
# in 2nd column, write '100' at ($index, 1).
# Again, this is not the 3rd column as requested originally
set asarr($index,1) 100
# set the special flag to 1, meaning that the above
# IF clause will not be run again
set flag1 1
# execute a break, meaning that we immediately interrupt
# the inner FOR loop, meaning that the above IF clause
# will not be run again in this iteration.
# As Donal pointed out, you could completely
# remove the $flag1 and achieve the same result with
# this break clause
break
}
}
}
# reset the flag so that IF clause is executed at least once
# for the next iteration of the outer loop
set flag1 0
}
If we assume that the 'flag1' is set to 0 (or anything != 1) before the FOR loops start, then the end result is that the code iterates through array rows, compares each column (starting from 3rd!) against column number 2, it repeats this for all other columns BUT ONLY if their values are equal to column 2 and EACH TIME OVERWRITES THE SAME value '50' over column 1. As soon as a different value is encountered, the result '100' is written to column 1 and the rest of the values are immediately skipped. Execution then starts from the beginning but with the next row.
How does that compare against your original intentions?
The code could (and should) be greatly simplified at least so it doesn't overwrite the same value many times, however, I am not exactly sure what you are trying to achieve. Please try to clarify your requirements.
I don't know if this will be useful (you said that the code does not behave as you would like it to), but here is an alternative version that behaves according to the requirements I guessed from your question (and listed above):
for {set index 1} {$index < $count} {incr index} {
set asarr($index,3) 50
for {set val 4} {$val < $d1 } {incr val} {
if {$asarr($index,2)!= $asarr($index,$val)} {
set asarr($index,3) 100
break
}
}
}

How to force expr to address a value as a string and not a number?

When TCL gets a string that starts with a 0 as its return value, it'll treat it as an octal number and will return the decimal value of the octal number. Is there a way to circumvent it and force expr to address the value as a string?
I encounter this problem because I have a line:
set val [expr {( $obj == "" ) ? "" : [$obj data]}]
And one the results of the [$obj data] operation is a binary string starting with 0, and the expr turns it into another number. Is there a way to fix this without turning the expr into an if?
The expr command is defined to convert its result to a number if it is legal to do so. It's been this way sinceā€¦ well, since at least Tcl 7.0 and probably since the first version of Tcl to have an expr command (which takes it a hugely long way back). This means that if you return a valid octal number (which 09 isn't), expr will convert it.
If this behaviour isn't desired, don't use expr for conditionals; use if. In your case, this works quite nicely (and I think it's clearer this time with the then and else pseudo-keywords).
set val [if {$obj == ""} then {} else {$obj data}]
(At the bytecode level, this generates almost the identical bytecode to what your original does, except it omits a call to the tryCvtToNumeric operation; that's the one you say you don't want!)
[warning: not an answer but a coment requiring formatting]
I'm not observing the same results
% proc obj {args} {return "09"} ;#specifically using invalid octal value
% set obj obj
obj
% $obj data
09
% set val [expr {($obj == "") ? "" : [$obj data]}]
09
% info patchlevel
8.6.1
What Tcl version are you using?

binary scan format 8-bit character code (c) in TCL

I am confused about the binary scan of types "c", as it is said that "c" refers to 8 bit character code. I have the following code to be maintained
puts ""
set b_val2 "0000021002020a042845245d868a8d9900081b000315aef0010c105d39b4f7c9a083a65e7d000306140508063024"
set msg2 [binary format H* $b_val2]
set msg2 [string range $msg2 1 end]
while {[binary scan $msg2 cc id2 len2] == 2} {
puts ""
#puts "SECOND ID is $id2 and SECOND LENGTH is $len2"
set id2 [expr {$id2 & 0xff}]
set len2 [expr {$len2 & 0xff}]
set val2 [string range $msg2 2 [expr {1+$len2}]]
switch -exact -- $id2 {
0 {
puts ""
if {$val2 == "\x10\x04"} {
puts "val is found for 04 "
} elseif {$val2 == "\x10\x02"} {
puts "ID is found for 02! CORRECT "
} else {
puts "not supported"
}
}
}
}
the idea is to take the value of "10 02" from the given hex. and this code work just fine until I Change the given Input of b_val as
00021002020903e845245ca0d29858081c00031ef7c001106d3931e7d3414e6d3df26831030614051608045c22
for the first given hex code "len" is "3" and it parses the binary correctly, but for the second hex Input, the "len2" is 16, hence parsing the wrong bytes.
I read that the binary scan cc will give back two variable of type 8 bit character code, but the above failure does not make any sense to me at all, as what i understand that what is the previous author tries to aim with the above code (expecially the set val2 where it tries to take the range) and why it fails for the second input
For starters: your code snippet never modifies msg2 within the while loop, so the scan returns the same result every loop, and you have an infinite loop. I tossed a break in to only loop once, but that leaves me uncertain I have the right behavior.
That said, the obvious issue is that when you go from your original message to the replacement, you've dropped the first byte (value of 00). Starting with line 2 (ignoring blank lines), where you set
set b_val2 "0000021002020a..."
let's parse by hand. Line 3 converts it to hex, and line 4 drops the first byte so that we start with a string of bytes with hex values of \x00 \x02 \x10 \x02 \x02 \x0a .... The binary scan on line 5 sets id2 to the first byte and len2 to the second byte; line 10 sets val2 to a string with values \x10 \x02, which matches your criteria. Success.
Now reparse with input of
set b_val2 "00021002020903e84..."
from your second input line. Again, the first byte is DISCARDED on line 4, leaving you with \x02 \x10 \x02 \x02 \x09 \x03.... Line 5 sets id2 to 2 and len2 to \x10, or decimal 16, which is what you see. That means val2 is very different from what you expected, but that's due to you dropping a byte from your input.
Byte parsers are EXTREMELY sensitive to initial position in the string. Once you mess that up, you'd better have a robust resynchronization mechanism or it's all over bar the shouting. This is one major reason that wire protocols are difficult. :)

Is there a way to vectorize sprintf() in an octave script?

Is there a way in Octave to vectorize sprintf()?
See the example, below. The iterative branch works as expected. When I set vectorize_sprintf=1, I don't get the desired effect. Instead of filling the LABELS cell array with one string per cell, all of the strings are concatenated into the first cell of LABELS and the remaining cells are left empty.
Is there a good way to vectorize number to string processing?
%%
%% Graph e^x and ln()
%%
top=8; %% highest power to graph
pow_vec = [0:top]';
ex_vec = e .^ pow_vec;
ln_vec = log(ex_vec);
LABELS=cell(size(ex_vec)); %% Pre-allocate cell matrix
vectorize_sprintf=1;
if ( vectorize_sprintf )
%% Vectorize attempt at sprintf is BROKEN
LABELS=sprintf("%d = log2(%d)\n",ln_vec,ex_vec)
else
%% Iterate for sprintf WORKS
counter=1;
for i = pow_vec'
LABELS(counter++) = sprintf("e^%d=%d\nln(%d)=%d",pow_vec(counter),ex_vec(counter) ,ex_vec(counter) ,ln_vec(counter));
endfor
endif
figure(1); %% Graph e^x
hold on;
plot(ln_vec, ex_vec, "r-"); %% solid red line segments
plot(ln_vec, ex_vec, "rx"); %% markers on the datapoints
text(ln_vec, ex_vec, LABELS);
figure(2); %% Graph ln()
hold on;
plot(ex_vec, ln_vec, "r-"); %% solid red line segments
plot(ex_vec, ln_vec, "rx"); %% markers on the datapoints
text(ex_vec, ln_vec, LABELS);
Your vectorization attempt is not filling the first cell of LABELS, it is actually converting it to a char array (take a look at whos LABELS). I think sprintf always returns a single string only. Since you're using \n, maybe you could use strsplit to get a cell array.
ex_vec = e .^ [0:8];
ln_vec = log(ex_vec);
LABELS = strsplit (sprintf ("%.0f = log2(%.4f)\n", [ln_vec; ex_vec]), "\n")(1:end-1)
I have made the following changes to what you had before:
don't transpose the [0:8] so you get a row, and you don't have to transpose it again when generating a matrix for sprintf
replaced the %d by %.0f and %.4f. It's specially important the %.0f because log (e^8) = 8.0000 but what is used in the string is fix (log (e^8)) which is 7. Alternatively, you can round ln_vec

Converting to Base 10

Question
Let's say I have a string or array which represents a number in base N, N>1, where N is a power of 2. Assume the number being represented is larger than the system can handle as an actual number (an int or a double etc).
How can I convert that to a decimal string?
I'm open to a solution for any base N which satisfies the above criteria (binary, hex, ...). That is if you have a solution which works for at least one base N, I'm interested :)
Example:
Input: "10101010110101"
-
Output: "10933"
It depends on the particular language. Some have native support for arbitrary-length integers, and others can use libraries such as GMP. After that it's just a matter of doing the lookup in a table for the digit value, then multiplying as appropriate.
This is from a Python-based computer science course I took last semester that's designed to handle up to base-16.
import string
def baseNTodecimal():
# get the number as a string
number = raw_input("Please type a number: ")
# convert it to all uppercase to match hexDigits (below)
number = string.upper(number)
# get the base as an integer
base = input("Please give me the base: ")
# the number of values that we have to change to base10
digits = len(number)
base10 = 0
# first position of any baseN number is 1's
position = 1
# set up a string so that the position of
# each character matches the decimal
# value of that character
hexDigits = "0123456789ABCDEF"
# for each 'digit' in the string
for i in range(1, digits+1):
# find where it occurs in the string hexDigits
digit = string.find(hexDigits, number[-i])
# multiply the value by the base position
# and add it to the base10 total
base10 = base10 + (position * digit)
print number[-i], "is in the " + str(position) + "'s position"
# increase the position by the base (e.g., 8's position * 2 = 16's position)
position = position * base
print "And in base10 it is", base10
Basically, it takes input as a string and then goes through and adds up each "digit" multiplied by the base-10 position. Each digit is actually checked for its index-position in the string hexDigits which is used as the numerical value.
Assuming the number that it returns is actually larger than the programming language supports, you could build up an array of Ints that represent the entire number:
[214748364, 8]
would represent 2147483648 (a number that a Java int couldn't handle).
That's some php code I've just written:
function to_base10($input, $base)
{
$result = 0;
$length = strlen($input);
for ($x=$length-1; $x>=0; $x--)
$result += (int)$input[$x] * pow($base, ($length-1)-$x);
return $result;
}
It's dead simple: just a loop through every char of the input string
This works with any base <10 but it can be easily extended to support higher bases (A->11, B->12, etc)
edit: oh didn't see the python code :)
yeah, that's cooler
I would choose a language which more or less supports natively math representation like 'lisp'. I know it seems less and less people use it, but it still has its value.
I don't know if this is large enough for your usage, but the largest integer number I could represent in my common lisp environment (CLISP) was 2^(2^20)
>> (expt 2 (expt 2 20)
In lisp you can easily represent hex, dec, oct and bin as follows
>> \#b1010
10
>> \#o12
10
>> 10
10
>> \#x0A
10
You can write rationals in other bases from 2 to 36 with #nR
>> #36rABCDEFGHIJKLMNOPQRSTUVWXYZ
8337503854730415241050377135811259267835
For more information on numbers in lisp see: Practical Common Lisp Book