(<--Previous) Back to Index (Next-->)

FB n00b: Tutorial 7

Note: This tutorial is for absolute beginners. If you aren't an absolute beginner and you dislike the tutorial, that's too bad. If you are an absolute beginner and there's something you don't understand, read through the tutorial a second time. If you STILL have difficulties, e-mail me (TheMysteriousStrangerFromMars@yahoo.com) and I'll try to help you out. I do assume you at least have a decent knowledge of how to use your computer - if not, there's not much I can do for you. By the way, you'll need FreeBasic to use this tutorial - you download that at freebasic.net, so be sure to download and install it before doing this tutorial. Now start reading!

Numbers

Today we'll be covering numbers, because there are some things that have been neglected in your education. Many programmers are math people, but not everyone is, and even though you've probably studied math in high school, you probably didn't study the kind of math you need to know in order to have a deep understanding of the way your computer works. So today we cover numbers.

Our first subject of discussion is unsigned numbers and how they are stored in the computer. Binary is the way they are stored, as most everybody knows; it is also called base-2, because there are only two values per digit (as opposed to the ten in decimal or base-10: 0-9). A binary digit is called a bit and it is either 0 or 1. Generally your variables need to store more information than that, so you'll usually use several bits per variable (the smallest number most processors can actually handle at a time is 8 - 8 bits make a byte). We need to understand how binary and decimal are related - and indeed, how all number systems are related, since there's more to the story than those two.

In base-10, we start counting with the numbers that can be expressed with a single digit, or as a multiple of 10 to the power of 0 (any number to the power of 0 is 1):

  0*100 = 0
  1*100 = 1
  2*100 = 2
  3*100 = 3
  ...
  9*100 = 9
Eventually we get to the highest value that can be represented, 9, and we start all over, putting a 1 in the next position:
  1*101 + 0*100 = 10
  1*101 + 1*100 = 11
  1*101 + 2*100 = 12
  1*101 + 3*100 = 13
  ...
And when we reach 19, we add another to that. Eventually we get to 99, then we start on ten squared, which is 100. One thing that might help you to understand is if you think of the counter on an automobile or bicycle, the one that tells how far you've travelled. There is a wheel that is moving, with the numbers 0 through 9 on it, and each time it gets back around to 0 the wheel to the right of it moves one notch up. Thus, over time that wheel will eventually traverse the numbers 0 to 9 and arrive back at 0, whereupon the next wheel turns once. This happens for as many wheels there are on the bicycle; eventually it will reset to 0. The mathematical concept of a number, however, allows this process to repeat ad infinitum.

As you can see above, the exponent on the ten starts at 0 and for each digit added on to a number, it is the next exponent up. Thus, for example,

  96321 = 9*104 + 6*103 + 3*102 + 2*101 + 1*100
And since 100 = 1, 101 = 10, 102 = 100, and so on, we see that for each power of ten multiplied by a single digit n, the result is simply n followed by the number of zeroes of the power. Therefore,
  9*104 + 6*103 + 3*102 + 2*101 + 1*100 = 90000 + 6000 + 300 + 20 + 1 = 96321
This is all very important to remember, because you see the same things in binary.

Binary works exactly the same way as decimal, in fact, with the exception that there are only two numbers, 0 and 1, instead of ten, 0-9. Think of the wheels in your counter again: this time, each wheel only has 0 and 1 on it. Obviously, you'll need a lot more to represent very large numbers in binary, and of course they'll turn much faster since they each only have two numbers to traverse through. So here's how we count in binary:

 0, 1
 10, 11
 100, 101, 110, 111
 1000, 1001, 1010, 1011, 1100, 1101, 1110, 1111
You can probably also see, now, that to find out what the value of a binary number is, you may simply multiply it by a power of the base, which in this case is 2 instead of 10. For example,
  01101011 = 0*27 + 1*26 + 1*25 + 0*24 + 1*23 + 0*22 + 1*21 + 1*20 = 26 + 25 + 23 + 21 + 20 = 64 + 32 + 8 + 2 + 1 = 107
By the way, it is important that you learn the powers of two as high as you can remember. In base-10, it's pretty easy to remember: 1, 10, 100, 1000... In base-2 it is the same, actually, but the meaning of those numbers is different: 1=1, 10=2, 100=4, 1000=8, and so on. The powers of two, as high as I can remember them, are 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536. It would help to remember more, but I doubt you will be able to. If you can remember at least the first 8, you can find any others easily: each one is double the one before it.

