tcl floating-point numbers handling - tcl

I am just starting to learn TCL. On its Tutorial page, there is a part of description as the following:
1.2 / 0.1 results in 11.999999999999998, not 12. That is an example of a very nasty aspect of most computers and programming languages today: they do not work with ordinary decimal fractions, but with binary fractions. So, 0.5 can be represented exactly, but 0.1 can not.
I don't know what exactly this means. I have tried the followings:
% expr {1.2 / 0.1}
11.999999999999998
% expr {1.2 / 0.5}
2.4
% expr {1.2 / 0.4}
2.9999999999999996
% expr {1.2 / 0.3}
4.0
Like it describes, 1.2 / 0.5 will give the exact answer. But with 0.1 as divisor, it won't.
Could anyone kindly explain what the mechanism is here? Thanks.

In short, this is not a Tcl specific issue but rather a consequence of representing decimal numbers using binary digits.
A wikipedia article is here.
And here is an even more detailed explanation.

Now, that you know that is not specific to Tcl, you might still want to explore ways forward when programming in Tcl. An educating start is exactexpr provided by math::exact module of tcllib
To pick up your example:
package req math::exact
namespace import math::exact::*
set a [[exactexpr {12/10}] ref]
set b [[exactexpr {1/10}] ref]
set c [[exactexpr {$a / $b}] ref]
$c asFloat 8; # returns '1.2e1'
$a unref; $b unref; $c unref;

https://wiki.tcl.tk/1650
As of Tcl 8.5, changing $tcl_precision to a value other than its default value of 0 is deprecated. If you need to control the display precision of floating point values, use format (eg format %.12g $x).
% set tcl_precision 12
12
% set a [expr 1.00000000000123]
1.0
% set b [expr 1.0]
1.0
% expr { $a == $b }
0
% expr { $a eq $b }
1

Related

Can someone pleaase clarify the meaning of the entire code mentioned below?

$seg addPoint "[expr -0.5 * $l] $r 0"
I have to convert above TCL code to python code for meshing, but I am unable to understand the above code.Can someone explain me the eqaution in the code?
The first word $seg is likely an object that was created, possibly using Tcl::oo as the object-oriented framework. This is probably a line segment consisting of two points.
addPoint looks like a method to add a point to the segment.
"[expr -0.5*$l] $r 0" is a three-item list used as an argument to the method. Why is it three items?
expr is the Tcl command to do math operations, so expr -0.5 * $l is just multiplying -0.5 by the value of $l.
A possible Python equivalent would be:
seg = Segment()
l = 2.0
r = 1.0
seg.addPoint(-0.5 * l, r, 0)
...where creating the Segment class and addPoint() method is up to you.

Comparison of rational numbers in GNU/Octave independent of numeric precision

The Octave interpreter evaluates this expression as false:
>> 2/3 + 1/6 == 5/6
ans = 0
cause
>> 2/3 + 1/6 - 5/6
ans = -1.11022302462516e-16
This can be avoided with the rat (or rats) function, or casting the values, but the resulting expression lacks the clear formatting of the initial one:
>> all(rat(2/3 + 1/6) == rat(5/6))
ans = 1
>> single(2/3 + 1/6) == single(5/6)
ans = 1
When using Octave to teach kids arithmetic, 'dirty' translations of mathematical expressions are of no use.
Is there any global adjustment that could be done to evaluate as true the original expression?
Julia has a rational numbers type and is free. You don't need to use Octave symbolics. You can use a Jupyter notebook. Note that notebooks make great teaching tools. My professor used these to teach. Some examples are here.
2//3+1//6
5//6

Is there a "native" way to convert from numbers to dB in Tcl

