http://www.pronix.de -> Forum -> C-Programmieren -> Code-Schnipsel

Unterseiten

Code-Schnipsel

Moderatoren: broesel, Martin Conrad, Patrick

Thema: [EDIT] - Ziffern aus einer Zahl auslesen

  • (nur registrierte Mitglieder)
Da Du deinen Code ja hier mit dem Ziel der Wiederverwendung eingestellt hast, hoffe ich das Du einem 'klein wenig' Kritik nicht abgeneigt bist. Also dann:

// Warum C++, wenn dann doch nur Quellcode im C-Stil folgt?
#include <iostream>
using namespace std;

// Diese Routine ist zunächst mal fehlerhaft und zusätzlich für eine Lösung
// des Problems noch völlig unnötig. Der Name erinnert mehr an frühes Fortran.
int cntz(int zahl)
{
    // 1. Fehler: Sofern die Zahlen das Zweierkomplement nutzen - was der Normalfall ist -
    // gilt für den kleinsten negativen Wert: zahl == -zahl. Die Zahl bliebe also negativ.
    if (zahl < 0)
        zahl *= -1;

    // Unschön, das i wird doch erst in der Schleife gebraucht.
    int i, ziffernzahl = 1;

    // 2. Fehler: Für den größten Teil des abgedeckten Zahlenbereichs, ist das Verhalten
    // der Schleife nicht definiert. Denn gehen wir z.B. von 16 Bit Integern aus, dann ist
    // die Größte erlaubte 10er Potenz 10.000. Für alle Werte ab 10.000 wird 10.000 
    // nochmals mit 10 multipliziert, es kommt zum Überlauf. Im allg. ist dann 
    // 10.000 * 10 < 10.000.
    for (i = 10; i <= zahl; i *= 10) 
        ziffernanzahl++; 

    return ziffernanzahl
}

// Auch diese Routine zeigt für die Mehrzahl der möglichen Eingaben
// ein undefiniertes Verhalten. Ausserdem wird ohne Grund ein
// dynamisch allokiertes Array verwendet, und der Name ist nichts 
// sagend.
int *getz(int zahl, int ziffernanzahl) 
{ 
    // Unschön: i und stellenwert werden doch nur in der Schleife verwendet.
    // Warum werden Sie hier definiert?
    int i, stellenwert = 10; 

    // Unschön: Warum muss es unbedingt ein Array sein? Warum kein STL-Container?
    // Wenn C++ dann sollten die Mittel der Sprache auch verwendet werden. 
    // Aber selbst wenn man ein Array nutzen muss, dann sollte der Zeiger als konstant
    // definiert werden, um dem Compiler die Möglichkeit zu geben uns zu warnen wenn
    // wir ihn verändern. Also besser:
    // int *const zptr = new int[ziffernzahl];
    int *zptr = new int[ziffernanzahl]; 

    // 3. Fehler: Hier taucht der 2. Fehler in abgewandelter Weise wieder auf.
    // Für die Mehrzahl der möglichen Zahlen liefert die Schleife das falsche 
    // Ergebnis, da stellenwert überläuft.
    for(i = ziffernanzahl-1; i >= 0; i--, stellenwert *= 10) 
    { 
        zptr[i] = (zahl % stellenwert) / (stellenwert/10); 
    } 
    return zptr; 
} 

// Neben den ganzen beschrieben Problemen kommt noch hinzu, dass ein Anwender der Routinen
// Länge und Array separat handeln muss. Folgefehler sind da geradezu vorprogrammiert.


Aber nach soviel Kritik stellt sich die Frage wie man es besser macht. Eigentlich gibt einem C++ mit der STL eine mächtige Waffe in die Hand mit der man die gegebene Aufgabenstellung schnell und effizient erschlagen kann. Man muss sie nur nutzen.