One final point to make and then we'll go onto other number systems. The highest number you can represent with n decimal digits is simply 9 repeated n times - for example in 5 digits, your biggest number is 99999. This happens to be 106-1. Likewise in binary, the largest number that can be represented in n digits is 1 repeated n times, which happens to be 2n+1-1. In fact, it so happens that the sum of (b-1) times each power of b up to but not including n is always equal to the nth power of b minus 1. In general:

  (b-1)*bn + (b-1)*bn-1 + (b-1)*bn-2 + ... + (b-1)*b1 + (b-1)*b0 = bn+1-1

Other number systems

You may have guessed that base-2 and base-10 are not the only number bases; it's rather obvious, in fact, that you can have any number of bases you wish. There is, indeed, a base-3 (consisting of digits 0-2), a base-4 (consisting of digits 0-3), a base 5 (consisting of digits 0-4), etc. We will mainly be studying base-8 and base-16, but you will come to see that the math is the same in every system, so if you ever need to do some calculation in base-23 you will be able to figure out how to do that.

For bases higher than 10, for example base-16, the digits after 9 are simply the letters. In base 16, your digits are 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, and F. Obviously, A is 10 and F is 15. As usual, you can find the decimal value of a base-16 number by multiplying by powers of 16 - you'll just need to convert the digits over 9 to their decimal counterparts:

  2F3B = 2*163 + 15*162 + 3*161 + 11*160 = 2*4096 + 15*256 + 3*16 + 11*1 = 8192 + 3840 + 48 + 11 = 12091
base-16 may seem somewhat akward, but there's a very good reason it's used. The reason is that each base-16 digit is exactly 4 bits. This means you can convert directly between base-16 and binary:
  0 = 0000
  1 = 0001
  2 = 0010
  3 = 0011
  4 = 0100
  5 = 0101
  6 = 0110
  7 = 0111
  8 = 1000
  9 = 1001
  A = 1010
  B = 1011
  C = 1100
  D = 1101
  E = 1110
  F = 1111
This means we can use hexadecimal as a kind of shorthand. While the computer actually stores numbers in binary, we can abbreviate the values using hexadecimal, because it's easy to convert but can be written using less characters. For example, a 32-bit number in binary (such as 10010101101010111110101011001000) becomes an 8-digit number in hexadecimal (95ABEAC8) which is still long but much shorter and easier to remember.

There are different ways of distinguishing between the number systems. In C, you'd precede a hexadecimal number by 0x, while octal is simply preceded by a 0 (in other words, 0345 is not the same as 345!). In assembly, we commonly place the letter "h", "o", or "b" behind a number to show which number system it comes from. And in FreeBasic you can use &h, &o, or &b before the number. In every case, the number is converted to it's actual binary equivalent (since everything is actually stored in binary internally). If you want to know what a number looks like in binary, hexadecimal, or octal, you can use Hex(), Oct(), or Bin() to tell you. They return a string, in the same way the function Str() does, in the specified number system. This may be helpful if you're playing around with number systems, or you just want to know what something is. You can, of course, use the Windows Calculator program to do this also, but if you need it in a program it might help.

base-8, or octal, which I've already mentioned once in the preceding paragraph, is sometimes used as well. I won't go into the details, since it's the same as all the other bases: the main thing to remember is that base-8 digits range from 0 to 7. You should always be careful, as you might be tempted to think the highest digit is 8. Learn to remember that because we count 0, it's always one less. In octal, 8 is represented by 10 (and the number before 10 is 7!)

Now we come to arithmetic. As usual, it's the same in all bases. When you add, you line the two numbers up and add the corresponding digits; if the number is longer than one digit, you put down the first digit and add the second digit in with the next column. This is called a carry operation. Very simple - in binary, for example:

  10110
 +01101
      1

  10110
 +01101
     11

   1
  10110
 +01101
    011

  1
  10110
 +01101
   0011

 1
  10110
 +01101
  00011

100011
I'll leave it to you to figure out the other number systems. It's the same everywhere; in hexadecimal, of course, you simply need to remember to convert the letters into numbers mentally when you do the operations.

Subtraction, of course, is also the same in all number systems. If the number on top is not big enough to subtract the number below from it, you have to take one from the next column over. So, for example,

  101
 -010
    1

  101
 -010
   11
