JavaScript Wie wird eine Dezimalzahl mit zwei Nachkommastellen in einen Bruch umgewandelt?

kali-hi schrieb:
@tollertyp Der Benutzer soll hinterher beide Geschmacksrichtungen sehen können, also einmal die Variante x.xx und einmal xx/xx, je nach dem, was bevorzugt ist. Und auf sehr hohe Genauigkeiten kommt es hierbei auch nicht an.
Nur kannst du halt aus x.xx nicht mehr xx/xx machen... Das habe ich in meinem Beitrag doch erklärt.

Mit Brüchen rechnen und wenn es um die Dezimalanzeige geht, dann halt entsprechend gerundet anzeigen.
Die Brüche halt auch regelmäßig kürzen.
Ergänzung ()

kali-hi schrieb:
Eigentlich sollten es hier 2/3 sein, nicht 33/50 ... Hat jemand dazu eine Idee? Ich runde ja bereits immer ab.
Das ist doch genau das, was ich hier sagte:
"anstatt am Ende zu hoffen, dass der Wert mit 2 Dezimalstellen in einen Bruch umgewandelt werden kann, der nahe an dem Wert ist, den die Zahl (die ja vermutlich gerundet sein dürfte?) repräsentieren soll?"

Dass das Umwandeln von gerundeten Dezimalzahlen in einen Bruch zum Scheitern verurteilt ist, ist doch offensichtlich.

Die gerundete Zahl 0.33 kann unendlich viele Zahlen repräsentieren...
0.50 kann 1/2 sein, kann aber auch 101/201 sein...
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: simpsonsfan, kali-hi und madmax2010
Hmmm, könnte man nicht sagen (oder annehmen), alles zwischen -1% und +1%, was gerundet eine exakte Bruchzahl ergibt (also einen Bruch, der einen möglichst kleinen Nenner hat...), wäre dennoch ein hinreichend genaues Ergebnis?

Beispiel:

BerechnungDezimalFraktional direktFraktional gerundet
1/30,333333/1001/3
2/30,666733/502/3
1/20,51/21/2
2/77
0,02597402597402597403

13/500
2/77

Geht das mit einer Formel?
 
was hinreichend genau ist kommt auf das an was du machst.
 
  • Gefällt mir
Reaktionen: BeBur
Sorry, aber anstatt sich irgendeinen Algorithmus zu überlegen, der die korrekten Brüche erraten versucht, schreib den Code doch um, dass er mit rationalen Zahlen arbeitet. Du machst doch eh nur Multiplikation und Division? Dafür sind Brüche doch perfekt geeignet? Und den Vergleich ob größer kann man ja auch problemlos umsetzen. Also eine Klasse RationalNumber, die einen Zähler, einen Nenner hat und wenn man mag auch schon den aktuellen Wert als Gleitkommazahl... aber das wäre vermutlich zu objektorientiert.

Außer du willst halt auch noch mit irrationalen Zahlen wie Pi oder e oder so rechnen...

Hab mir mal den Spaß erlaubt ChatGPT zu fragen... und bitte, man kann mir kein Framing vorwerfen...
1766341301005.png

Lösung 2 war eine Approximation, aber ich habe von Double gesprochen, hier wurde ja sogar von Zahlen die abgeschnitten sind geredet... puh...

Lösung 3 wäre eine Library zu nehmen - und das wäre das sinnvollste, weil man das Rad nicht immer neu erfinden sollte:
1766341365059.png
 
  • Gefällt mir
Reaktionen: BeBur und simpsonsfan
Also die Aussage, dass 1/3 "direkt" 33/100 und "gerundet" dann 1/3 ist, muss man sich auch erst mal trauen.
Und wäre denn 2/77 nicht hinreichend genau mit 1/39 approximiert?
Klar kann man vieles sagen.

Nur ist der Umweg von einem exakten Bruch über eine auf ein Hunderstel gerundete (oder abgeschnittene) Dezimalzahl hin zu einem erneuten Bruch nicht unbedingt sinnig.