Also zunächst mal der Header. Zeiger brauchen wir gar keine und zum Speichern der Ziffern nutzen wir kein fehleranfälliges dynamisch allokiertes Array, sondern eine std::deque. Selbige nimmt uns schon fast alle Arbeit ab, die Routine cntz() brauchen wir nichtmehr.

/*****************************************************************************
 *  Datei: 'value_as_digits.hpp'
 *  Deklarationen zum 'Zahl als Ziffern' Modul.
 */
#ifndef VALUE_AS_DIGITS_H
#define VALUE_AS_DIGITS_H

#   include <deque>

    long digitsToValue (const std::deque<long>& queue);
    void valueToDigits (long value, std::deque<long>& queue);
    long digitSum (const std::deque<long>& queue);
#endif



Jetzt die Implementierung

/*****************************************************************************
 * Datei: 'value_as_digits.cpp'
 * Implementierung des 'Zahl als Ziffern' Moduls.
 */
#include "value_as_digits.hpp"
#include <numeric>

namespace {
    // Die Hilfsfunktion kommt in einen anonymen Namespace.
    inline long accumulator (const long value, const long digit)
    {
        return value * 10L + digit;
    }
}

// Ziffern -> Zahl:
// Es wird nicht überprüft ob die Ziffern alle <= 0 oder alle >= 0 sind.
long digitsToValue (const std::deque<long>& queue)
{
    return std::accumulate(queue.begin(),
                           queue.end(),
                           0L,
                           accumulator);
}

// Zahl -> Ziffern:
// Die Ziffern einer positiven Zahl sind alle >= 0, die einer negativen alle <= 0.
void valueToDigits (long value, std::deque<long>& queue)
{
    queue.clear(); // Evtl. Reste erstmal loeschen.
    do {
        queue.push_front (value % 10L);
    } while (value /= 10L);  // Hier kann nichts ueberlaufen!
}

// Quersumme:
// Es wird angenommen, dass die Quersumme einer negativen Zahl selbst negativ ist.
// Es wird nicht überprüft ob die Ziffern alle <= 0 oder alle >= 0 sind.
long digitSum (const std::deque<long>& queue)
{
    return std::accumulate(queue.begin(),
                           queue.end(),
                           0L);
}



Obwohl nicht nur die Richtung Zahl -> Ziffern implementiert wurde, ist der Code kompakter
und deutlich klarer. Wir brauchen in keiner der Funktionen auch nur eine lokale Variable ausser den sowieso notwendigen Parametern.

Jetzt die Anwendung im Beispiel

/*****************************************************************************
 * Datei: main.cpp
 * Beispiel zur Nutzung des 'Zahl als Ziffer' Moduls.
 */
#include "value_as_digits.hpp"
#include <algorithm>
#include <iterator>
#include <iostream>
#include <climits>

int main (void)
{
   // Zum Testen nehmen wir ein paar kritische Werte.
    const long value[] = { LONG_MIN, LONG_MAX, -1, 1, 0 };

    for (register size_t i(0);
         i < sizeof(value) / sizeof(value[0]);
         ++i)
    {
        std::deque<long> digits;

        // Hin- und Herwandeln ...
        valueToDigits (value[i], digits);
        const long result(digitsToValue(digits));

        // und zum Schluss testen.
        std::cout << "Ziffern: ";
        std::copy (digits.begin(),
                   digits.end(),
                   std::ostream_iterator<long>(std::cout, " "));
        std::cout << std::endl << "Test: " << value[i]
                  << ((value[i] == result) ? " == " : " != ")
                  << result << std::endl
                  << "Quersumme: " << digitSum(digits) << std::endl;
    }
    return 0;
}



Lizenz: BSD mit dem Zusatz, dass bei Verwendung in OpenSource Projekten die Lizenz nicht weiter eingeschränkt werden darf (keine GPL).
 
Und da es sich hier um ein Forum zum Thema C-Programmierung handelt, folgt jetzt die Lösung in C99.