And in hexadecimal:
  23
 -0F
  14
Where you would normally subtract the digits separately, you must subtract F from 13 and leave 1 behind. Of course, if the entire number on top is larger than that on bottom, it's easiest to swap the two numbers, then do the subtraction but negate the result.

Multiplication is the same, as well. Simply multiply each digit in one number by each digit in the other, taking care to shift the result over by appending 0s.

Aside form arithmetic, the binary number system also has some other interesting operations defined for it. These are called boolean operations and they take one or two inputs and give one output. These are usually studied in high school geometry, but if you haven't studied them that's fine: we study them here, too.

  And______
  In    Out
  A B   C
  0 0   0
  0 1   0
  1 0   0
  1 1   1

  Or_______
  In    Out
  A B   C
  0 0   0
  0 1   1
  1 0   1
  1 1   1

  Xor______
  In    Out
  A B   C
  0 0   0
  0 1   1
  1 0   1
  1 1   0

  Not____
  In  Out
  A   B
  0   1
  1   0
The meanings of And and Or are pretty intuitive:
  And :- If one bit AND the other are 1, return 1
  Or  :-  If one bit OR the other is 1, return 1
Xor (eXclusive OR) is the same as Or, with the exception that if both bits are 1 then the output is 0. Not, of course, is simply the inversion: the output is whatever the input is Not. With these elementary operations, many others may be formed. For example,
  Not (Xor A, B)
  In         Out
  A B        C
  0 0        1
  0 1        0
  1 0        0
  1 1        1
This is sometimes called equivalent, or Eqv, because it returns 1 only if the inputs are the same or equal.

There are some useful properties of the boolean operations to remember; you can verify all of these for yourself:

  A Xor B = B Xor A
  A And B = B And A
  A Or B = B Or A

  A Xor 0 = 0 Xor A = A
  A Xor 1 = 1 Xor A = Not A
  A = Not(Not A)
  Not A = Not(Not(Not A)))

  Not(A Or B) = (Not A) And (Not B)
  Not(A And B) = (Not A) Or (Not B)
where A and B are single bits. In FreeBasic and most other programming languages, the boolean operations are usually extended to all bits in a variable, so to Not a byte is to invert each bit in that byte separately. When this is done, the above properties do not change except for the property A Xor 1 = 1 Xor A = Not A. This property is only true if every bit in the variable is 1. For a byte, this means A Xor FF = FF Xor A = Not A (FF being the hexadecimal equivalent of 11111111). All this may remind you of tutorial 2 and conditionals. Now you see precisely how they work.

Incidentally, Xor is addition in modulus-2 and And is multiplication in modulus-2 - we'll discuss the meaning of that later, but keep it in mind.

You can see the interesting effect of inversion in hexadecimal:

  Not 0 = F
  Not 1 = E
  Not 2 = D
  ...
  Not D = 2
  Not E = 1
  Not F = 0
This is an interesting result that could be useful in some ways.

In decimal, by the way, the inversion operation can be performed to work similarly as in hexadecimal. It doesn't do quite the same thing, but it produces results that are sometimes useful. You use a lower and upper bound to invert a number n; n must be between the lower and upper bound, and the result will be as well:

  A' = (lbound - A) + ubound
In most cases, the lower bound is simply 0; thus, you use
  A' = (0-A)+ubound
  A' = -A + ubound
  
  A' = ubound-A
This is sometimes useful, for example in graphics. I won't show you graphics yet, but I'll tell you now that the y-coordinate is flipped from the usual: the top is 0 and the bottom is the upper bound. This formula can be used to flip it, in case you want to have it more naturally (it's not required, but it's a possibility).

What about negative numbers?

Now we arrive at signed numbers and modulus. Obviously when you have a signed number you need to have a way of knowing whether it's positive or negative. You could just treat one of the bits as a sign bit so if that bit is 0 the number is positive but if it's 1 the number is negative. However, this would create a slight amount of waste in that it would be possible to create a negative 0 - and obviously, -0 is no different from +0 so that's a waste. The solution, then, is to NOT the positive number and add 1. Thus, -1 = NOT(1) + 1, and -2 = NOT(2) + 2. Of course, the actual number you get will vary depending on how many bits you are using. In an eight bit number, for example, we get:

-1 = NOT(1) + 1 = NOT(00000001) + 1 = (11111110) + 1 = 11111111 = &hff = 255
-2 = NOT(2) + 1 = NOT(00000010) + 1 = (11111101) + 1 = 11111110 = &hfe = 254

If you go through the numbers, you'll see that -1 = 255, -2 = 254, -3 = 253, and so on. In other words, a negative number is really just equal to its absolute value subtracted from &hff (of course this is different if it's more than a byte) plus one. To generalize this, you can figure out what -x is (where x is a positive integer) with the formula

-x = ((2n) - x)

Where n is the number of bits in the number. Thus, for example, in a 32-bit number you have

-1 = 232-1 = 100000000-1 = FFFFFFFF

Which as you know from our previous discussion is true.

One benefit of this method of storing negative numbers (the method is known as two's complement notation) is that they can be added and subtracted properly. That is, -3+2 = -1 in two's complement notation. I'll demonstrate in 4 bits, for simplicity; you can easily verify it in more bits.

-3+2 = 1101 + 0010 = 1111 = -1

This makes it very easy to perform subtraction, since instead of creating new circuits for subtraction in the processor, you can simply complement the number being subtracted, add 1, then add it to the other number. Thus, instead of 2-3 we would just write 2 + (-3). Of course, there are cases when the numbers involved are too large and will overflow. For example, in an 8-bit signed number the largest number you can store is 01111111 = 127, while the smallest number you can store is 10000000 = -128. So what would happen if you subtract 1 from -128?

Because of the way the math works, you get 127! This occurs when you don't have enough bits to store a number. Fortunately, this doesn't happen terribly often, especially since FreeBasic lets you use numbers up to 32-bits long (either signed or unsigned), but it is something to remember. First, only use signed numbers if you need them. When you declare an Integer, use uInteger if your number will never be negative. For 16-bit numbers, it's Short and uShort, and of course for bytes it's Byte and uByte. If you use unsigned numbers they can go twice as large, though of course you won't be able to use negative numbers. If there's any danger of a number getting too big (or too small) after a calculation, you should check it to be certain it doesn't.

Right, so what about modulus? What is it? Modulus is a different kind of number system. In our number system, the numbers proceed in a straight line in both directions, off into infinity. But modulus number systems are not like that - modulus number systems are like a circle. In fact, if you look at the clock, you're seeing a modulus system. The time on the clock doesn't get bigger and bigger forever... eventually, it loops back to 1. Generally, modulus systems loop back to 0, but the concept is the same. This is applicable in computers because as I said before, numbers wrap around (since they can't just march on to infinitely large amounts). Thus, in a byte &hff + 1 = 0, and &hff + 2 = 1. In modulus arithmetic, you get a periodic function, like a sine wave, so that &hff + &hff + &hff + ... + 1 = &hff + 1 = 0. Now other than it's curiosity, how is this useful to us?

As it turns out, modulus is the same as the remainder of division. Thus, a MOD b is the number a expressed in Modulus system b, but it's also the remainder of the division of a / b. The only time this is not true is when either a or b is negative - then you should use the formula a-b*Int(a/b) to get the mathematical modulus (that is, a expressed in Modulus system b). But if both are positive, a Mod b is the remainder as well as the Modulus version.

Base Conversion

This knowledge enables us to explore an important issue: converting from decimal to another base. Now you know how to convert from another base to decimal - simply multiply each digit by the base to the power of the place of that digit. We've been through that already. But how do you convert from decimal to another base? How, for example, do you convert the number 23 to binary?

The answer lies in the remainder. You must divide by the base you're converting to, and each digit in the result is the remainder, while the quotient is again divided by the base and so on. So, for example, to convert a 23 to binary, we use the following code.

  'Takes an unsigned integer (this will NOT work for signed numbers) and returns a string
  Function dec2bin (dec_num As uInteger) As String
  Dim As Integer q, r
  Dim As String result_string
    'Set the first quotient to the number itself
    q = dec_num
    Do
      'First get the remainder...
      r = q Mod 2
      '...then do an integer division by 2, since we're converting to base 2.
      q = q \ 2
      result_string = Str(r) + result_string
    'Continue until we're down to nothing
    Loop Until q <= 0
    Return result_string
  End Function

  Print Str(23)
  Print dec2bin(23)

  Sleep
  End

This of course does just the same thing as Bin(), but it's important to understand how it works. Basically, each bit has a meaning - it is the value of that bit multiplied by 2 to the power of the position of the bit. Essentially, we can call each of the powers of two a possible "component" of our number - if we can subtract that component from the number and have a number greater than or equal to 0, then that component must be part of the number. For example, the number 23 is made up of the components 16, 4, 2, and 1. Those are the powers of two that add up to 23. You can easily see that any number can be created simply by adding powers of two; this is how binary works (in decimal, it's a little more complicated - any number can be created by adding multiples of powers of 10. This is actually the case in binary, but since the only multiples availible are 0 and 1, we just say we add the powers as they are). In fact, the above algorithm becomes greatly simplified if you're using a calculator (it's better to use the above form in programming, though):

  1.  If your number is a power of two (1, 2, 4, 8, 16, 32, 64, 128, 256, 512... etc.) then you're done,
       as you only need to set the bit representing that power of two.  If it's not a power of two,
       continue with Step #2.
  2.  Find the power of two that is larger than your number plus one.  For example, if your
       number is 23 you need the power of two that is larger than 23+1 = 24, and this power of two
       will be 32.  If your number is equal to one less than the power of 2, then set all the bits below
       that to 1 and quit.  For example, if your number is 31 find the bit that corresponds to 32,
       then set all the bits below that to 1 (but don't set the bit that represents 32).
  3.  Since you know the power of two that is greater than your number plus one, you know that
       that power of two and all above it are not components of the number (since you couldn't
       possibly subtract them from the number and get a positive result).  Therefore, start with
       the next power below it.
  4.  For each power of two starting with the one you figured in Steps #3 and 4 (the one that is
       one less than the power of two higher than the number plus one), try to subtract it from the
       number.  If you get a positive result, it's part of the number so that particular bit is 1.  If it's
       less than 0, then it's not part of the number so that bit must be 0.

