Code-Schnipsel
Moderatoren: broesel, Martin Conrad, PatrickThema: [EDIT] - Ziffern aus einer Zahl auslesen
[EDIT] - Re: Ziffern aus einer Zahl auslesen
scusi71 am 19.09.2008 um 22:02
// 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).
[EDIT] - Re: Ziffern aus einer Zahl auslesen
scusi71 am 19.09.2008 um 22:16
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).