Und wie @tollertyp schon sagte. Wenn ich dir sage, "ich habe hier eine gerundete Zahl zwischen 0,495 und 0,505. Bitte wandle mir die in den ursprüngliche Zahl um (mit einer erlaubten Unsicherheit von max. 1%)", wie willst du das machen? Was weg ist, ist weg. (Und die Spanne von 0,01 sind in dem Fall bis max. 2% Fehler. Bei kleineren Zahlen wird es noch schlimmer.)

Du kannst aber freilich mit dem Ansatz "gebe mir den Bruch mit kleinstem Nenner aus, der noch innerhalb von 1% relativem Fehler ist" fortfahren. Ist prinzipiell pretty straightforward, einfach machen. Oder halt gleich mit Brüchen rechnen.
 
  • Gefällt mir
Reaktionen: tollertyp
Bitte nix mit npm ...

simpsonsfan schrieb:
Nur ist der Umweg von einem exakten Bruch über eine auf ein Hunderstel gerundete (oder abgeschnittene) Dezimalzahl hin zu einem erneuten Bruch nicht unbedingt sinnig.
Vielleicht ist es zurzeit aber das kleinere Übel...

also... wir würde ich an den Algo herangehen?
 
kali-hi schrieb:
Vielleicht ist es zurzeit aber das kleinere Übel...
Verglichen mit was? Eine Klasse für ganzzahlige Brüche die die Grundrechenarten unterstützt ist in 15 Minuten erledigt. Bis dahin hast du dir noch nichtmal einen Algorithmus überlegt den du ausprobieren kannst, debuggen kannst, umschreiben kannst um dann am Ende festzustellen, dass das kein sinnvoller Ansatz ist.
 
  • Gefällt mir
Reaktionen: madmax2010 und tollertyp
@kali-hi Auch wenn ich den anderen Stimmen bzgl. Sinnhaftigkeit hier beipflichte, ich sehe zwei simple Herangehensweisen:
  1. Liste mit hundert Einträgen
  2. Pseudocode* (freilich ungetestet):
Code:
dec = decimal
if decimal<0:
    dec=-dec
    save_flag()
if dec == 0:
    return 0
dec = decimal - floor(decimal)
dec_times_100 = dec*100
enumerator = dec
denominator = 100
for(i=1..100):
    test_denom  = i
    test_enum = round(enumerator*test_denom/100)
    test_val = test_enmum/test_denom
    if abs(test_val/dec)<0.01:
        break
if decimal>1:
    whole_part = decimal modulo 1 (or integer cast)
    test_enum += whole_part*test_denom
if decimal<1:
    analogous
return string("test_enum / test_denom")
Edit: nicht wundern, ich hab hier Formatierungsprobleme, weil ich eine Liste mache und es mir den Code in den Code-Tags nicht unabhängig von der Liste machen möchte. Der Code gehört zu Punkt 2.

(*ich war kurz davor, was in Python zu schreiben und darauf hinzuweisen, dass sich das ja ähnlich liest, aber es nicht wirklich zu implementieren ist für mich einfacher)
 
  • Gefällt mir
Reaktionen: kali-hi und tollertyp
Die Liste mit 100 Einträgen, also ein Mapping, ist vermutlich noch das sinnvollste, wenn man von den auf zwei Stellen gerundeten Zahlen ausgeht.
 
  • Gefällt mir
Reaktionen: kali-hi und simpsonsfan
simpsonsfan schrieb:
*ich war kurz davor, was in Python zu schreiben und darauf hinzuweisen, dass sich das ja ähnlich liest, aber es nicht wirklich zu implementieren ist für mich einfacher
Python:
import math