Nachteil gegenüber C++:
* Der Code ist Umfangreicher und es ist mehr Handarbeit notwendig.
Vorteil:
* "inttypes.h" liefert mit intmax_t den größten Integertypen auf der Plattform. Bei der C++
Lösung waren wir durch long beschränkt.

Der Header

/*****************************************************************************
 * File: 'number_as_digits.h'
 * Declaration of the 'number as digits' facility.
 */
#ifndef NUMBER_AS_DIGITS_H
#define NUMBER_AS_DIGITS_H


#   if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L
#       error Because an iso c99 compatible compiler is required!
#   else
#       include <stdlib.h>
#       include <inttypes.h>

typedef struct number_as_digits
{
    size_t size;
    int    digit[];
} number_as_digits_t;

extern _Bool number_to_digits (intmax_t number,
                                                        number_as_digits_t **const restrict ptr_to_handle);
extern _Bool digits_to_number (const number_as_digits_t  *const restrict handle,
                                                         intmax_t  *const restrict number);
#   endif
#endif



Die Implementierung

/*****************************************************************************
 * File: 'number_as_digits.c
 * Implementation of the 'number as digits' facility.
 */
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L
#   error Because an iso c99 compatible compiler is required!
#else
#   include "number_as_digits.h"
#   include <assert.h>
#   include <limits.h>
#   include <stdbool.h>

bool number_to_digits (intmax_t number,
                                          number_as_digits_t **const restrict ptr_to_handle)
{
	assert (ptr_to_handle && NULL == *ptr_to_handle);
	
	const size_t    max_digits  = CHAR_BIT * sizeof (number);
	int       temp_digit[max_digits];
	
	for (register size_t i = 0U; i < max_digits; ++i)
	{
		temp_digit[i] = number % 10;
		if (!(number /= 10))
		{
			++i;
			*ptr_to_handle = malloc (sizeof(**ptr_to_handle) +
                                                                     sizeof ((*ptr_to_handle)->digit[0]) * i);
			if (*ptr_to_handle)
			{
				(*ptr_to_handle)->size = i;
				for (register size_t j = 0U; i--; ++j)
					(*ptr_to_handle)->digit[j] = temp_digit[i];
				return true;
			}
			break;
		}
	}
	return false;
}

bool digits_to_number (const number_as_digits_t *const restrict handle,
                                                      intmax_t  *const restrict number)
{
	assert (handle && number);
	
	if (0U == handle->size)
		return false;
	
	*number = 0;
	for (register size_t i = 0; i < handle->size; ++i)
	{
		*number *= 10;
		*number += handle->digit[i];
	}
	return true;
}
#endif



Das Testprogramm

/*****************************************************************************
 * File: main.c
 * Usage example of the 'number as digits' facility.
 */
#include "number_as_digits.h"
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    const intmax_t number[] = { LLONG_MIN, LLONG_MAX, 0, -1, 1 };
	
	
    for (size_t j = 0; j < sizeof(number) / sizeof(number[0]); ++j)
    {
        number_as_digits_t * digits = NULL;
        if (!number_to_digits (number[j], &digits))
            return EXIT_FAILURE;
		
        printf ("Digits: ");
        for (register size_t i = 0; i < digits->size; ++i)
            printf ("%d ", digits->digit[i]);
        puts ("");
		
        intmax_t result;
        if (!digits_to_number (digits, &result))
            return EXIT_FAILURE;
		
        printf ("%" PRIdMAX " == %" PRIdMAX "? %s\n",
                result,
                number[j],
                result == number[j] ? "JA" : "NEIN");
		
        free (digits);
    }
    return EXIT_SUCCESS;
}



Lizenz: BSD mit dem Zusatz, dass bei Verwendung in OpenSource Projekten die Lizenz nicht weiter eingeschränkt werden darf (keine GPL).
 
  • (nur registrierte Mitglieder)