Numbers in LB

Numbers in Liberty BASIC

(all you ever wanted to know about numbers but did not know whom to ask ;) )
by - tsh73 tsh73, January 2012
Note to author: if you need to edit this page, let us know. It has been systematically vandalised, so I'm locking it.

Liberty BASIC has only two data types: string and numbers. So if you have any numerical result it entitled to be "just a number". However, "under the hood" numbers in any programming language are a bit more complicated. Liberty BASIC is not an exception – and has complications (and unique abilities) of its own. To get most out of Liberty BASIC, you must understand what’s going on.

Numbers could be integers (AKA whole numbers) – without decimal point – and real numbers (AKA floating point numbers). Since Liberty BASIC has no Boolean (logical, yes/no true/false) datatype, numbers are used for this purpose as well.

Integer numbers

Whole numbers like 0, 1, 123, -7 are integers.

Combining integers with + - * MOD operators produces a result that is also an integer. Obviously, result of a division could end up as a non-integer (having fractional part). It is less obvious that power operators on integers could produce non-integer results: for example, 2^(-1) is actually 0.5.

Liberty BASIC has no "integer division" operator, but it could be done as INT(a/b), taking only the integer part of result.

Long integers (aka Arbitrary length integers)

Now, there is a somewhat unique strong point in Liberty BASIC: in most of languages, the size of an integer is determined by the underlying computer architecture.

QBasic's integer type is stored in 2 bytes, and hence limited to +/- 2^15, from -32,768 to 32,767.

In contemporary C, an integer is stored in 4 bytes, so it is limited to +/-2^31, from -2,147,483,648 to 2,147,483,647.

To get all 158 digits of 100! (factorial of 100, defined as 1*2*3*...*99*100) in these languages one has to invent things, but for Liberty BASIC, numbers of such length pose no problem:
for i=1 to n
print len(str$(f))
print f

output is (line breaks added to the long number for page formatting):
158 – number of digits
and factorial itself, hold your breath:

It looks like the length of those integers is limited only by available memory and computer speed (of course working with these beasts will be slower!). So getting 10^10000 might take several seconds. But it works, and with all digits!

Functions VAL( ) and STR$( ) in Liberty BASIC support long integers.

(Of course) +, -, *, as well as ^ and MOD supports long integers.

  • If a is a long integer and a/b is supposed to be whole number,
    • then a/b will be a long integer
    • else a/b will be a real number (and it even could overflow).
There are some beneficial quirks about INT(a/b):
  • If a is a long integer, and a/b is supposed to be real number,
    • then result will be a long integer (with all digits preserved). Also we evade real overflow.

It looks like Liberty BASIC somehow bypasses the intermediate step of storing a/b as real.
It looks like when calling standard math functions, the argument is converted to the "real" data type. This way, you can get overflow. So while 10^200 is within double range, you cannot get it by sqr(10^400) – 10^400 will try to convert to real and overflow.

Real numbers

Storing (small) integers is rather straightforward but our data or result turns out to be non-integer. It strongly looks like they end up stored in common in some other languages DOUBLE data type. This data type came with common limitations (existing in many programming languages):

Range limitation

Here is data on limitation for double-precision numbers from QBasic Help:
  • Positive 1.79769313486231D+308 4.940656458412465D-324
  • Negative -4.940656458412465D-324 -1.79769313486231D+308
If your result exceeds these limits, you’ll get overflow error. In Liberty BASIC, it is easy to get when working with long integers.

Precision limitation

Double real value stores only about 16 "real" digits. You can check it with USING function:
for i=1 to 20: mask$=mask$+"#": next '20 digits after "."
print using(mask$, 1/3)
print "*.12345678901234567890"
, that is, we get “garbage” after 16-th digit.

Just checking how small number d should be so 1+d will register as d:
for i=1 to 20
 print i,1-a

Precision limitation consequences

This gives some really bad things you should be aware of (and it is not a bug, it is a way real numbers work in pretty much any language):
  • You should not count on real numbers being exact.
  • You should never test real numbers for being exactly equal.
