This tutorial describes how to use the floating point math capabilities of pbLua. In most cases, the 32-bit integer math capabilities of pbLua will be good enough, but there are times when you'll want to use more complex math for your robots.
Examples of more complicated math include trajectory calculations, positioning based on angle and so on. The pbLua system provides almost all of the math functions you are likely to need.
For detailed information pbLua's math capabilities, refer to the pbLua Float Math API.
As a reminder, there is an excellent online version of Programming in Lua that is a complete description of Lua along with many programming examples, and an even better idea is to purchase the latest Programming in Lua book .
By the end of this tutorial, you'll be using the Float Math API to calculate trig functions, take square roots, and many other functions.
Just as the File System tutorial described the differences between the pbLua file system and a typical file system, this tutorial needs to lay out a bit of background on numbers types and how they are interpreted by pbLua.
Programmers that are familiar with standard Lua and some other languages may get tripped up by the fact that floating point numbers are a special data type in pbLua.
Because of the limited processing power in the ARM7 microcontroller that powers the NXT, the basic numerical data type is signed 32-bit integers. Floating point math takes a lot more time on a processor that does not have the special floating point instructions or a coprocessor. These numbers are therefore separate and must be created on demand by the user.
Floating point numbers are implemented using a really powerful concept in the Lua language called userdata. Basically these are data that can have metatables attached to them so that certain operations (like basic arithmetic) can be done implicitly, and so that their type can be checked.
Whoa! Did we lose you in that last paragraph? Don't worry if we did because we'll explain it again later. For now, all you need to remember is that floating point numbers are not like regular numbers in pbLua - the interpreter cannot parse them automatically, you'll have to create them yourself.
If floating point numbers have to be created, then how do we do it? First, let's see what doesn't work....
> f=123.456 tty: stdin:1: malformed number near '123.456'
This cannot work because the pbLua parser/compiler (the program that reads text and converts it into machine instructions) does not understand numbers with decimal points in them. It's as simple as that.
We could get into a long and involved discussion about why this is the way it must be, but we'd get back to the same place we are now and you still would not be able to make a floating point number.
Let's have a close look at each of these examples to see what's going on. Keep in mind that floating point numbers are actually approximations to a number. The more bits you have available for your floating point representation, the closer the approximation is. The pbLua implementation uses the IEEE 754 32-bit representation.
With 32-bit floats, you'll only get about 6 digits of precision, so there's not much point in specifying the fractional part of a number to more than one part in 1,000,000 - which explains this:
> =nxt.float(123, 456) 123.000457
When pbLua executes nxt.float(123, 456) it takes the first value as the integer part of the value (the left side of the decimal point) and the then takes the second value and divides it by 1,000,000 before adding it the the first part.
This example shows how to make the number 123.456:
> =nxt.float(123, 456000) 123.456001
Negative numbers can trip you up, be careful! Remember that the two parameters are added together to get the final floating point number, so mixing a negative integer part and positive fraction will give the wrong answer:
> =nxt.float(-123, 456000) -122.543998
To make a negative number, both the integer and fractional parts have to be negative:
> =nxt.float(-123, -456000) -123.456001
Or you can do it the easy (preferred) way and just put a "-" sign in front of the nxt.float() function like this:
> =-nxt.float(123, 456000) -123.456001
We can also take advantage of the Lua language's flexibility with the number of parameters that you supply to a function. If you do not supply the second parameter, it is assumed to be zero, like this:
> =nxt.float(123) 123.000000
And yes (if you're wondering) you can get away with no parameters and pbLua will still give you a float number initialized to 0.000000 like so:
> =nxt.float() 0.000000
It is, however, possible to get confusing results with the nxt.float() function, as shown in the next example. In this case we're trying to use more than 6 significant digits - can you see why?
> =nxt.float(32456,9) 32456.000000
An easy technique to determine how many significant digits a floating point number will have is to write out the expected result, which is 32456.000009 for this example. Remember, the second parameter gets divided by 1,000,000 before being added to the first parameter. Now, starting from the first digit on the left hand side, count 6 digits to the right. That's where your floating point precision disappears, so the previous example is correct!
There is one additional, advanced technique that you might use for, say, parsing GPS data where the floating point numbers come in as strings. Just pass the string value to nxt.float() and it will figure out what you are trying to do:
> =nxt.float("08056.2984")
8056.298339
Once again, note that the floating point value is only an approximation of what we passed in, which was a text string. You might be asking yourself why the value is off in the 4th digit after the decimal place if we have 6 digits of precision.
As in the previous example, just count from the first digit on the left. You'll see that the number is in fact accurate to about 7 or 8 digits in this example.
We've gone over how to create floating point numbers, but what if we want to go back to use the floating point results in functions that require integers, like the pbLua Output API? In this case, we'll want to use the nxt.int() function, which has an important limitation that we'll find out about in a minute...
> f=nxt.float("08056.2984")
> =nxt.int(f)
8056 298339
Finally, we'll mention that the nxt.float() function has some limitations when you pass in integers to build up numbers. It's fine for the normal range of values that you're likely to use in robotics, but you should be aware that the absolute value of integer portions larger than 2,147,483,648 will give you trouble. Fractional parts larger than 1,000,000 will also cause problems, and of course you can't specify anything smaller than 0.000001 using this technique.
Yes, there's a way around it. You simply supply the string in what's called scientific notation to get really big or really small numbers, like this:
> =nxt.float("123.45E17")
1.234496E19
> =nxt.float("123.45e-10")
1.234500E-8
You'll see that for very large and very small numbers that pbLua prints them using what's called normalized scientific notation. This includes values with an absolute value outside the range of 2147483648.0 and 0.001 - all other numbers are printed without the exponent.
Rest assured that your floating point values are stored the same way no matter how they are printed.
Another limitation is that floating point numbers can't be printed directly using Lua's string.format() function - you have to print the value to a string first.
The final limitation is on converting a floating point number back to an integer. For the same reason that the nxt.float() function only accepts a limited (but still quite large) range of integers to build a float, the nxt.int() function can only return the same range of integers when converting from a float.
In most real-world cases this will not cause a problem, but you always want to be careful when switching between float and integer numbers.
Now that you know just about everything there is to know about creating floating point numbers, let's use them! Remember that I mentioned that these numbers are implemented using a very powerful Lua's construct called userdata? Well, now we're going to find out what this means for basic arithmetic.
Userdata objects can have what's called a metatable attached to them, and this metatable can be used to hold functions that operate on the userdata. The Lua language allows the programmer to override some of the basic operations withing the language, such as "+", "-", "*", and "/". These are the basic arithmetic operators.
Here's an example of pbLua doing some basic math on standard numbers, which are integers in this implementation.
> =1+2 3
pbLua also has the fortunate property of automatically converting strings to numbers if it can.
> ="11"+2 13
But if the string cannot be converted to a pbLua number, or if the value is not understood by the pbLua compiler, you'll get an error message:
> =1+4.567 tty: stdin:1: malformed number near '4.567' > ="123.45"+98 tty: stdin:1: attempt to perform arithmetic on a string value
Here's the magic part! The pbLua compiler knows when either the left or right operand of a basic arithmetic operation is a float userdata. In this case, it forces the values on both sides of the operation to be floats, does the operation, and returns the float value again, like so:
> f=nxt.float(1) > =f 1.000000 > =9+f 10.000000
Of course, this works for all 4 basic operations, not just addition:
f=nxt.float(3) g=nxt.float(4) =f+g 7.000000 =f-g -1.000000 =f*g 12.000000 =f/g 0.750000
Now that you've got an appreciation for how simple the basics are with pbLua's floating point numbers, let's move on to where the real fun starts, which is advanced functions.
There are times when you might need to do some calculations with floating point numbers that are simply too awkward or inaccurate when using integers. These are the times you need floating point functions, and pbLua has a full slate of them. They are all described in detail in the pbLua Float Math API.
There is a subtle difference in the way that the C library and pbLua handle the unit of measurement for angles. The C library uses radians to measure angles and its trig functions expect their parameters to be in radians.
In the course of designing pbLua, I determined that most students would find radians confusing, so pbLua uses degrees as the unit of measurement for angles. This is also useful since the motors send back one pulse for every degree of rotation.
Here are some examples of the use of the nxt.sin() function:
f=nxt.float(45,0)
=f
45.000000
f=nxt.float("45.0")
=f
45.000000
=nxt.sin(f)
0.707106
=nxt.sin(45)
0.707106
=nxt.sin("45")
0.707106
Notice how many different ways there are to represent floating point numbers and how they can be passed to these functions. This is where the flexibility of a dynamic language like pbLua really comes through.
Here are the rest of the floating point math functions that are available in pbLua
| Function | Description |
| nxt.pi() | constant pi |
| nxt.sin(x) | sin of x |
| nxt.asin(x) | inverse sin of x |
| nxt.cos(x) | cosine of x |
| nxt.acos(x) | inverse cosine of x |
| nxt.tan(x) | tangent of x |
| nxt.atan(x) | inverse tangent of x |
| nxt.atan2(x,y) | inverse tangent of x/y (for very small angles) |
| nxt.ceil(x) | first integer greater than x |
| nxt.floor(x) | first integer smaller than x |
| nxt.exp(x) | e to the power of x |
| nxt.log(x) | base e logarithm of x |
| nxt.log10(x) | base 10 logarithm of x |
| nxt.pow(x,y) | x to the power of y |
| nxt.sqrt(x) | square root of x |
There are some math operations that fall between floating point and integer math and pbLua treats them in a special way. Normally the floating point functions return floating point values even when the parameters are integers, since that often makes the most sense.
But there are some times when you want the result of a function to be the exact same type as the input. The following functions operate like this:
| Function | Description |
| nxt.abs(x) | absolute value of x |
| nxt.sign(x) | sign of x |
| nxt.min(x,y) | minimum of x and y |
| nxt.max(x,y) | maximum of x and y |
Here's an example of how this works using the nxt.max() function. Notice how the value returned is the correct type:
f = nxt.float(49) g = nxt.float(234) =nxt.max(f,g) 234.000000 =nxt.min(f,g) 49.000000 f=49 g=234 =nxt.max(f,g) 234 =nxt.min(f,g) 49
Really, there's not much more to demonstrate as far as using floating point numbers goes. But we will cover one last topic before we close out this tutorial.
For all their wonderful benefits, floating point numbers in pbLua have a couple of issues that you need to keep in mind when you use them in your program:
If floating point numbers have these drawbacks, then what are some of the ways we can use integers to do math faster? There are many well known techniques, but I'm going to show you one of my favorites to get you started.
Let's say you want to figure out the circumference of a wheel, and you know the diameter. The tires that come with the NXT kit are 56x30, which means they have a diameter of 56mm. Using floating point math, you'll probably end up doing something like this to calculate the circumference:
d = 56 =nxt.pi()*d 175.929199
This works, and if you want to round the result, the answer is closer to 176 than 175. The next example shows a little benchmark framework I often use to determine how fast things are:
d = 56 t = nxt.TimerRead() for i=1,10000 do c = nxt.pi()*d end print( "10,000 iterations in ", nxt.TimerRead()-t, " milliseconds " ) print( "c is ", c ) 10,000 iterations in 3426 milliseconds c is 175.929199
In this case, we do 10,000 iterations of the main calculation in a little over 3 seconds. Not bad, but can we do better with just integers? More importantly, can we get accurate answers?
It turns out that there's a fixed point approximation to many irrational numbers, such as pi and e that can be calculated using continued fractions. Don't worry, even though the math is tedious, the results are surprisingly useful.
The continued fraction technique has been used to produce an approximation to pi that is accurate to about 6 digits, and it's a simple division of integers (355/113). But of course, if you naively apply this in your calculations, you'll get the wrong result.
=nxt.float(355)/nxt.float(113) 3.141592 =nxt.pi() 3.141592 =355/113 3
Of course, there's a way around this. If you change the order of operations and multiply the diameter by 355 and then divide the result by 113, you'll get a much better answer, and it's way faster:
d = 56 t = nxt.TimerRead() for i=1,10000 do c = (d*355)/113 end print( "10,000 iterations in ", nxt.TimerRead()-t, " milliseconds " ) print( "c is ", c ) 10,000 iterations in 304 milliseconds c is 175
About 10 times faster in fact. The answer is within 1 mm or the "true" result, so it's probably good enough for what we're doing here.
This tutorial should provide a really good reference on how and when to use floating point numbers. Come back and refer to it when you're not sure why you're getting "wrong" results. It might be that they are the results you have asked for.
Also keep in mind that in many cases it's not necessary to use floating point numbers. It's often possible to use integers to speed things up and get answers that are "close enough"