Unofficial LSL Reference

[[types:float]]


Unofficial LSL reference

User Tools

Login

You are currently not logged in! Enter your authentication credentials below to log in. You need to have cookies enabled to log in.

Login

Forgotten your password? Get a new one: Set new password

Float type

The float type represents a number with decimals, as opposed to the integer type which is for whole numbers only.

Floating-point (float) constants can be entered in two formats. The first is by just writing the number with its sign, the decimal point (mandatory for the constant to actually be a float and not an integer), and the decimals. For example, 3.7 or -5.4164 are valid float constants. Neither the part to the left of the point nor the part to the right of the point are mandatory (e.g. 37. or -.25 are valid), but a point without any digits around it is not a valid floating-point constant.

The second format is scientific notation. That means to write a number (with or without decimals, and with an optional minus sign) that is to be multiplied by a power of ten, then an e (upper or lower case, doesn't matter), then the power of ten (optionally with a plus or minus sign). It can be optionally followed by an f (upper or lower case) but that extra f has no effect, and it is only allowed if there's a point present. For example: 2e4 and 2.0E+4F are float constants that represent the same number, namely 2.0 times 10 to the 4th power, which can also be written as 20000.0; other examples are: -3.6123e-7, 1.2e-27f, 5e30. The number 2e4f is not valid because it has an f and there is no point present. When the power of ten is negative, it means to divide by 10 to that power without sign; for example, 3.3e-5 means 3.3/100000 or 0.000033.

This so called scientific notation allows writing very big and very small numbers without having to write that many digits. For example, imagine having to write 5.4e30 as 5400000000000000000000000000000.0.

The range of a float is approximately from -3.4028234e38 to 3.4028234e38. The smallest positive number that it can represent is approx. 1.4e-45. It can also have the value minus zero (-0.0) but that one is usually not distinguished from a regular zero. In Mono, a float can also have three special values: Infinity, -Infinity, and NaN (Not a Number). In LSO, however, any operation that would result in any of these values will instead cause a math error and stop the script until it's reset.

Automatic type conversion between float and integer

Most places that accept a float-type value also accept an integer-type value, which will be automatically converted to float if necessary. For example:

// 3 is an integer constant, but it is automatically converted to float
float x = 3;
 
// llSetTimerEvent accepts a float parameter, but this will work;
integer j = 2;
llSetTimerEvent(j);

This can save some space in Mono, because integer constants that get converted to floats take less bytes than float constants.

The opposite is not true in general. If a float is used where an integer is expected, that will generate a compiler or run-time error, depending on the context. An exception is the llList2Integer function, which will implicitly convert a floating-point value in a list to integer. For example:

integer a = 3.0; // will give a type mismatch error when compiling
 
// PRIM_MATERIAL should be followed by an integer.
// The following line will cause a run-time error when executed.
llSetPrimitiveParams([PRIM_MATERIAL, 1.0]);
 
// This will display 3 and not cause any errors, because
// llList2Integer can be used on a float value.
llOwnerSay((string)llList2Integer([3.0], 0));

Precision

float values are single-precision floats. An implication is that their precision is 6 to 9 significant digits, that is, total digits starting from the leftmost non-zero digit.

For example, in this format, the number 1.000000001 can't be distinguished from the number 1.000000002 because it requires 10 significant digits to distinguish them, and there are not enough significant digits in the float type. However, the number 1.00001 can be distinguished from the number 1.00002 because it has 6 significant digits.

Due to the way floats work, the closer to zero they are the more precision after the decimal point they have, and vice versa. Numbers above 8,388,608.0 or less than -8,388,608.0 don't have any decimal places at all, while numbers between -1.0 and 1.0 have at least 6 decimal places, possibly more depending on how close to zero they are.

Type casting a float to string

When they are typecast to string, floats are always translated with all of their digits to the left of the decimal point, and six decimal places to the right, rounded. However, as an exception, when a vector (a collection of three floats) or a rotation (a collection of four floats) is typecast to string, its floats are converted using five decimal places to the right of the decimal point. This exception does not apply if they are inside a list and the list is converted to string.

In Mono, the typecast of a float to string will only store up to 7 significant digits and set the rest to zero, rounding the last digit; for example, 1234567890.0 will be converted to "1234568000.000000".

Some examples will illustrate these rules (the results from these examples are generated with Mono; output in LSO will be different because the float formatting functions differ):

llOwnerSay((string)1.);
// Prints 1.000000
 
llOwnerSay((string)3e38);
// Prints 300000000000000000000000000000000000000.000000
 
llOwnerSay((string)1e-6);
// Prints 0.000001
 
llOwnerSay((string)1e-7);
// Prints 0.000000
 
llOwnerSay((string)5e-7);
// Prints 0.000001 because of the rounding
 
llOwnerSay((string)3.1415926535897932384626433832);
// Prints 3.141593
 
llOwnerSay((string)<1.0, 2.0, 3.0>);
// Prints <1.00000, 2.00000, 3.00000>
// (note each number displayed with 5 decimal places, not 6)
 
llOwnerSay((string)<1.0, 2.0, 3.0, 4.0>);
// Prints <1.00000, 2.00000, 3.00000, 4.00000>
// (note each number displayed with 5 decimal places, not 6)
 
llOwnerSay((string)[<1.0, 2.0, 3.0>]);
// In this case, it's a list what gets converted to string.
// When a list is converted to string, the floats in the vectors and rotations
// that it contains are converted with 6 decimal places instead of 5.
// The output is thus: <1.000000, 2.000000, 3.000000>
 
llOwnerSay(llList2String([<1.0, 2.0, 3.0>], 0));
// Same as above. llList2String applied to a vector element will convert to
// string using 6 decimal places too.

The float value NaN is translated to the string NaN, no matter the kind; the float value Infinity is translated to the string Infinity and the float value -Infinity to the string -Infinity. This happens also with other functions that convert floats to string (e.g. llList2String, llDumpList2String), except one: llList2CSV translates the float value NaN to the string nan (if it's regular NaN) or -nan (if it's the indeterminate kind of NaN), and translates Infinity and -Infinity to inf and -inf respectively.

Precision caveats

Floats are represented internally in base 2, not in base 10. That leads many people to confusion, because some decimal numbers can't be represented accurately in base 2, and rounding errors occur. It's not a bug; it's a limitation of the type, and a necessary compromise. For example:

float-equal.lsl
default
{
    state_entry()
    {
        float a = 0.6 + 0.1;
        float b = 0.7;
        if (a == b)
            llOwnerSay("equal");
        else
            llOwnerSay("different");
    }
}

The code above prints "different", because the addition introduces rounding errors. A frequent instance of this problem arises when counting from 0 to 1 in increments of 0.1. The following example illustrates a typical case:

float-loop.lsl
default
{
    state_entry()
    {
        float f;
        for (f = 0.0; f <= 1.0; f += 0.1)
        {
            llOwnerSay((string)f);
        }
    }
}

This example prints 0.000000, 0.100000, etc. up to 0.900000 but not 1.000000. Due to accumulated rounding error, the actual final value is approximately 1.00000012 instead of 1, which is slightly larger than 1 and thus doesn't meet the condition for f <= 1. That is unfortunate but expected, because 0.1 can not be represented exactly in a float type, and it has to be approximated.

A float can only exactly represent fractions which have a power of two as a denominator when simplified. For example, 3.75 = 15/4 has a power of two in its denominator and can thus be represented exactly in a float. Same happens with 3.78125 = 121/32. However, 0.1 = 1/10 can't be represented in that form, because 10 is not a power of two. Instead, it's represented with the approximation 13421773/134217728 which is approximately 0.1000000015, and when adding many of these together, rounding does not always get the desired result, and it's expectable that sooner or later, the obtained result will differ from the expected one. That causes the above problem, and confuses many unaware people who use floating-point math in many languages, not just LSL.

In general, it's not a good idea to loop over floats, unless you are aware of the limitations and know that the operations you're doing are safe. One possible solution to the problem in the particular example above is to loop using integers instead, and divide inside the loop, like this:

integer-loop.lsl
default
{
    state_entry()
    {
        integer i;
        for (i = 0; i <= 10; ++i)
        {
            llOwnerSay((string)(i/10.0));
        }
    }
}

This example will print 0.000000, 0.100000, 0.200000, etc. up to 1.000000 inclusive.

Another possible solution is to use a step that is a power of 2, like 0.125 (1/8), instead of 0.1, because that step is not affected by rounding errors, since it can be represented exactly. This will work as expected:

float-loop-8steps.lsl
default
{
    state_entry()
    {
        float f;
        for (f = 0.0; f <= 1.0; f += 0.125)
        {
            llOwnerSay((string)f);
        }
    }
}

That loop will print 0.125000, 0.250000, 0.375000, etc. up to 1.000000 inclusive.