def make_fraction(decimal_num):
    is_negative = False
    if decimal_num < 0:
        decimal_num = -decimal_num
        is_negative = True # "save_flag()"
    
    if decimal_num == 0:
        return "0/1"
   
    # Pseudocode: dec = decimal - floor(decimal)
    whole_part = math.floor(decimal_num)
    frac_part = decimal_num - whole_part
    
    best_enum = 0
    best_denom = 1
    
    # Pseudocode: for(i=1..100) -> Python uses range(start, stop_before)
    for i in range(1, 101):
        test_denom = i
        
        # Pseudocode: test_enum = round(enumerator*test_denom/100)
        # CORRECTION: The /100 in pseudocode assumes percentage logic which breaks small decimals.
        test_enum = round(frac_part * test_denom)
        
        if test_denom == 0: continue
        test_val = test_enum / test_denom
        
        # Pseudocode: if abs(test_val/dec) < 0.01
        # CORRECTION: Checking the ratio (dividing) is unstable.
        # We check the difference (subtraction). If difference is small, we found it.
        if abs(test_val - frac_part) < 0.0001:
            best_enum = test_enum
            best_denom = test_denom
            break
                    
        best_enum = test_enum
        best_denom = test_denom

    # Pseudocode: test_enum += whole_part*test_denom
    final_enum = int(best_enum + (whole_part * best_denom))
    final_denom = int(best_denom)

    if is_negative:
        final_enum = -final_enum

    return f"{final_enum}/{final_denom}"

# --- Testing the code ---
print(make_fraction(0.75))   # Expected: 3/4
print(make_fraction(1.5))    # Expected: 3/2
print(make_fraction(-0.33))  # Expected: -33/100

Ich war mal so frei
 
  • Gefällt mir
Reaktionen: kali-hi und simpsonsfan
Der Code von madmax2010 ist garnicht so schlecht (3-).

Geht aber besser und simpler.

Python:
from fractions import Fraction

def make_fraction_pro(decimal_num):
    res = Fraction(decimal_num).limit_denominator(100)
    return f"{res.numerator}/{res.denominator}"
 
  • Gefällt mir
Reaktionen: kali-hi und simpsonsfan
@madmax2010 Ich mach so viel mit Python, und ich hasse es einfach für seinen Umgang mit Whitespaces. Deinen Code kann ich nicht einfach hier rauskopieren und im Terminal einfügen (weil der Foren-Formatter wahrscheinlich wieder Whitespaces rumkickt, aber das ist so im Netz), sondern muss erst die Leerzeilen (die nämlich keine Leerzeichen haben) anpassen.

Bei dem Code kommt für 101/201 oder 100/201 als Eingabewert übrigens ("50/100") raus (statt "1/2"). Gut möglich, dass das auch schon mein Fehler war.
Der Code von @Oppenheimer funktioniert dort hingegen bestens. Bringt aber natürlich in Javascript konkret nicht viel. Aber evtl. ist die von @tollertyp verlinkte Klasse ja ebenfalls geeignet.

Trotzdem würde ich generell dazu raten, die Werte eben von Anfang an nicht auf zwei Nachkommastellen zu runden, sondern eine Rundung, soweit möglich, immer erst bei der Ausgabe vorzunehmen.

Edit: Das mit der Ausgabe von 50/100 bei 0,497512...=101/201 liegt daran, dass die in Zeile 33 geforderten 0,01% Fehler mit einem Nenner<=100 nicht erreichbar sind. Setzt man hier 1% erlaubten Fehler an, kriegt man auch wieder 1/2. Wenn man ja aber nur maximal zwei Nachkommatestellen an den Eingabewerten erlaubt, ist das egal. War nur drübergestolpert.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: madmax2010, kali-hi und tollertyp
simpsonsfan schrieb:
Trotzdem würde ich generell dazu raten, die Werte eben von Anfang an nicht auf zwei Nachkommastellen zu runden, sondern eine Rundung, soweit möglich, immer erst bei der Ausgabe vorzunehmen.
Mache ich ja auch nicht. Nur in der print-Funktion wird auf zwei Nachkommastellen gerundet.

Mit Python-Code kann man in JS nicht viel anfangen.

Eine fremde Lib. möchte ich nicht einsetzen... auch wenn es technisch auch ganz einfach ohne npm ginge, so muss man sich wieder mit dem Copyright beschäftigen usw.
Ergänzung ()

tollertyp schrieb:
Die Liste mit 100 Einträgen, also ein Mapping, ist vermutlich noch das sinnvollste, wenn man von den auf zwei Stellen gerundeten Zahlen ausgeht.
Wäre aber eine ziemlich große map... 100x100?
 