a = 2.1 - 2
b = 0.1
print a, b
if a=b then print "Equal" else print "Not equal"
print "Difference "; a-b
0.1 0.1
Not equal
Difference 0.83266727e-16
Instead, you should test for equality with given precision:
precision = 1e-10
if abs(a - b) < precision then print "Equal with precision" else print "Not equal"
  • You should not make FOR loops with real step,
    if the number of steps is important (like in numerical integration).
for i = 1 to 2 step 0.1
 print i
This example misses the last step (end up on 1.9 instead of 2.0).

Instead, you should use loop by integer index.

Saving/loading real number

There is no way in Liberty BASIC to save/load real number in binary representation, making sure we read back exactly what we saved. The only provided way is in text form.
The problem is that PRINT outputs only 7 digits of 16 we could use. If we try STR$, we’ll see that it has same limitation.
The USING function could provide the necessary precision, but how do we get right mask? There no support for scientific notation.

This handy function could help. To save 15 digits of x, use usingS$(x,15) :
'scientific USING function.
'(keep in mind that there are no more then 16 digits stored in real number (Double data type)).
function usingS$(n,prec)
 if n = 0 then usingS$="0e+0":exit function
 fmt$ = "#"+left$(".",prec>0)+left$("#################",prec)
'fmt of mantissa
'QB like INT. Makes mantissa for negative exponents
 'start from digit (not 0 as LB do)
 if left$(using(fmt$,n/p),1)="%" then p=p*10:e = e+1
 usingS$=s$+using(fmt$,n/p) +"e"+left$("+",e>0) +str$(e)
'Excel always shows "+" for exponent
end function

Using numbers as Booleans

As was stated, Liberty BASIC lacks a Boolean type, but it does have conditional statements. A condition normally evaluates to false or true.

If we print result of any relational operator (> < = >= <= <>), we'll see that true prints as 1 and false prints as 0.

This explains construction like eternal loop:
While 1
Also it opens possibilities for using a condition result as a number in some shortcuts like this one:
(returns x if it is >0, otherwise 0)

The only official rule is "0 represents false, everything else means true".

That allows for things like
If instr(txt$,searchFor$) then
Meaning the same as
If instr(txt$,searchFor$)<>0 then
That is, "if searchFor$ is found in txt$".

However when working with Boolean expressions involving numbers other then 0 and 1, one should take into account that logical operators AND OR XOR are bitwise:
5 or 3 -> 7 (101 or 011 -> 111)
5 and 3 -> 1 (101 and 011 -> 001)
5 xor 3 -> 6 (101 xor 011 -> 110)
You can examine bits in a number with function BitPattern$:
 function BitPattern$(num)
 for i = 0 to 31
 if i mod 8 = 0 then BitPattern$ = " " + BitPattern$
 BitPattern$ = str$((num and 2 ^ i) > 0) + BitPattern$
 next i
end function
So it is possible to get
if a then print "a is true" else print "a is false"
if b then print "b is true" else print "b is false"
if a and b then print "a and b is true" else print "a and b is false"
resulting at
a is true
b is true
a and b is false

Also note that some functions returns -1 for true. One such function is (EOF(#handle))

Function NOT(x), contrary to logical operators, is not bitwise. It returns -1 for true, too. You can get bitwise NOT(x) as (x XOR -1) (it looks like -1 in binary representation has 32 bits set).

There are two exceptions to the rule "0 represents false, everything else means true":
1) Syntax of SELECT CASE allows conditions in CASE, but here only the value 1 is accepted as true; others (including -1) are considered false.
x = 1
select case
case x: print "true"
case else: print "false"
end select
 'prints true
x = -1
select case
case x: print "true"
case else: print "false"
end select
 'prints false
2) while real number like 0.1 is surely not 0, you cannot use reals instead of conditions. BASIC just dies at runtime with "isEmpty not understood" error.
x = .1
if x then print "true" else print "false" ' dies at runtime with "isEmpty not understood" error.