For example:
  23:
  1.      Our number is NOT a power of two.
  2.      The smallest power of two greater than 24 is 32.
  3.      Since 32 is larger than 24, we know it's not part of the number.
  4(a).  We can subtract 16 from 23;  the result is 7.  Thus, we put down a 1.
  4(b).  We can't subtract 8 from 7;  thus, we put down a 0.
  4(c).  We can subtract 4 from 7;  the result is 3.  Thus, we put down a 1.
  4(d).  We can subtract 2 from 3;  the result is 1.  Thus, we put down a 1.
  4(e).  We can subtract 1 from 1;  the result is 0.  Thus, we put down a 1 and we're done.
  The result is 10111.

Another example:
  56:
  1.      Our number is NOT a power of two.
  2.      The smallest power of two greater than 57 is 64.
  3.      Since 64 is larger than 57, we know it's not part of the number.
  4(a).  We can subtract 32 from 56;  the result is 24.  Thus, we put down a 1.
  4(b).  We can subtract 16 from 24;  the result is 8.  Thus, we put down a 1.
  4(c).  We can subtract 8 from 8;  the result is 0.  Thus, we put down a 1.
  4(d).  We can't subtract 4, 2, or 1 from 0, so we put down a 0 for each of those.
  The result is 111000.

This method is the easiest to use with a calculator (unless you're using a scientific calculator which will do the conversion for you), but it's probably easiest to use the other method when using the computer.

You can understand the algorithm method as being somewhat the opposite of this method, in that we find the binary digits in reverse order. If you think about it, the first thing we do is check if it's even or odd. If it's even, then there is no 1 component - if odd, there is (this is because all other powers of two are even, but 20 is 1, odd). After that, the result is simply the same thing but with that component removed. Each time we check if the number is even or odd, removing the components we've checked from the list. It's really more complicated than that, but that's an equally valid explanation for powers of 2. What this won't explain (and I won't try to explain) is why this works with any other number base as well.

That's right, you can convert to hexadecimal or octal in the same way - only dividing by 8 or 16, of course. This is also how you convert a number to a decimal string (that is, it's how Str() works) - dividing by 10. I'll leave it to you to figure out how it works, but I will show you hexadecimal since it's a bit different. For any number that exceeds 10, the number must be converted to an alphabetic character manually. For this we use two special functions I haven't introduced to you yet - Chr() and Asc().

Like everything else, strings are stored as numbers, using a special system known as ASCII. So what if you want to know what the number that represents a certain letter is? Then you must use Asc() to figure it out. For example, Asc("A") gives you the ASCII value for the capital letter A. Chr() works conversely it gives you the letter for a given number. It's really something like converting a byte into a string, and of course in reality the string is stored as a number so it's exactly the same as the byte. Anyways, to convert a digit greater than 10 to its proper alphabetic digit, we must subtract 10 from the digit, add the result to the ASCII value of "A", and convert the whole thing back to a character using Chr(). This procedure will work for ANY number system, even systems greater than base-16. Of course, if you get too high eventually your digits will go past Z and then you'll have weird characters on the screen, but if you need to convert to a particular base you use the same code except divide by a different number:

  'Takes an unsigned integer (this will NOT work for signed numbers) and returns a string
  Function dec2hex (dec_num As uInteger) As String
  Dim As Integer q, r
  Dim As Byte t
  Dim As String result_string
    'Set the first quotient to the number itself
    q = dec_num
    Do
      'First get the remainder...
      r = q Mod 16
      '...then do an integer division by 16, since we're converting to base 16.
      q = q \ 16

      'If our digit is greater than 9
      If r >= 10 Then
        'Find the proper value for it - by the way, if you want lower-case characters just
        'change the capital "A" to "a".
        t = Asc("A") + (r-10)
      Else
        'Otherwise, just convert the numerical digit to a string and then convert that to ASCII
        t = Asc(Str(r))
      End If

      'And we simply add the character to the front of the result string.
      result_string = Chr(t) + result_string
    'Continue until we're down to nothing
    Loop Until q <= 0
    Return result_string
  End Function

  Print Str(23)
  Print Hex(23)
  'They're both the same thing!
  Print dec2hex(23)

  Sleep

The result will be 17, which if you check your calculator you'll find is exactly correct. Our special trick for binary (subtracting possible components) won't work here, since for each digit you have 16 possible combinations instead of 1. Thus, this is how you would convert between any bases other than binary on your own:

  1.  Figure out the possible range for each digit.  For hexadecimal this is 0-F;  for octal, 0-7.
       All bases the range is from 0 to the base minus one.  Obviously, if your number is within
       the proper range, you don't need to do any special conversions.
  2.  If your number is not within the proper range, start dividing!  Use integer division to get
       the result and remainder;  put the remainder on the front of the result string and repeat
       using the result.  (By the way, if your calculator does not support integer divisions, it's not
       hard to do it on your own.  Do a regular division.  The part before the decimal point is the
       integer division;  the part afterwards should be multiplied by whatever you divided by and
       that's the remainder.  For example, 211 / 3 = 70.33333;  70 is the division result while
       1/3 is multiplied by 3 and thus we get 1, which is the remainder.)

For example:
23:
  1.      Our number is NOT between 0 and 15 (which is F).
  2(a).  Dividing 23 by 16, we get 1.4375.  1 is the divided part, and 16*0.4375 = 7 is the
           remainder.  Thus, put down a 7 and try again.
  2(b).  Dividing 1 by 16, we get 0.0625, so 0 is the divided part and 16*0.0625 = 1 is the
            remainder.  Since the divided part is 0, we're done.
  The result is 17.

This is of course just the same as the algorithm, only human-usable. As you learn to program, you'll need to learn more and more to be able to convert between the human form and the computer form of an algorithm. In fact, it's virtually impossible to write any really complicated program without this ability. Of course, some algorithms are hard for a human to use (for example, those which use a lot of memory - humans don't have a lot of memory, so it isn't feasible to directly simulate that sort of algorithm with your brain alone) - which is why another very important skill is the ability to simplify an algorithm, figure out an equivalent algorithm that does something similar in the same way, but with less requirements. This greatly simplifies debugging - if you know the simplest form of a complicated piece of code, you only need to find the bug in the simple form and you'll spot it in the longer piece.

Now you have a basic understanding of much of the math used at the low-level. Much of this stuff is hidden from you as a programmer, so you wouldn't need it, but that doesn't mean you shouldn't bother to learn or understand it. Following is a very short section on how floating point numbers work. I won't go into the nitty-gritty details; but I will teach you the most important concepts to keep in mind.

Floating Point notation and other systems of storing numbers

Floating point numbers work a bit differently than you might expect. If you recall scientific notation, it will help you a great deal in understanding them. Floating point numbers work similarly to scientific notation, except that you multiply by a power of two instead of ten. You generally have a sign bit, a mantissa (which is the thing you multiply), and an exponent. They are all packed into a number either 32, 64, or 80 bits long (Single is 32 bits, Double is 64 bits). Your formula is something like this:

myNumber = mantissa * 2exponent
Floating point numbers can store many ranges of numbers, from very small numbers (exponent is negative) to very large numbers (exponent is positive). The magnitude of the number is the exponent; the actual value of the number is determined by the mantissa.

The actual standard is a little more complicated, because just like with integers certain waste may apply, such as the ability to store negative zeroes, or two identical numbers differently. The standard seeks to address some of these problems to make the system more efficient; in general, however, this is approximately how it works. What you need to understand about this system is not so much the way it works, but what it's limitations are. You need to understand that although your numbers can be very huge in magnitude or very small in magnitude, the actual numerical information stored is not always the most accurate. That's because the exponent stores only the magnitude, which can range, but the mantissa, which has the actual information, is somewhat limited. Thus, you may store 1000000000000 or 0.00000000001, and this is a very good range, but you when you do so you may not be able to know the difference between 0.00000000001 and 0.00000000002, or between 1000000000000 and 1000000000001. See, when you use this method all the information in the mantissa affects the parts of the number furthest away from the decimal point. For the most part this is OK - and of course medium magnitude numbers like 25 and -16 are stored just fine. However, it's important to remember that extremely large or extremely small magnitude numbers lose their accuracy, in exchange for precision.

If you'd like to understand more about floating point numbers, you may look at the page http://docs.sun.com/source/806-3568/ncg_goldberg.html.

As a matter of fact, floating point numbers should never be used in situations where peoples lives or well-being would be affected by errors (for example banks keeping track of money). So what are the alternatives? Well, there are a number of alternatives. For banks keeping track of money, it might be best to store money in terms of cents (the lowest unit of money), but it may be wiser to use even lower units to keep track of interest properly. When possible, integers should be used; if it makes it easier, more than one variable might be used:

  Type moolah
    As Integer hundreds, dollars, cents, hundredthcents
  End Type
Other methods may also be used; one method often used is known as fixed point. In this system, numbers are stored just as integers, but an invisible (and implied) point is placed somewhere. For example, an 16.16 Fixed point notation might be used, where the decimal point is precisely in the middle of a 32-bit number. As you can imagine, the magnitude of the numbers that may be stored won't change, but you do get a fair magnitude. Of course, you may also use a 8.24 or 24.8 system, or any other conceivable system, depending on how much precision you need in either direction. This method is also fast, because for the most part integer math is used. Of course, you can easily figure out what a floating point number represents just using multiplication or division by powers of two - which essentially amount to shifts left or right (using Shl or Shr). For example, the value of the number
1111111111111111.1111111111111111
is ascertained first by shifting all the way to the right by 16 bits:
n = n Shr 16
which gives us the Integer portion,
1111111111111111 (=65535)
and then to the left by 16 bits, which will give us the reciprocal of the fractional part:
1111111111111111 (=65535, therefore the fractional portion is 1/65535)
Of course the amount you shift depends on where you fix the point.

An alternate way to store integers, Binary Coded Decimal or BCD, is mostly obsolete now but is worth a mention. It's very easy to convert BCD numbers to strings in decimal notation, because BCD numbers work by storing each digit of the decimal as if in hexadecimal. For example, the number 23 is 17 in hexadecimal, but under the BCD system it is stored as 23. This works since all decimal digits may be stored in hexadecimal, but there is some inherent waste since the digits A-F are never used and thus a one-byte BCD number can only hold up to 100 unique and valid values, while a one-byte hex number holds 256. Still, it's supported by the processor (there are commands in assembly language for working with BCD numbers) and it's easier to convert BCD numbers to strings (you don't have to use that divison algorithm I introduced above) - especially when unpacked BCD is used, which would store 23 as 0203 instead of 23 - but of course wastes even more space in the process. Generally you don't need to use BCD in an HLL like FreeBasic, because all the details like converting decimals to strings are taken care of for you, but it is worth mentioning anyways.

Wrapping up quickly

I probably could have put a lot more in here, but as it is this is by far the longest tutorial in the series yet, so I guess I'll give you a break. The next tutorial is about pointers, but look forward to a rather round-about introduction as I give you a tour of the internals of your system and how it all relates to pointers. Expect to learn a lot!

The eighth tutorial is here

(<--Previous) Back to Index (Next-->)