Thread: 24 bit integer class

  1. #1
    Registered User
    Join Date
    Dec 2014
    Posts
    143

    24 bit integer class

    What do you guys think about my 24 bit integer class? I need it for processing audio data. I need it to be as fast as possible to reduce latency so if anybody has some suggestions please tell me.

    Code:
    #ifndef INT24
    #define INT24
    
    
    #pragma pack(push, 1)
    
    
    /* __int24 class declaration
    
    
       __int24 is a class used for 24 bit signed
       integers. Most machines do not support 24 bit
       integers, so a char array is used to keep up with
       the bits. When operations need to be done, the
       char array is converted to an integer, and then
       the operation is done. 24 bit integers are very
       typical when it comes to audio streaming, however,
       because the of the lack of support for 24 bit
       integers, it may run slightly slower which may
       increase latency.
    */
    
    
    class __int24 {
    
    
    public:
        /* Constructors */
        __int24() {}
        __int24(const __int24& val) { *this = val; }
        __int24(const __int32& val) { *this = val; }
        template <class T> __int24(const T& val) { *this = (__int32)val; }
    
    
        /* Conversions */
        operator __int32() {
            __int32 val = byte[0] | (byte[1] << 8) | (byte[2] << 16);
            if (val & 0x800000) val |= ~0xffffff;
            return val;
        }
        template <class T> operator T () { return (T)this->operator __int32(); }
        operator bool() const { return (__int32)*this != 0; }
    
    
        /* Operators */
        __int24& operator = (const __int24& val) {
            byte[0] = val.byte[0];
            byte[1] = val.byte[1];
            byte[2] = val.byte[2];
            return *this;
        }
        __int24& operator = (const __int32& val) {
            byte[0] = (val & 0x000000ff);
            byte[1] = (val & 0x0000ff00) >> 8;
            byte[2] = (val & 0x00ff0000) >> 16;
            return *this;
        }
        template <class T> __int24& operator = (const T& val) { return *this = (__int32)val; }
    
    
        template <class T> __int24& operator += (const T& val) { return *this + val; }
    
    
        template <class T> __int24& operator -= (const T& val) { return *this - val; }
    
    
        template <class T> __int24& operator *= (const T& val) { return *this * val; }
    
    
        template <class T> __int24& operator /= (const T& val) { return *this / val; }
    
    
        template <class T> __int24& operator %= (const T& val) { return *this % val; }
    
    
        __int24 operator + (__int24& val) { return __int24((__int32)*this + (__int32)val); }
        template <class T> __int24 operator + (const T& val) { return __int24((__int32)*this + val); }
    
    
        __int24 operator - (__int24& val) { return __int24((__int32)*this - (__int32)val); }
        template <class T> __int24 operator - (const T& val) { return __int24((__int32)*this - val); }
    
    
        __int24 operator * (__int24& val) { return __int24((__int32)*this * (__int32)val); }
        template <class T> __int24 operator * (const T& val) { return __int24((__int32)*this * val); }
    
    
        __int24 operator / (__int24& val) { return __int24((__int32)*this / (__int32)val); }
        template <class T> __int24 operator / (const T& val) { return __int24((__int32)*this / val); }
    
    
        __int24 operator % (__int24& val) { return __int24((__int32)*this % (__int32)val); }
        template <class T> __int24 operator % (const T& val) { return __int24((__int32)*this % val); }
    
    
        __int24 operator - () { return __int24(-(__int32)*this); }
    
    
        bool operator ! () const { return this == 0; }
    
    
        bool operator == (__int24& val) { return (__int32)*this == (__int32)val; }
        template <class T> bool operator == (T& val) { return (__int32)*this == val; }
    
    
        bool operator != (__int24& val) { return (__int32)*this != (__int32)val; }
        template <class T> bool operator != (T& val) { return (__int32)*this != val; }
    
    
        bool operator >= (__int24& val) { return (__int32)*this >= (__int32)val; }
        template <class T> bool operator >= (T& val) { return (__int32)*this >= val; }
    
    
        bool operator <= (__int24& val) { return (__int32)*this <= (__int32)val; }
        template <class T> bool operator <= (T& val) { return (__int32)*this <= val; }
    
    
        bool operator > (__int24& val) { return (__int32)*this > (__int32)val; }
        template <class T> bool operator > (T& val) { return (__int32)*this > val; }
    
    
        bool operator < (__int24& val) { return (__int32)*this < (__int32)val; }
        template <class T> bool operator < (T& val) { return (__int32)*this < val; }
    
    
    private:
        unsigned char byte[3];
    };
    
    
    #pragma pack(pop)
    
    
    #endif
    EDIT: I just noticed my +=, -=, etc did not work because I forgot say the value equals it. I'm just used to making the += like operators first and then making the + like operators.
    Last edited by cmajor28; 02-26-2015 at 11:28 PM.

  2. #2
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    I'm not sure I see the point. Why not just use 32 bits? Yes, the audio stream itself uses 24 bits per sample but that doesn't mean you have to represent the data that way during processing. You are just introducing a bunch of pointless data conversions.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  3. #3
    Registered User
    Join Date
    Dec 2014
    Posts
    143
    Yeah that's what I first thought. But I have been doing some testing in Matlab (I know Matlab isn't C/C++) and some of the algorithms introduce clipping to audio data. I don't want to check after every processing module every value to see if its in the range of a 24bit signed number (I don't feel like calculating it right now). According to Matlab, it is faster to create a 24 bit data type instead of checking the range on only a few functions. And yes you do have to check after each processing function because it would be processed differently if it was clipped. However, I posted on here because I don't really like how I'm doing things. And the Matlab library I was using had 24 bit integers so I figured they created their own since most machines don't implement 24 bit integers. Also I figured if I did it really well, my 24 bit class wouldn't cost me too much latency because 8 bit and 16 bit integers are often "padded" into 32 bit integers for operations.

  4. #4
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,661
    > Most machines do not support 24 bit integers,
    Is this for the purposes of the assignment, or is someone genuinely clueless?

    Code:
    struct foo {
        int thisIsMyInt:24;
    };
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  5. #5
    Registered User
    Join Date
    Dec 2014
    Posts
    143
    Is a bitfield the best way to do this? I don't really know. I just chose my way because I looked at RTAudio's source code and they had a 24 bit integer class very similar to mine without the operators.

  6. #6
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by cmajor28 View Post
    Yeah that's what I first thought. But I have been doing some testing in Matlab (I know Matlab isn't C/C++) and some of the algorithms introduce clipping to audio data. I don't want to check after every processing module every value to see if its in the range of a 24bit signed number (I don't feel like calculating it right now). According to Matlab, it is faster to create a 24 bit data type instead of checking the range on only a few functions. And yes you do have to check after each processing function because it would be processed differently if it was clipped. However, I posted on here because I don't really like how I'm doing things. And the Matlab library I was using had 24 bit integers so I figured they created their own since most machines don't implement 24 bit integers. Also I figured if I did it really well, my 24 bit class wouldn't cost me too much latency because 8 bit and 16 bit integers are often "padded" into 32 bit integers for operations.
    What you are talking about is commonly known as "saturation" in signal processing community, not "clipping." Yes, it can be very important to the intended functioning of an algorithm. But again, you do not have to store the data in a 24-bit container just to do saturating arithmetic. In fact, as you well know, you actually have to do the math with extra bits so you can detect the saturated condition in the first place. So just convert it up to 32 bits, do your algorithmic processing, with proper saturation, and then pack back down to 24 bits on the way out.

    EDIT: The reason Matlab 24-bit integers are faster than explicit saturation is because Matlab is optimized for saturated arithmetic. It doesn't have anything to do with the width of the type, you are just telling it to saturate to 24-bit range, and it knows how to do that very efficiently, more efficiently than you could express in Matlab yourself.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  7. #7
    Registered User
    Join Date
    Dec 2014
    Posts
    143
    Well "clipping" caused "saturation" but yeah I see what you're saying. To keep the checks out of the algorithm itself (the functions will be templated for use with different size data types) would be it be best to make a 24 bit class using a 32 bit container, and then after each operation do a simple range check within the class? Or do you have a better idea? And another question is I know the processor has a flag to detect overflow, but I want this turned off because I DO want data types to overflow without an error occurring. Do you know how I can do this?

  8. #8
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by cmajor28 View Post
    Well "clipping" caused "saturation" but yeah I see what you're saying. To keep the checks out of the algorithm itself (the functions will be templated for use with different size data types) would be it be best to make a 24 bit class using a 32 bit container, and then after each operation do a simple range check within the class?
    That's the general approach I would take. Stay in 32-bit land, but after each operation make sure you saturate the result. So you will be overloading arithmetic operators.

    Addition is safe, because the sum of two 24-bit integers will never exceed the range of a 32-bit integer. But multiplication could overflow and a naive range check will miss it. Think long and hard about the implementation!
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  9. #9
    Registered User
    Join Date
    Dec 2014
    Posts
    143
    I think I'm going to go run some test and see what yields the best result. I also need to be aware that functions will also accept normalized float values so my range check may need to be an overloaded function for each data type I want to use.

  10. #10
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by cmajor28 View Post
    I think I'm going to go run some test and see what yields the best result. I also need to be aware that functions will also accept normalized float values so my range check may need to be an overloaded function for each data type I want to use.
    Have you considered just doing all your processing with floats? It would make saturation checking much easier, and on some platforms it is actually faster than integers.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  11. #11
    Registered User
    Join Date
    Dec 2014
    Posts
    143
    I've actually heard the total opposite. Okay, so I have tons of DSP algorithms written in matlab for this project im working on. I was originally going to use all doubles for all my calculations because matlab is heavily oriented to them (especially the dsp toolbox). However, I talked to some of the guys I work with who have been doing dsp for longer than I've been alive and they told me they always try to use integers for algorithms because its a lot faster. So I asked my dad this who has been designing computer chips and he told me integer math is a lot faster than floating point math.

    But anyway from my tests I have some hugely mixed results. I did some tests first with gcc then with visual studio. gcc uses the processor flag that detecs overflow (which was what I was talking about above) while visual studio thinks nothing of it but I get weird answers (which is easily understandable why because of how math is done).

    EDIT: gcc actually has a trap for overflow. It is NOT using the flag.
    Last edited by cmajor28; 02-27-2015 at 02:17 PM.

  12. #12
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by cmajor28 View Post
    I've actually heard the total opposite. Okay, so I have tons of DSP algorithms written in matlab for this project im working on. I was originally going to use all doubles for all my calculations because matlab is heavily oriented to them (especially the dsp toolbox). However, I talked to some of the guys I work with who have been doing dsp for longer than I've been alive and they told me they always try to use integers for algorithms because its a lot faster. So I asked my dad this who has been designing computer chips and he told me integer math is a lot faster than floating point math.
    "Floats are slower than integers" just isn't a generally true statement. It's not true even if you limit yourself strictly to DSP processors. Float might be faster, slower, or not even available.

    If you intend to target a specific set of platforms, and you know they have fast floats, then use floats. If you have no idea what the target is, or if you suspect you'll need to port the code to other platforms you aren't aware of yet, then integers are a safer choice.

    You should listen to the old DSP guys, but if they are making blanket statements like that, then they must be living in caves. Float performance outstripped integer performance on Intel processors as far back as 2003, if my memory serves. Even an embedded DSP subprocessor like Qualcomm's Hexagon chip on the Snapdragon, not only has floats, but it has SIMD floats.

    Writing DSP algorithms in such a way that they are easily switchable between floats and integers of varying bit-depths, is one of the holy grails of DSP.

    EDIT: BTW, by "float" I really do mean 32-bit floats, not doubles.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  13. #13
    Registered User
    Join Date
    Dec 2014
    Posts
    143
    Well I'm not an expert on floating-point vs integer math. I've heard both sides of it. I think my solution is to implement it for both floating point and integer math (which was my original plan. Floating point I'm not to worried about. I can easily do range checks because I'm going to normalize the values between -1 and 1 (which is very typical). However, I should also implement 8,16,24,and 32 bit integers. And I'm starting to notice some huge problems with it. C/C++ doesn't have a way to get that processor flag. Yes gcc has something similar but from what I'm reading, it isn't very efficient to use it. I don't want to rewrite every math operator with a check before the operation is done. That would just be horrible on speed. I only want to do range checks after each function (so that I can template out each function and have overloaded range checks for different data types). Do you have any ideas on what I should do?

  14. #14
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    I'm not entirely following, but as far as keeping it within range, couldn't you just do an and operation after each operation? E.g. for 24-bit calculations:

    val &= 0xFFFFFF00;

    This is just one instruction (unless the value is written to memory at this point), and should easily be done in one cycle if the hardware is good.
    If you want varying integer widths, you could just make a function Normalize and overload it, e.g.:

    Normalize(val);

    void Normalize(int8_t& Val);
    void Normalize(int16_t& Val);
    // etc

    (Yes, int8_t, int16_t, etc are part of the C++ standard as of C++11.)

    If you enable whole program optimization or the like for your compilers, the compiler should be able to manage register pressure for inter-function, making is so it is possible that the value you're calculating might not have to go to memory. If you can't use such optimizations, the inlining all operations in the same translation unit would probably help.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  15. #15
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by cmajor28 View Post
    Well I'm not an expert on floating-point vs integer math. I've heard both sides of it. I think my solution is to implement it for both floating point and integer math (which was my original plan. Floating point I'm not to worried about. I can easily do range checks because I'm going to normalize the values between -1 and 1 (which is very typical). However, I should also implement 8,16,24,and 32 bit integers. And I'm starting to notice some huge problems with it. C/C++ doesn't have a way to get that processor flag. Yes gcc has something similar but from what I'm reading, it isn't very efficient to use it. I don't want to rewrite every math operator with a check before the operation is done. That would just be horrible on speed. I only want to do range checks after each function (so that I can template out each function and have overloaded range checks for different data types). Do you have any ideas on what I should do?
    I'm a DSP guy by trade, although my hair isn't completely gray yet it is getting there.

    If you find a good, fast, general-purpose, portable solution please let me know. Like I said, holy grail.

    Again though, what platforms are of interest here? I may be able to give you more specific advice.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. constant integer class variable errors
    By Trey Brumley in forum C++ Programming
    Replies: 17
    Last Post: 10-01-2013, 11:22 AM
  2. c++ equivalent to java's Integer class
    By abriggs2 in forum C++ Programming
    Replies: 1
    Last Post: 08-29-2008, 01:09 PM
  3. Replies: 5
    Last Post: 06-12-2007, 02:18 PM
  4. determining if an integer is a legal integer
    By LiquidBlaze33 in forum C++ Programming
    Replies: 2
    Last Post: 10-09-2005, 07:06 PM
  5. overloading the division operator with Integer Class
    By silk.odyssey in forum C++ Programming
    Replies: 3
    Last Post: 05-17-2004, 06:59 PM