dB or decibel is a unit that is used to show ratio in logarithmic scale, and specifecly, the definition of dB that I'm interested in is X(dB) = 20log(x) where x is the "normal" value, and X(dB) is the value in dB. When wrote a code converted between mil. and mm, I noticed that if I use the direct approach, i.e., multiplying by the ratio between the units, I got small errors on the opposite conversion, i.e.: to_mil [to_mm val_in_mil] wasn't equal to val_in_mil and the same with mm. The library units has solved this problem, as the conversions done by it do not have that calculation error. But the specifically doesn't offer (or I didn't find) the option to convert a number to dB in the library.
Is there another library / command that can transform numbers to dB and dB to numbers without calculation errors?
I did an experiment with using the direct math conversion, and I what I got is:
>> set a 0.005
0.005
>> set b [expr {20*log10($a)}]
-46.0205999133
>> expr {pow(10,($b/20))}
0.00499999999999
It's all a matter of precision. We often tend to forget that floating point numbers are not real numbers (in the mathematical sense of ℝ).
How many decimal digit do you need?
If you, for example, would only need 5 decimal digits, rounding 0.00499999999999 will give you 0.00500 which is what you wanted.
Since rounding fp numbers is not an easy task and may generate even more troubles, you might just change the way you determine if two numbers are equal:
>> set a 0.005
0.005
>> set b [expr {20*log10($a)}]
-46.0205999133
>> set c [expr {pow(10,($b/20))}]
0.00499999999999
>> expr {abs($a - $c) < 1E-10}
1
>> expr {abs($a - $c) < 1E-20}
0
>> expr {$a - $c}
8.673617379884035e-19
The numbers in your examples can be considered "equal" up to an error or 10-18. Note that this is just a rough estimate, not a full solution.
If you're really dealing with problems that are sensitive to numerical errors propagation you might look deeper into "numerical analysis". The article What Every Computer Scientist Should Know About Floating-Point Arithmetic or, even better, this site: http://floating-point-gui.de might be a start.
In case you need a larger precision you should drop your "native" requirement.
You may use the BigFloat offered by tcllib (http://tcllib.sourceforge.net/doc/bigfloat.html or even use GMP (the GNU multiple precision arithmetic library) through ffidl (http://elf.org/ffidl). There's an interface already defined for it: gmp.tcl
With the way floating point numbers are stored, every log10(...) can't correspond to exactly one pow(10, ...). So you lose precision, just like the integer divisions 89/7 and 88/7 both are 12.
When you put a value into floating point format, you should forget the ability to know it's exact value anymore unless you keep the old, exact value too. If you want exactly 1/200, store it as the integer 1 and the integer 200. If you want exactly the ten-logarithm of 1/200, store it as 1, 200 and the info that a ten-logarithm has been done on it.
You can fill your entire memory with the first x decimal digits of the square root of 2, but it still won't be the square root of 2 you store.

round number to 2 decimal places

I need to round a number to two decimal places.
Right now the following rounds to the nearest integer I guess
puts [expr {round($total_rate)}]
If I do something like below it does not work. Is there another way around?
puts [expr {round($total_rate,2)}]
The simplest way to round to a specific number of decimal places is with format:
puts [format "%.2f" $total_rate]
Be aware that if you're using the rounded value for further calculations instead of display to users, most values that you print using rounding to X decimal places will not have an exact representation in binary arithmetic (which Tcl uses internally, like vast numbers of other programming languages). It's best to reserve rounding to a specific number of DPs to the point where you're showing values to people.
expr {double(round(100*$total_rate))/100}
example
% set total_rate 1.5678
1.5678
% expr {double(round(100*$total_rate))/100}
1.57
% set total_rate 1.4321
1.4321
% expr {double(round(100*$total_rate))/100}
1.43
puts [format "%.2f" $total_rate]
By using format, we can see the result in output but how to use the same value in the program, i.e., we can see 1.448 as 1.45 in the output but can we use 1.45 in the program then.
It is unclear whether the original question "I need to round a number" really was "I need to print out a rounded-off value of a number". The latter is really best answered with a [format ...], but the former could be interpreted as a need for a number of significant digits, i.e. how to adjust the number itself, and not just to format the printout string. I think the only answer that serves this purpose so far is the elegant one Donal Fellows has provided. However, for "significant digits" instead of "digits after the decimal" I think a small modification is in order: get the number to be between 1 and 10 first (or between 0.1 and 1, if that is your convention), then trim the number of digits after the decimal. Without that, something like roundto(0.00000001234567,4) will get you a zero.
proc tcl::mathfunc::roundto {value sigfigs} {
set pow [expr ($sigfigs-1)-floor(log10($value))]
expr {round(10**$pow*$value)/10.0**$pow}
}
expr roundto(0.000000123456789,5)
produces a value rounded off to 5 significant figures:
1.2346e-7

How could I make this procedure more elegant?

I have a servo I'm controlling that is moving an object closer and closer to a sensor, trying to trigger it.
I want the distance to start at 15.5. However, in each iteration, I want it to decrease the distance .1, until the sensor triggers. For convenience sake, I'd like to exit the while loop with the variable $currentHeight set to this triggering height, so I've placed the decrement line at the beignning of the loop.
But, I've had to hardcode a 15.6 starting point before the while loop so that it will decrement in the first line of the loop to 15.5.
That doesn't seem elegant. Any suggestions on how to spruce this up?
By the way, this is Tcl for all you old school and obscure programmers. ;)
Code:
set currrentDistance 15.6
set sensorStatus 4
while {$sensorStatus == 1)} {
set currentDistance [expr $currentDistance - .1]
moveServo $currentHeight
set sensorStatus [watchSensor 2]
}
I'd use a for loop:
for {set d 155} {$d > 0} {incr d -1} {
set currentDistance [expr {$d * 0.1}]
moveServo $currentHeight
set sensorStatus [watchSensor 2]
# If we've found it, stop searching!
if {$sensorStatus == 1} break
}
This has the advantage of firstly having a limit against physical impossibility (no point in grinding the robot to pieces!) and secondly of doing the iteration with integers. That second point is vital: binary floating point numbers are tricky things, especially when it comes to iterating by 0.1, and Tcl (in common with many other languages) uses IEEE floating point arithmetic internally. The way to avoid those problems is to iterate with integers and have a bit of code to convert to floating point (e.g., by dividing by 10). Think in terms of dealing with counting down in units of 0.1. :-)
One other lesser stylistic point. Put {braces} round expressions as it boosts safety and performance. (The performance boost comes because the runtime knows it can't have weird expression fragments, which are also what would count as unsafe. Not that it is critical in this code because of the dependance on the servo hardware, but it's a good habit to get into.)
I don't know Tcl, but it could look something like this:
set currrentDistance 15.5
set sensorStatus 4
while {true} {
moveServo $currentHeight
set sensorStatus [watchSensor 2]
if {$sensorStatus == 1} then {break};
set currentDistance [expr $currentDistance - .1]
}