@kali-hi
Hier mal ein anderer und ganz pragmatischer Vorschlag:

Du behandelst gerundete und ungerundete Werte separat und fügst die rationale Zahl nur dann unmittelbar hinzu, wenn die Zahl nicht gerundet ist. Im Falle, dass eine Rundung vorliegt, kannst du anstelle der rationalen Zahl beispielsweise ein Icon zum Öffnen eines Hinweisfensters hinzufügen, das neben einer Beschreibung des "Problems" zunächst einmal die folgenden 4 Werte enthält:
  • die gerundete reelle sowie die anhand dessen ermittelte rationale Zahl
  • die ungerundete reelle Zahl sowie die anhand dessen ermittelte rationale Zahl

Optional kannst du dann noch einen "vermuteten" Wert hinzufügen, für den du beispielsweise eine Liste mit Fallunterscheidungen erstellst, und der nur dann angezeigt wird, wenn die Liste einen Treffer erzielt, der sich zugleich von den bereits ermittelten Werten unterscheidet.

Im Falle, dass die ungerundete Zahl die maximale Länge an Nachkommastellen des jeweiligen Datentyps (double?) erreicht und somit nicht sichergestellt werden kann, dass diese Zahl "korrekt" (oder beispielsweise selbst nur eine Approximation) ist, könnte das Fenster bzw. der Hinweis noch entsprechend erweitert werden.
Auch kannst du optional noch die Abweichung zwischen den gerundeten und den originalen Werten angeben. So ist es für den Nutzer maximal transparent, und er kann anhand der vorhanden Informationen selbst entscheiden, welche der jeweiligen Zahlen er verwenden möchte.

Auch dieser Entscheidungsprozess ließe sich theoretisch noch vereinfachen, indem man auch noch einen empfohlenen Wert anhand der persönlichen Präferenzen (bezüglich Genauigkeit und String-Länge) angibt, aber das würde womöglich den Bogen überspannen und wäre mit einem erheblichen Mehraufwand verbunden.

Somit hättest du alles clean, aber zugleich vollständig und mit nützlichen Informationen für den Nutzer. Ich persönlich habe mit einer ähnlichen Vorgehensweise vor einigen Jahren gute Erfahrungen gemacht, bei denen meine "Lösung" von den Nutzern sehr positiv angenommen wurde.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: madmax2010 und kali-hi
SaschaHa schrieb:
Du behandelst gerundete und ungerundete Werte separat und fügst die rationale Zahl nur dann unmittelbar hinzu, wenn die Zahl nicht gerundet ist.
Ich verstehe hier nicht, was mit "behandeln" gemeint ist.
Ergänzung ()

kali-hi schrieb:
Wäre aber eine ziemlich große map... 100x100?
Ähm... wieso?
Die Map würde so aussehen:
Code:
const fruits = new Map([
  ["00", ""],
  ["01", "1/100"],
  ["02", "1/50"],
  ["03", "3/100"],
 ...
  ["33", "1/3"],
  ...
  ["99", "99/100"]
]);
Wenn man die ganze Zahl als einen Bruch haben möchte, müsste man dann noch den Nenner raussplitten und den Ganzzahl-Anteil damit multiplizieren und zum Zähler addieren...

Woher kommt bei dir das "100x100"?
 
  • Gefällt mir
Reaktionen: madmax2010 und Nilson
@tollertyp So wie das jetzt gebaut ist wird doch 0.33 zu 1/3 umgebaut, oder missverstehe ich das? Es ist nicht abwegig, dass beim Gaußverfahren (das scheint hier der Kontext zu sein) 33/100 gerechnet wird. Da wird jetzt aber 1/3 draus gemacht, was nicht nur falsch, sondern auch stark irreführend ist z.B. für jemanden der die Lösung nachvollziehen möchte.

Das ganze ist ein komplettes Nicht-Problem mit schrecklicher Verschlimmbesserung. Das Anzeigen von Brüchen verweist auf symbolische Berechnungen. Numerisch zu rechnen, dann nochmals beliebig zu runden und das dann als Bruch darzustellen ist daher doppelt falsch.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: madmax2010

Ähnliche Themen

Zurück
Oben