Zahlen in Text umwandeln
Frank Schönmann
- perl
hi!
Irgendwo weiter unten hatten wir dieses Thema. Ich habe mal versucht, ein passendes Skript zu schreiben. Anscheinend funktioniert es, ich habe schon ein bisschen damit rumprobiert. 100%-ig sicher bin ich aber noch nicht. Also füttert es mit den abwegigsten Zahlen, um es zu testen (die Null habe ich nicht eingebaut!).
Zur Bedienung:
* Aufruf auf der Kommandozeile mit der Zahl als erstem Parameter: betrag.pl 123345
Eingabe der Zahl:
* keine führenden Nullen
* keine Kommazahlen
* nicht Null
* höchstens 36 Stellen
Ok, viel Spaß, ich hoffe es funktioniert.
=== cut ===
#!/usr/bin/perl
@einer = ("", "ein", "zwei", "drei", "vier", "fünf", "sech", "sieben", "acht", "neun");
@eeiner = ("", "eins", "zwei", "drei", "vier", "fünf", "sechs", "sieben", "acht", "neun");
@zeiner = ("", "elf", "zwölf", "dreizehn", "vierzehn", "fünfzehn", "sechzehn", "siebzehn", "achtzehn", "neunzehn");
@heiner = ("", "ein", "zwei", "drei", "vier", "fünf", "sechs", "sieben", "acht", "neun");
@zehner = ("", "zehn", "zwanzig", "dreißig", "vierzig", "fünfzig", "sechzig", "siebzig", "achtzig", "neunzig");
@namen = ("", "tausend ", " Millionen ", " Milliarden ", " Billionen ", " Billiarden ", " Trillionen ", " Trilliarden ",
" Quadrillionen ", " Quadrilliarden ", " Quintillionen ", "Quintilliarden ");
for (reverse split /-/, &teilen3(shift))
{
my $string;
($ein, $zehn, $hun) = reverse split /-/, &teilen1($_);
$string = $hun ? "$heiner[$hun]hundert" : "";
if ($zehn > 1)
{
$string .= $ein ? "$einer[$ein]und$zehner[$zehn]" : "$zehner[$zehn]";
}
if ($zehn == 1)
{
$string .= "$zeiner[$ein]";
}
if ($zehn < 1)
{
$string .= "$eeiner[$ein]";
}
push @teile, $string;
}
for (@teile)
{
$string = $_ . $namen[$i] . $string;
$i++;
}
print $string;
sub teilen3
{
my $text = reverse shift;
$text =~ s/(\d\d\d)(?=\d)(?!\d*.)/$1-/g;
return scalar reverse $text;
}
sub teilen1
{
my $text = shift;
$text =~ s/(\d)(\d)/$1-$2/g;
$text =~ s/(\d)(\d)/$1-$2/g;
return $text;
}
=== cut ===
Hallo Frank,
hey- das sieht ja überwiegend schon mal _sehr_ cool aus.
Klappt in den meisten Fällen.
Hier die Ausnahmen:
Zahlwert Ausgabe
---------------------------
1000000 "eins Millionen tausend"
10000000 "Millionen tausend"
10 ""
10000 "tausend"
2000000 "zwei Millionen tausend"
2000001 "zwei Millionen tausend eins"
2000010 "zwei Millionen tausend"
2000100 "zwei Millionen tausend einhundert"
2001000 "zwei Millionen einstausend"
2010000 "zwei Millionen tausend"
20000000 "zwanzig Millionen tausend"
20000001 "zwanzig Millionen tausend eins"
Fast "endcoole" Sache soweit, da wenige Zeilen und viel Effekt!!!
Grüße aus Bonn
Bo
Hallo Frank,
das ist auf jeden Fall eine elegant programmierte Loesung! Bin gespannt, ob es gelingt, die Routine noch fehlerfrei zu kriegen, das waere ein echtes "Schmankerl"...
viele Gruesse
Stefan Muenz
Hi allerseits,
das konnte ich ja nicht auf mir sitzen lassen, daß ich eine Methode vorgebe und dann jemand anders die Lösung postet *g* :-)))
Also, hier ist nun mein Ansatz. Die Null wird nicht abgefragt, für "ein Tausend" und "eine Million/Billion/etc." habe ich einen provisorischen Quickhack gemacht (ist sonst "eins Tausend/Million/Billion/etc."), sonst dürfte alles beachtet worden sein, denke ich. Man kann die Zahlen nach deutscher und englischer Regel ausgeben ("zweiundvierzig" oder "vierzigundzwei"). Amerikanische Zählweise (million*1000 = billion statt milliard) ist nicht möglich, dazu müßte ich einen unmathematischen Ansatz wählen *g*, dafür sind auch die Variablen an sich kürzer (und weniger, weil ich den Plural nicht für jedes Wort einzeln definieren muß). Naja, ich höre mal auf und poste den Quellcode:
--- cut here to damage your monitor ---
#!/usr/bin/perl
$par = $ENV{'QUERY_STRING'};
@bname = ('', 'Mill', 'Bill', 'Trill', 'Quadrill', 'Quintill', 'Sextill', 'Septill', 'Oktill', 'Nonill', 'Dezill');
@hname = ('null', 'ein', 'zwei', 'drei', 'vier', 'fünf', 'sechs', 'sieben', 'acht', 'neun');
@dname = ('', 'zehn', 'zwanzig', 'dreißig', 'vierzig', 'fünfzig', 'sechzig', 'siebzig', 'achtzig', 'neunzig');
@d2name = ('zehn', 'elf', 'zwölf', 'dreizehn', 'vierzehn', 'fünfzehn', 'sechzehn', 'siebzehn', 'achtzehn', 'neunzehn');
@ename = ('null', 'eins', 'zwei', 'drei', 'vier', 'fünf', 'sechs', 'sieben', 'acht', 'neun');
$million = 'ion'; $millions = 'ionen';
$milliard = 'iarde'; $milliards = 'iarden';
$thousand = 'Tausend';
$hundred = 'hundert';
$and = 'und'; # '' in english, 'und' in german
$lang = 1; # 0 = 'fifty one', 1 = 'one fifty' (german)
$komma = 'Komma';
$minus = 'Minus';
print "Content-type: text/html\n\n";
print "$par = " . &num2word($par) . "<BR>\n";
exit;
sub num2word {
my $num = $_[0];
my $string = ($num =~ /-/) ? $minus . ' ' : '';
if ($num =~ /^(.*),(.*)$/) { $num = $1; $real = $2; }
$num =~ s/\D//g;
$real =~ s/\D//g;
$string .= &num2string($num,0);
# Folgende zwei Zeilen sind eine Notlösung, die den Text nur schöner machen:
$string =~ s/eins (\S*?)illi/eine $1illi/g;
$string =~ s/eins $thousand/ein $thousand/;
# ('ein Tausend' oder 'eine Million' sehen besser aus als 'eins Tausend' und 'eins Million')
if ($real) {
$string .= ' ' . $komma . ' ';
foreach (split(//,$real)) { $string .= $ename[$_] . ' '; }
}
return $string;
}
sub num2string {
my $this = $_[0];
my $temp;
if ($this =~ /^(.*)(.{6})/) { $still = $1; $temp = $2; } else { $still = 0; $temp = $this; }
if ($temp =~ /(.*)(.{3})/) { $high = $1; $low = $2; } else { $high = 0; $low = $temp; }
$temp = &hundreds($high,$_[1],1) . &hundreds($low,$_[1],0);
if ($still > 0) { return &num2string($still, $_[1]+1) . $temp; }
else { return $temp; }
}
sub hundreds {
my $val = $_[0];
my $oldval = $val;
my $block = $_[1];
my $type = $_[2];
my $temp = '';
my $temp2 = '';
my $temp3 = '';
if ($val >= 100) { $temp = $hname[int($val/100)] . $hundred; $val = $val % 100; }
if ($val >= 20) { $temp2 = $dname[int($val/10)]; $val = $val % 10; }
elsif ($val >= 10) { $temp2 = $d2name[$val-10]; $val = 0; }
if ($val > 0) { $temp3 = $ename[$val]; }
if ($lang == 0) {
if ($temp3) {
if ($temp.$temp2) { $temp = $temp . $temp2 . $and . $temp3; }
else { $temp = $temp3; }
}
else { $temp = $temp . $temp2; }
} else {
if ($temp2) {
if ($temp.$temp3) { $temp = $temp . $temp3 . $and . $temp2; }
else { $temp = $temp2; }
}
else { $temp = $temp . $temp3; }
}
if ($temp) {
$temp .= ' ' . $bname[$block];
if ($type == 1) {
if ($block > 0) { if ($oldval > 1) { $temp .= $milliards; } else { $temp .= $milliard; } }
else { $temp .= $thousand; }
} else {
if ($block > 0) { if ($oldval > 1) { $temp .= $millions; } else { $temp .= $million; } }
}
$temp .= ' ';
}
return $temp;
}
--- cut here to damage your monitor ---
Die letzte Funktion ist ziemlich lang, dafür sind mir aber auch keine "undeutschen" Wörter mehr aufgefallen :-) Wenn jemand was findet, bitte bescheid sagen!
Ach ja, Nachkommazahlen bitte mit "," abtrennen, nicht mit "." ;-)
Cheatah
Ach ja,
beinahe hätte ich es vergessen: Ein "-" vor der Zahl bewirkt eine entsprechende Ausgabe, also "Minus Zahl". Fröhliches testen!
Cheatah
Sodele,
ich hab mal eine optimierte Version ins Netz gestellt. Den Quelltext könnt ihr euch unter http://cheatah.net/cgi-bin/zahl.pl?lang=english&val=* ansehen. Ändert ihr das * in der URL in eine Zahl, wird diese "ausgewortet". Der Parameter lang akzeptiert derzeit 'english' und 'alles andere = deutsch'. Wenn mir jemand helfen könnte, die Zahlen z.B. auf französisch zu übersetzen, wäre ich dankbar :-)
Folgende Features werden unterstützt:
Known Bugs:
Das Script ist in der Betatest-Phase! Bitte testet ausführlich. Um die gefundenen Probleme kümmere ich mich, ansonsten überlasse ich das Script der Allgemeinheit. Vielleicht kann's ja jemand gebrauchen :-)
Cheatah
Moin Cheatah,
Known Bugs:
- Es wird ein bißchen getrickst, um "ein Tausend" und "eine Million" statt "eins Tausend/Million" zu erzeugen. Daran arbeite ich aber noch.
Eben bei diesem tricksen hast du dir scheinbar einen neuen Bug eingehandelt: 21000, 31000 etc. werden auch als "ein Tausend" ausgegeben!
Ansonsten: schöne Sache, vor allem die (tatsächlich korrekte Reihenfolge ausgebende) Sprachauswahl!
Gruß
Dirk
Hi Dirk,
Known Bugs:
- Es wird ein bißchen getrickst, um "ein Tausend" und "eine Million" statt "eins Tausend/Million" zu erzeugen. Daran arbeite ich aber noch.
Eben bei diesem tricksen hast du dir scheinbar einen neuen Bug eingehandelt: 21000, 31000 etc. werden auch als "ein Tausend" ausgegeben!
stimmt, ich habe aus Versehen eine falsche Variable zur Prüfung benutzt. Das ist korrigiert, inkl. einer weiteren Unstimmigkeit ("einshundert"), die ich in dem Zusammenhang fand. Danke!
Ansonsten: schöne Sache, vor allem die (tatsächlich korrekte Reihenfolge ausgebende) Sprachauswahl!
:-)
Cheatah
hi!
Hier die korrigierte Version, die auch die von Boris gemeldeten Fehler beseitigt. Und sie ist immer noch kürzer als Cheatahs Metode ;-))
Das Problem mit der grundlosen Angabe der "tausend" trat übrigens auch bei Millionen und allen weiteren folgenden Werten auf, und zwar immer, wenn dieser Dreierblock eigentlich 000 war. Hm, verständlich? ;)
#!/usr/bin/perl
@einer = ("", "ein", "zwei", "drei", "vier", "fünf", "sech", "sieben", "acht", "neun");
@eeiner = ("", "eins", "zwei", "drei", "vier", "fünf", "sechs", "sieben", "acht", "neun");
@zeiner = ("zehn", "elf", "zwölf", "dreizehn", "vierzehn", "fünfzehn", "sechzehn", "siebzehn", "achtzehn", "neunzehn");
@heiner = ("", "ein", "zwei", "drei", "vier", "fünf", "sechs", "sieben", "acht", "neun");
@zehner = ("", "zehn", "zwanzig", "dreißig", "vierzig", "fünfzig", "sechzig", "siebzig", "achtzig", "neunzig");
@namen = ("", "tausend ", " Millionen ", " Milliarden ", " Billionen ", " Billiarden ", " Trillionen ", " Trilliarden ",
" Quadrillionen ", " Quadrilliarden ", " Quintillionen ", "Quintilliarden ");
for (reverse split /-/, &teilen3(shift))
{
my $string;
($ein, $zehn, $hun) = reverse split /-/, &teilen1($_);
$string = $hun ? "$heiner[$hun]hundert" : "";
if ($zehn > 1)
{
$string .= $ein ? "$einer[$ein]und$zehner[$zehn]" : "$zehner[$zehn]";
}
if ($zehn == 1)
{
$string .= "$zeiner[$ein]";
}
if ($zehn < 1)
{
$string .= "$eeiner[$ein]";
}
push @teile, $string;
}
for (@teile)
{
if($_)
{
$string = $_ . $namen[$i] . $string;
}
$i++;
}
print $string;
sub teilen3
{
my $text = reverse shift;
$text =~ s/(\d\d\d)(?=\d)(?!\d*.)/$1-/g;
return scalar reverse $text;
}
sub teilen1
{
my $text = shift;
$text =~ s/(\d)(\d)/$1-$2/g;
$text =~ s/(\d)(\d)/$1-$2/g;
return $text;
}
Hi,
Hier die korrigierte Version, die auch die von Boris gemeldeten Fehler beseitigt. Und sie ist immer noch kürzer als Cheatahs Metode ;-))
*grins* das kann ich nicht auf mir sitzen lassen ;-)))
Das Problem mit der grundlosen Angabe der "tausend" trat übrigens auch bei Millionen und allen weiteren folgenden Werten auf, und zwar immer, wenn dieser Dreierblock eigentlich 000 war. Hm, verständlich? ;)
ja. Da mußt Du hier:
{
$string = $_ . $namen[$i] . $string;
}
eine Abfrage machen. Weiterhin fällt mir folgendes auf:
1000000: "eins Millionen" statt "eine Million"
1000: "einstausend", naja, ich würde zumindest "eintausend" schreiben. Bei mir ist das "ein Tausend".
1.234: "einhundertvierunddreißig" - da hat er die 2 nach dem Punkt verschluckt?
0.923: "hundertdreiundzwanzig" - offenbar ist der Fehler doch gravierender...
7%4$9&43(12a496: "zweihundertsechsundneunzig" - okay, bei solchen Falscheingaben kann man eigentlich nichts vernünftiges mehr erwarten... :-)
Wenn Du das Problem mit den Sonderzeichen noch in den Griff bekommst (evtl. sogar Nachkommastellen beachtest), ist das Script fast veröffentlichungsreif. Schade nur, daß sich Dein Script nicht auf englische Zahlen ("fourtytwo") konvertieren läßt, ohne die Programmierung zu ändern ;-)))
Cheatah
hi!
Das Problem mit der grundlosen Angabe der "tausend" trat übrigens auch bei Millionen
und allen weiteren folgenden Werten auf, und zwar immer, wenn dieser Dreierblock
eigentlich 000 war. Hm, verständlich? ;)ja. Da mußt Du hier:
{
$string = $_ . $namen[$i] . $string;
}
eine Abfrage machen. Weiterhin fällt mir folgendes auf:
??? Ist doch in der korrigierten Version längst geschehen. Hast du die if-Abfrage übersehen?
1000000: "eins Millionen" statt "eine Million"
1000: "einstausend", naja, ich würde zumindest "eintausend" schreiben. Bei mir ist das "ein
Tausend".
Das stimmt natürlich. Dieses Problem liese sich aber nur mit sehr speziellen if-Abfragen lösen. Zur Zeit ist jedoch alles sehr allgemein gehalten. Die Umwandlung einer Dreiergruppe in Worte hängt alleine vom Wert der Zehnerstelle ab (0, 1 oder >1). Würde ich diese ganzen Probleme lösen, müsste ich sehr viele Abfragen einbauen. Das kann wohl auch jemand anderes übernehmen ;-)) Stichwort: SELFHTML-Workshop :-)
An Stefan: vielleicht kannst du so eine Seite oder einen Bereich aufnehmen für diese und ähnliche Probleme, bei denen mehrere Leute in Zusammenarbeit ein nützliches Skript oder sonstiges basteln :)
1.234: "einhundertvierunddreißig" - da hat er die 2 nach dem Punkt verschluckt?
0.923: "hundertdreiundzwanzig" - offenbar ist der Fehler doch gravierender...
7%4$9&43(12a496: "zweihundertsechsundneunzig" - okay, bei solchen Falscheingaben
kann man eigentlich nichts vernünftiges mehr erwarten... :-)
s/\D//g; filtert sämtliche nicht-Ziffern heraus, das sollte dieses Problem lösen. Aber wenn ich eine Zahl umwandeln will, dann erwarte ich eigentlich auch eine Zahl, nicht eine sinnlose Zusammenwürfelung von Ziffern und Zeichen.
Wenn Du das Problem mit den Sonderzeichen noch in den Griff bekommst (evtl. sogar
Nachkommastellen beachtest), ist das Script fast veröffentlichungsreif.
Nachkommastellen ist doch kein Problem. "0,1234" hieße ja korrekterweise einfach "Null komma eins zwei drei vier".
Schade nur, daß sich Dein Script nicht auf englische Zahlen ("fourtytwo") konvertieren läßt,
ohne die Programmierung zu ändern ;-)))
Das war auch nicht die Aufgabenstellung :) Ich wäre froh gewesen, englische Namen verwenden zu dürfen. Dann hätte ich nicht soviele verschieden Daten-Arrays verwenden müssen.
Warum muss es "eins", "sechs" und "sieben" heißen, bei Zehnern "zehn", "sechzig" und "siebzig", bei Hundertern "ein", "sechs" und "sieben" und bei Zahlen zwischen 10 und 20 "elf", "sechzehn" und "siebzehn"? Vor allem durch diese Unterschiede muss ich so viele verschiedene Werte beachten :-((
Und jetzt freuen wir uns auf die Umwandlung für französische Zahlen. Natürlich ohne Spezialüberprüfung für 91 bis 96 (Patrick?) ;-))
bye, Frank!
Hi,
ja. Da mußt Du hier:
{
$string = $_ . $namen[$i] . $string;
}
eine Abfrage machen. Weiterhin fällt mir folgendes auf:??? Ist doch in der korrigierten Version längst geschehen. Hast du die if-Abfrage übersehen?
oh, nicht ganz... ich hatte mich nur verlesen :-) Das kommt davon, wenn man müde ist... sorry!
1000000: "eins Millionen" statt "eine Million"
1000: "einstausend", naja, ich würde zumindest "eintausend" schreiben. Bei mir ist das "ein
Tausend".Das stimmt natürlich. Dieses Problem liese sich aber nur mit sehr speziellen if-Abfragen lösen.
Das siehst Du ganz richtig! Ich kann gut verstehen, wenn das nicht gemacht wird, man versteht die Zahl ja auch so. (Dabei fällt mir immer ein: "Bei dieser Satz scheint die Grammatik stimmt so ganz nicht, aber versteht man ihn trotzdem" *g*)
Zur Zeit ist jedoch alles sehr allgemein gehalten. Die Umwandlung einer Dreiergruppe in Worte hängt alleine vom Wert der Zehnerstelle ab (0, 1 oder >1). Würde ich diese ganzen Probleme lösen, müsste ich sehr viele Abfragen einbauen. Das kann wohl auch jemand anderes übernehmen ;-)) Stichwort: SELFHTML-Workshop :-)
Naja, siehe mein Script ;-) Schau Dir die Abfragen an, dann siehst Du warum das Script so lang ist!
An Stefan: vielleicht kannst du so eine Seite oder einen Bereich aufnehmen für diese und ähnliche Probleme, bei denen mehrere Leute in Zusammenarbeit ein nützliches Skript oder sonstiges basteln :)
Prima Idee! Ich unterstütze den Antrag! Eventuell sogar einfach als unwesentlich angepaßte Kopie dieses Forums? Ein Thread ist halt die Entwicklung eines Projektes. Klingt gut für mich!
1.234: "einhundertvierunddreißig" - da hat er die 2 nach dem Punkt verschluckt?
0.923: "hundertdreiundzwanzig" - offenbar ist der Fehler doch gravierender...
7%4$9&43(12a496: "zweihundertsechsundneunzig" - okay, bei solchen Falscheingaben
kann man eigentlich nichts vernünftiges mehr erwarten... :-)s/\D//g; filtert sämtliche nicht-Ziffern heraus, das sollte dieses Problem lösen. Aber wenn ich eine Zahl umwandeln will, dann erwarte ich eigentlich auch eine Zahl, nicht eine sinnlose Zusammenwürfelung von Ziffern und Zeichen.
Steht so glaube ich in meinem Script ;-)
Klar habe ich in meinen Beispielen etwas übertrieben. Aber eine Formatierung der Art "1.234.567,89" ist durchaus normal.
Wenn Du das Problem mit den Sonderzeichen noch in den Griff bekommst (evtl. sogar
Nachkommastellen beachtest), ist das Script fast veröffentlichungsreif.Nachkommastellen ist doch kein Problem. "0,1234" hieße ja korrekterweise einfach "Null komma eins zwei drei vier".
Sollte mein Script auch so ausgeben ;-)
Schade nur, daß sich Dein Script nicht auf englische Zahlen ("fourtytwo") konvertieren läßt,
ohne die Programmierung zu ändern ;-)))Das war auch nicht die Aufgabenstellung :) Ich wäre froh gewesen, englische Namen verwenden zu dürfen. Dann hätte ich nicht soviele verschieden Daten-Arrays verwenden müssen.
Stimmt!
Warum muss es "eins", "sechs" und "sieben" heißen, bei Zehnern "zehn", "sechzig" und "siebzig", bei Hundertern "ein", "sechs" und "sieben" und bei Zahlen zwischen 10 und 20 "elf", "sechzehn" und "siebzehn"? Vor allem durch diese Unterschiede muss ich so viele verschiedene Werte beachten :-((
Nun ja, "eleven" und "twelve" hast Du ja auch im Englischen. Schlimmer ist "eins" kontra "einhundert".
Und jetzt freuen wir uns auf die Umwandlung für französische Zahlen. Natürlich ohne Spezialüberprüfung für 91 bis 96 (Patrick?) ;-))
Oh je... die hatte ich ganz vergessen! Machen wir einfach quatre vingt dix für neunzig und setzen bei Bedarf un - neuf drauf. Muß man eigentlich auch manchmal une sagen? Herrje... Franzosen bitte an die Front :-)
Cheatah