Introduction

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.

Contents

Floating Point Math Basics

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.

Creating Floating Point Numbers

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

Limitations in Creating, Printing, and Converting Floating Point Numbers

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.

Basic Arithmetic Using Floating Point 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.

Advanced Functions Using Floating Point Numbers

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")

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

Special Math Functions Using Floats or Integers

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.

When To Use Floating Point Numbers

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:

  1. Math on floating point numbers is slower than on integers integers. Every time you operate on a float, pbLua has to create a new userdata, and point it at the correct metatable. Then the actual calculation gets done, which is slow because the ARM7 micro in the NXT does not have native floating point operation.
  2. Floating point numbers each take up about 22 bytes, while integers only use 8 bytes. This is not a problem until you start storing lots of floats in an array, for example.
  3. Floating point numbers have limited precision, and your results will have rounding errors. More to the point, it's always a bad idea to compare floats for equality. You can only ever get close to an exact value. Experienced programmers usually solve this problem by testing to see if values are within a small tolerance of each other.

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 " )
10,000 iterations in    3026     milliseconds 
> print( "c is ", c )
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 " )
10,000 iterations in    313      milliseconds 
> print( "c is ", c )
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.

Summary

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"