This probably sounds dumb, but I am not getting any clear answer for this. What data type allows a reasonable amount of decimals?

  • 1
    What does "reasonable" mean? – Ignacio Vazquez-Abrams Oct 28 '16 at 4:10
  • At least 2 or 3, just the type smallest amount above this so I can save processing power on my Trinket board – pdustin101 Oct 28 '16 at 4:11
  • 4
    You can use a float but you might also consider multiplying your values by as scale factor in order to use integer arithmetic which will be much more efficient if you do not need a wider range. – Chris Stratton Oct 28 '16 at 4:41

If you need to represent numbers that are not integers, the simplest solution is to use floating point variables, also known as “floats”. A float is declared with the float keyword. A numeric constant is automatically a float if it has a decimal point :

  • 42 is an integer (int type)
  • 42.0 is a float

If you combine an int and a float in an arithmetic operation, the int is implicitly promoted to float, and the result is a float.

A float gives you a precision of 24 significant bits. “Significant” means that we are counting both the bits before and after the binary point. Then, a small number will have many fractional bits, whereas a large number will have only a few, and any number larger than 223 = 8388608 will have no fractional bits at all.

This precision is almost always better that 7 significant decimals and worse than 8 significant decimals. For example, the following two numbers are consecutive: any computation that yields a result in between them will be rounded to either of them (usually the closest):

    binary (24 bits)          decimal
101010.000000000000000000  42.0
101010.000000000000000001  42.000003814697265625

The issue with floats is that they are expensive. Unlike a desktop computer, the Trinket has no hardware support for floats (it's an ATtiny: it doesn't even have a hardware multiplier!). Then every computation is done by software. Even the simplest things, like adding two numbers, can take quite a few CPU cycles and a lot of flash space. That's why on very small devices people sometimes prefer fixed point arithmetic. In a nutshell, fixed point is all about choosing appropriate units of measurement in order to achieve the required precision using only integers. For example, the ADC converter in the Trinket gives you the voltage readings in units of

5 V ÷ 1024 = 4.8828125 mV

That unit may seem unpractical, but it allows a reasonable precision using only integers. If you can avoid converting to volts, you will be able to write integer-only code which is smaller and faster.

I would suggest you first try using floats, as it's the simplest solution. If that leads to code with is either too large or too slow, then try to rewrite with fixed-point (i.e. integer only) arithmetic.

| improve this answer | |

At least 2 or 3 [digits], just the type smallest amount above this so I can save processing power on my Trinket board

Processors (computers) do not normally store values in Binary Coded Decimal (BCD) (as in base 10) because such storage wast valuable space. Rather, processors usually use Binary numbers (as in base 2).

Most processors deal with binary values in multiples of 8 bits. So, values are usually represented by 8 bits, 16 bits, 32 bits, ect. An 8 bit binary number can have 2^8 possible values or 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 or 256. That is to say, an 8 bit binary number can have values between 0 and 255 inclusive.

If your requirement of "reasonable amount of decimals" falls within that range, then the type you are looking for is "uint8_t" or "char". Both are normally 8 bits in the C / C++ language. If you need more the next binary number has 16 bits or 2^16 or values between 0 and 65535. Then the type you are looking for is "uint16_t" or "unsigned int".

| improve this answer | |
  • While the facts you state are mostly true, this does not address the question asked in any practical way. As a result your final recommendations are wrong. The questions concerns non-integers, so an integer type cannot, by itself be a solution, though as already mentioned it could be one by use of a scaling factor. – Chris Stratton Oct 28 '16 at 14:54

Another option that Edgar doesn't mention (mainly because it's a more esoteric and tricky system to use) is fixed point values. These values, also known as Q values, are often used in digital signal processing (DSP) since they give predictable resolution and high speed processing.

A Q value is simply an integer where you split the bits within that integer into two portions - the integer portion and the numerator portion.

For instance, you may choose to implement a Q4.11 format. That gives 4 bits of integer value (it can store 0-15) and 11 bits of numerator (that is a decimal resolution of 1/(2^11) or 0.000488281). The 16th bit (4+11 = 15, in a 16-bit integer there is one more bit to account for) is the implicit sign bit. Some implementations have that, some don't - it's entirely up to you. If you only want positive values you can include the 16th bit in your value to increase resolution or integer size, etc.

You're not limited to 16 bit of course - you could use 8 bit for smaller numbers and lower precision, or 32 bit for bigger numbers and higher precision.

The disadvantage to using Q values compared to using floats is that floats can arbitrarily exchange decimal precision for integer size. With fixed point you have limited yourself to the range of integers you can represent. But that sacrifice can be worth it if you have a limited range in the first place (0-5V, say) and you need extra speed.

To work with fixed point it is first necessary to convert a floating point number to and from your target Q value. This is quite simple - just multiply (or divide to convert from Q to float) the value by the Q value of 1. That is, for a Q4.11 you would use the value of the 12th bit (the first of the integer bits), or 1<<11 (start counting from 0, so the 12th bit is 11).

Once you have your Q values adding and subtracting is just that - exactly as normal, using the + and - operators. Multiplying and dividing though is slightly more involved, but the resultant code is considerably more efficient. The whole lot can be done using just left and right shifts. For instance, some functions which I use in a small library of mine for Q15 values (more specifically Q0.15), which store values between ±1 with 15 bit resolution, look like:

typedef int16_t Q15;

static inline int16_t __satQ15(int32_t x)
    if (x > 0x7FFFL) return 0x7FFF;
    else if (x < -0x8000L) return -0x8000;
    else return (int16_t)x;

static inline Q15 Q15_mul_Q15(Q15 a, Q15 b) {
    int16_t result;
    int32_t temp;

    temp = (int32_t)a * (int32_t)b; // result type is operand's type
    // Rounding; mid values are rounded up
    temp += (1 << 14);;
    // Correct by dividing by base and saturate result
    result = __satQ15(temp >> 15);
    return result;

static inline Q15 Q15_div_Q15(Q15 a, Q15 b)
    int16_t result;
    int32_t temp;

    // pre-multiply by the base (Upscale to Q16 so that the result will be in Q8 format)
    temp = (int32_t)a << 15;
    // Rounding: mid values are rounded up (down for negative values).
    if ((temp >= 0 && b >= 0) || (temp < 0 && b < 0))
        temp += b / 2;
        temp -= b / 2;
    result = (int16_t)(temp / b);

    return result;

You can learn more about Q numbers here:

| improve this answer | |
  • Note that Q0.15 is natively supported by gcc as _Fract. I haven't yet experimented with that type though. – Edgar Bonet Oct 28 '16 at 10:27
  • @EdgarBonet Only if the version of GCC you use has that option compiled in - not all do. I don't know off hand if the Arduino's compilation of avr-gcc has it or not. – Majenko Oct 28 '16 at 10:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.