chars in double



  • ich habe ein charArray mit 400 Elementen. Dabei gehören immer 2 Byte zusammen. Die Werte kommen von einem 16Bit-ADC. Das erste Byte ist immer das HighByte. FFFF (hex) entsprechen 2,5 Volt.
    Nun möchte ich das in ein double-Array mit 200 Elementen konvertieren. Wie mach ich das am effektivsten?


  • Mod

    Effektiv gar nicht, du musst jeden einzelnen Wert von Hand umrechnen wenn du hinterher doubles haben möchtest. Wenn du portabel bleiben möchtest, versuchst du dabei auch nicht durch reinterpret-Casts auf short als Zwischenschritt zu tricksen, sondern rechnest wirklich den Wert aus. Und dann machst u noch einen Dreisatz und multiplizierst mit 2.5/0xFFFF und weist das Ergebnis dem double zu. Man kann das ganze auch wunderbar kurz mit den Algorithms aus der Standardbibliothek schreiben, aber falls dir das nichts sagt, sollte das von Hand auch nicht mehr als zwei bis drei Zeilen sein.



  • double* array_2int_to_double(unsigned char* array, size_t size)
    {
    	double* result = new double[size/2];
    	for (size_t n = 0; n < size; ++n)
    	{
    		unsigned char value1 = array[n];
    		unsigned char value2 = array[++n];
    		result[n/2] = (value1*256 + value2) / 26214.0;
    	}
    	return result;
    }
    

    edit: Da war jemand schneller.


  • Mod

    Na, das kannst du aber auch ohne händische Speicherverwaltung. Die an sich unnötigen Zwischenwerte lasse ich mal als die Lesbarkeit erhöhend durchgehen (wobei ich persönlich das anders sehe), werden sowieso wegoptimiert. Ohne kämst du auch auf die erwähnten 2-3 Zeilen.



  • Die an sich unnötigen Zwischenwerte lasse ich mal als die Lesbarkeit erhöhend durchgehen

    Ich war mir gerade nicht sicher, ob definiert ist, welchen Wert das erste n hat, wenn beim zweiten der Inkrement-Operator angewandt wird.


  • Mod

    Wurstinator schrieb:

    Die an sich unnötigen Zwischenwerte lasse ich mal als die Lesbarkeit erhöhend durchgehen

    Ich war mir gerade nicht sicher, ob definiert ist, welchen Wert das erste n hat, wenn beim zweiten der Inkrement-Operator angewandt wird.

    Es ist in der Tat nicht definiert, aber das braucht man ja auch nicht so zu machen. Du darfst auch gerne mal +2 in einer for-Schleife rechnen. Oder Multiplikation benutzen.



  • template<class InIter, class OutIter>
    void dings(InIter beg, InIter end, OutIter out)
    {
      while (beg!=end) {
        const unsigned hi = (*beg) & 0xFFu;
        ++beg;
        assert(beg!=end);
        const unsigned lo = (*beg) & 0xFFu;
        ++beg;
        *out = (hi << 8) | lo;
        ++out;
      }
    }
    
    int main() {
      vector<char> data1 = ...
      vector<double> data2;
      dings(data1.begin(),data1.end(),back_inserter(data2));
    }
    

    Als Boost Range Adapter wär's aber auch mal nett. Hat einer Lust zu, eine solchen Adapter zu zeigen?. 🙂



  • rudpower schrieb:

    ich habe ein charArray mit 400 Elementen. Dabei gehören immer 2 Byte zusammen. Die Werte kommen von einem 16Bit-ADC. Das erste Byte ist immer das HighByte. FFFF (hex) entsprechen 2,5 Volt.
    Nun möchte ich das in ein double-Array mit 200 Elementen konvertieren. Wie mach ich das am effektivsten?

    Effektiver wird es z.B. dann, wenn Du auf das charArray[400] und das Kopieren der Bytes in das Array verzichtest.

    Die Tatsache, dass hier einzelne Bytes vorliegen und immer zwei zusammen gehören, sieht doch stark nach Streaming aus. Und IO in C++ geht Standard gemäß mit der IO-Library, also istream, streambuf & Co.
    Da Du uns keine Auskunft über die Quelle der Daten gegeben hast, kann ich Dir auch keinen Vorschlag machen, wie der konkrete streambuf aussieht. Vielleicht klärst Du uns noch über die API des 16Bit-ADC auf.

    Wie dagegen der Stream aussieht ist im Prinzip klar. std::istream kommt nicht in Frage, da der vorwiegend für das Streamen von lesbaren Text designed ist. Aber seine Basisklasse std::basic_ios<char> gibt schon einiges her. Man muss ihr nur noch das binäre Lesen eines 2Byte-Wertes beibringen.
    Da ich mal davon ausgehe, dass Du auf einer Intel-Plattform (Windows, Linux) arbeitest, gilt es, die Bytereihenfolge beim Lesen zu vertauschen. Das ganze sieht so aus:

    #include <ios>
    #include <streambuf>
    #include <iterator> // std::reverse_iterator<>
    
    // --   input stream für binäres Lesen
    class ibinstream : public std::basic_ios< char >
    {
        typedef std::basic_ios< char > base_type;
    public:
        explicit ibinstream( std::streambuf* sb = 0 )
            : base_type( sb )
        {}
        ibinstream& operator>>( unsigned short& x )
        {
            if( good() )
            {
                iostate state = std::ios_base::goodbit;
                // --   binäres Lesen; mit Vertauschen der Byte-Reihenfolge
                const std::reverse_iterator< char* > dstEnd( reinterpret_cast< char* >( &x ) );
                for( std::reverse_iterator< char* > dst( reinterpret_cast< char* >( &x + 1 ) ); dst != dstEnd; ++dst )
                {
                    const base_type::int_type m = rdbuf()->sbumpc();
                    if( traits_type::eq_int_type( m, traits_type::eof() ) )
                    {
                        state |= std::ios_base::eofbit | std::ios_base::failbit;
                        break;
                    }
                    *dst = traits_type::to_char_type( m );
                }
                setstate( state );
            }
            else
                setstate( std::ios_base::failbit );
            return *this;
        }
    };
    

    Für den streambuf muss man noch für Ersatz sorgen, also eine einfache Demo-Lösung, die Zeichen aus einem char-Array liefert:

    class Sb : public std::streambuf
    {
    public:
        Sb( char* src, std::size_t n )
        {
            setg( src, src, src + n );
        }
    };
    

    .. das gilt es natürlich zu ersetzen - siehe Bemerkung oben.

    Jetzt können schon uint16_t-Werte gelesen werden:

    #include "ibinstream.h" // s.o.
    #include "Sb.h" // s.o.
    #include <cassert>
    #include <iostream>
    
    int main()
    {
        using namespace std;
        char arr[] = { 0xff,0xff, 0,0, 0x0a,0x3d };
        Sb sb( arr, sizeof(arr) );
        ibinstream in( &sb );
        for( unsigned short x; in >> x; )
            cout << x << endl;
        cin.get();
        return 0;
    }
    

    Die Ausgabe ist:

    65535
    0
    2621
    

    Verbleibt noch die Anforderung, die Werte zu konvertieren und nach double zu schreiben. Elegant geht das, wenn man das als Eigenschaft des Streams sieht, der selber den Faktor für die Kalibrierung kennt. Ein Extractor mit double wäre hier wohl fehl am Platz, da es doch eine spezielle Anwendung ist, also bleibt noch ein Manipulator - und alles zusammen sieht so aus:

    #include "ibinstream.h" // s.o.
    #include "Sb.h" // s.o.
    #include <cassert>
    #include <iostream>
    #include <vector>
    
    struct messwert
    {
        explicit messwert( double& target ) : m_target( &target ) {}
    
        // --   liest einen Messwert als UINT16 von einem ibinstream
        //      und schreibt ihn multipliziet mit einem Faktor in einen double-Wert
        friend ibinstream& operator>>( ibinstream& in, messwert me )
        {
            unsigned short x;  // ein Messwert ist in 2Byte untergebracht
            assert( sizeof(x) == 2 );
            if( in >> x )
                *me.m_target = *reinterpret_cast< float* >( &in.iword( FACTOR_IDX ) ) * double(x);
            return in;
        }
    
        static void setFactor( std::ios_base& ios, double factor )
        {
            // Bem.: der Faktor wird hier in ein long gepresst
            assert( sizeof(float) <= sizeof(long) );
            *reinterpret_cast< float* >( &ios.iword( FACTOR_IDX ) ) = float( factor );
        }
    private:
        double* m_target;
        static const int FACTOR_IDX;
    };
    const int messwert::FACTOR_IDX = std::ios_base::xalloc();
    
    // --  Anwendung
    int main()
    {
        using namespace std;
        // --   folgende zwei Zeilen sollten durch einen Streambuf mit Zugriff auf den 16Bit-ADC ersetzt werden
        char arr[] = { 0xff,0xff, 0,0, 0x0a,0x3d };
        Sb sb( arr, sizeof(arr) );
    
        vector< double > doubleArray;
        ibinstream in( &sb );
        messwert::setFactor( in, 2.5/0xFFFF ); // FFFF (hex) entsprechen 2,5 Volt
        for( double spannung; in >> messwert( spannung ); )
            doubleArray.push_back( spannung );
    
    	cin.get();
        return 0;
    }
    

    Am Ende befinden sich drei Elemente im Array 'doubleArray' mit den Werten 2.5, 0 und 0.1.

    Ich vermute stark, dass es nicht die aller schnellste Lösung ist, aber bei 200 Werten würde ich mir da keine Sorgen machen 😉
    Die Lösung hat aber Schnittstellen aus dem C++-Standard und ist damit allgemein gültig und besser verwendbar.

    Das ganze ist noch sehr speziell; weiter wird erst zur Laufzeit und nur im Debug-Mode bekannt, ob ein 'unsigned short' auch 2 Byte lang ist. Auch die Fähigkeiten von ibinstream sind eher mager.

    Gruß
    Werner



  • Werner Salomon schrieb:

    Das ganze ist noch sehr speziell; weiter wird erst zur Laufzeit und nur im Debug-Mode bekannt, ob ein 'unsigned short' auch 2 Byte lang ist. Auch die Fähigkeiten von ibinstream sind eher mager.

    deshalb kommt noch eine allgemeinere Version hinterher, die auch C++11 sei Dank sich alleine auf den Standard abstützt.

    #include <ios>
    #include <streambuf>
    #include <iterator> // std::reverse_iterator<>
    #include <type_traits>
    
    // --   input stream für binäres Lesen
    class ibinstream : public std::basic_ios< char >
    {
        typedef std::basic_ios< char > base_type;
    public:
        explicit ibinstream( std::streambuf* sb = 0 )
            : base_type( sb )
        {}
        template< typename I >
            typename std::enable_if< std::is_integral< I >::value, ibinstream >::type&
            operator>>( I& x )
        {
            if( good() )
            {
                iostate state = std::ios_base::goodbit;
                // --   binäres Lesen; mit Vertauschen der Byte-Reihenfolge
                const std::reverse_iterator< char* > dstEnd( reinterpret_cast< char* >( &x ) );
                for( std::reverse_iterator< char* > dst( reinterpret_cast< char* >( &x + 1 ) ); dst != dstEnd; ++dst )
                {
                    const base_type::int_type m = rdbuf()->sbumpc();
                    if( traits_type::eq_int_type( m, traits_type::eof() ) )
                    {
                        state |= std::ios_base::eofbit | std::ios_base::failbit;
                        break;
                    }
                    *dst = traits_type::to_char_type( m );
                }
                setstate( state );
            }
            else
                setstate( std::ios_base::failbit );
            return *this;
        }
    };
    
    #include "Sb.h" // s. vorherigen Beitrag
    #include <iostream>
    #include <vector>
    #include <type_traits>
    #include <cstdint> // std::uint16_t
    
    struct messwert
    {
        explicit messwert( double& target ) : m_target( &target ) {}
    
        // --   liest einen Messwert als UINT16 von einem beliebigen Stream
        //      und schreibt ihn multipliziet mit einem Faktor in einen double-Wert
        template< typename S > friend
            typename std::enable_if< std::is_base_of< std::ios_base, S >::value, S >::type&
            operator>>( S& in, messwert me )
        {
            std::uint16_t x;  // ein Messwert ist in 2Byte untergebracht
            if( in >> x )
                *me.m_target = *reinterpret_cast< float* >( &in.iword( FACTOR_IDX ) ) * double(x);
            return in;
        }
    
        static void setFactor( std::ios_base& ios, double factor )
        {
            // Bem.: der Faktor wird hier in ein long gepresst
            static_assert( sizeof(float) <= sizeof(long), "Auf dieser Plattform passt der Faktor (float) nicht in long (iword)" );
            *reinterpret_cast< float* >( &ios.iword( FACTOR_IDX ) ) = float( factor );
        }
    private:
        double* m_target;
        static const int FACTOR_IDX;
    };
    const int messwert::FACTOR_IDX = std::ios_base::xalloc();
    
    int main()
    {
        using namespace std;
        // --   folgende zwei Zeilen sollten durch einen Streambuf mit Zugriff auf den 16Bit-ADC ersetzt werden
        char arr[] = { 0xff,0xff, 0,0, 0x0a,0x3d };
        Sb sb( arr, sizeof(arr) );
    
        vector< double > doubleArray;
        ibinstream in( &sb );
        messwert::setFactor( in, 2.5/0xFFFF ); // FFFF (hex) entsprechen 2,5 Volt
        for( double spannung; in >> messwert( spannung ); )
            doubleArray.push_back( spannung );
    
    	cin.get();
        return 0;
    }
    

    ibinstream kann alle integralen Typen lesen, der Manipulator messwert kommt auch mit anderen Stream-Typen klar und wenn die size der Typen nicht zur Art der Lösung passt, so kommt das schon zur Compile-Zeit raus.

    Gruß
    Werner


Anmelden zum Antworten