Dakuan
Anmeldungsdatum: 2. November 2004
Beiträge: 6241
Wohnort: Hamburg
|
Ich benötige mal wieder einen Denkanstoß und hoffe, dass mir hier jemand diesen verabreichen kann. Es geht um folgendes: Ich habe ein Objekt, das Testergebnisse grafisch darstellen soll. Der "Auftraggeber" gibt dazu beispielsweise den gewünschten Wertebereich für die Y-Achse an, etwa so:
scope->scale( 5, 0, sample_rate/2, "4.0", "f/Hz", 0.95, 1.05, "3.2", "u" );
In diesem Beispiel ist für Y ein Wertebereich von 0.95 bis 1.05 angegeben (Die Format Strings sind hier noch fest vorgegeben). Der Wertebereich wird vom Objekt in 4 Abschnitte unterteilt. Aus dem angehängten Bild ist ersichtlich, das die Beschriftung der Y-Skala nicht stimmt, weil hier eine Nachkommastelle zu wenig ausgegeben wird. Da sollte z.B. statt 1.02 der Wert 1.025 stehen. Gibt es eine irgendwie geartete Möglichkeit, die Anzahl der benötigten Nachkommastellen zu ermitteln? Ich meine der Wertebereich und die Abstufungen sind ja begrenzt. Da sollte sich doch was machen lassen, aber ich habe noch keine Idee. Ich möchte dem Anwender meines Programms die Möglichkeit geben, den Wertebereich, den er betrachten möchte frei auszuwählen. Aber wenn ich dafür keine fehlerfreie Skalen Beschriftung hin kriege, kann ich das nicht machen.
- Bilder
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17433
Wohnort: Berlin
|
Das Intervall von 0.95 bis 1.05 ist ja 0.1 breit (oder hoch). Das willst du in 4 Intervalle teilen, also Intervalle zu je 0.025. Wenn ich das naiv in Scala nachstelle, welches auf Java aufsetzt und einen ähnlichen Umgang mit Floats/Doubles hat wie C oder C++: 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | scala> val low=0.95f
low: Float = 0.95
scala> val lo=0.95f
lo: Float = 0.95
scala> val hi=1.05f
hi: Float = 1.05
scala> val breite = hi - lo
breite: Float = 0.099999964
scala> val abschnitte=breite/4
abschnitte: Float = 0.024999991
|
Dann lande ich rasch bei Präzisionsverlusten. Ich kann auch nicht eine Stringrepresentation nehmen, und einfach nachzählen. Die Ausgangswerte haben 2 Nachkommastellen - kann man dann nicht einfach sagen, dass 4 Intervalle dann 3 Nachkommastellen benötigen (was u.U… eine zu viel ist, wenn das Intervall von 0.96 bis 1.04 ginge), bei über 10 braucht man eine mehr, bei über 100 zwei mehr? Genügt bei einem Intervall 0 - 2 und 3 Abschnitten 0.6, 1.3 oder braucht man 0.66/1.33 um die Periodizität mindestens anzudeuten? Je nach Divisionen ist wohl schlecht vorhersehbar, ab der wievielten Stelle Periodizität auftritt bzw. Ungenaugigkeiten der Floatdarstellung gerundet werden sollten. Kann man die Zahl der Nachkommastellen nicht auf den User abwälzen? ☺ Man verkauft ihm das als volle Kontrolle und er freut sich. ☺
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12536
|
Dakuan schrieb:
In diesem Beispiel ist für Y ein Wertebereich von 0.95 bis 1.05 angegeben (Die Format Strings sind hier noch fest vorgegeben). Der Wertebereich wird vom Objekt in 4 Abschnitte unterteilt. Aus dem angehängten Bild ist ersichtlich, das die Beschriftung der Y-Skala nicht stimmt, weil hier eine Nachkommastelle zu wenig ausgegeben wird. Da sollte z.B. statt 1.02 der Wert 1.025 stehen.
Aber wer entscheidet das? Abgesehen von dem Rundungsfehler kann ich nichts Falsches an der Ausgabe "1.02" erkennen.
Gibt es eine irgendwie geartete Möglichkeit, die Anzahl der benötigten Nachkommastellen zu ermitteln? Ich meine der Wertebereich und die Abstufungen sind ja begrenzt. Da sollte sich doch was machen lassen, aber ich habe noch keine Idee.
Wenn Du beantworten kannst, was "benötigt" meint, kannst Du es auch implementieren. user_unknown schrieb:
Kann man die Zahl der Nachkommastellen nicht auf den User abwälzen? ☺ Man verkauft ihm das als volle Kontrolle und er freut sich. ☺
Erscheint mir nicht als die schlechteste Lösung. Der einzige Algorithmus, der mir im Moment einfällt, geht so: Starte mit 0 als Anzahl Nachkommastellen Generiere alle Skalenbeschriftungen mit der aktuellen Anzahl Nachkommastellen. Wenn sich mindestens ein Wert wiederholt (kann man leicht mit einem Set oder einem Hash ermitteln), erhöhe die Anzahl der Nachkommastellen um 1 und gehe zu Schritt 2. Fertig
Da gehen natürlich gewisse Annahmen ein, was Du ggf. mit "falsch" meinst.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6241
Wohnort: Hamburg
|
Kann man die Zahl der Nachkommastellen nicht auf den User abwälzen? ☺ Man verkauft ihm das als volle Kontrolle und er freut sich. ☺
Das kann man natürlich machen, aber das macht die Bedienung unübersichtlicher.
Dann lande ich rasch bei Präzisionsverlusten.
Das Thema hatten wir schon ein oder zwei Wochen abgehandelt und das spielt hier keine Rolle. Ich will hier nur grobe offensichtliche Fehler vermeiden. Es soll halt plausibel aussehen. Für genaue Messungen nehme ich den Mauszeiger und ein Tooltip. Da ist die Formatierung dann egal. Geht ja nach loslassen der Maustaste wieder weg. Die Anzahl der Abschnitte in Y-Richtung habe ich absichtlich auf 4 festgelegt. Der Konstruktor sorgt dafür das alle Koordinatenbereiche durch 8 Teilbar sind. Damit wollte ich vermeiden, das meine Achsen zwischen die Pixel fallen.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6241
Wohnort: Hamburg
|
Das Problem lässt mir keine Ruhe. Ich habe jetzt aber mal folgenden, noch nicht ausgereiften, Denkansatz: Wenn ich standardmäßig von 2 Nachkommastellen ausgehe, könnte ich doch erstmal mit modf() den Nachkommaanteil ermitteln, diesen mit 100 multiplizieren und dann mittels round() den nächst passenden Wert ermitteln und daraus dann einen passenden Skalenwert, der ins vorgegebene Raster passt, zusammenbasteln. Die Skala währe dann allerdings nicht mehr gleichmäßig unterteilt. Die Frage ist dann, ob der Benutzer das akzeptieren kann oder will. Die Alternative währe, wenn sich nach der entsprechenden Multiplikation immer noch ein Nachkommaanteil ergibt, die Beschriftung einfach weg zu lassen. Um mal zu verdeutlichen, was mir ursprünglich vorgeschwebt hatte, hier mal ein Beispiel, wie ich versuche die horizontale Beschriftung vorzugsweise auf glatte Tausender, Hunderter oder Zehner zu legen.
| ...
// draw horizontal scaling -----------------------------------------------
scale_step = (xmax - xmin) / 8; // this factor should depend on sc_width
scale_step = scale_step < 1.0 ? 1.0 : scale_step;
scale_div = pow( 10, floor( log10(scale_step) ) );
scale_step = scale_div * floor( scale_step/scale_div );
...
|
leider funktioniert das nur mit Werten größer als 1.
|
ChickenLipsRfun2eat
Anmeldungsdatum: 6. Dezember 2009
Beiträge: 12067
|
Ich liebe ja solche Themen, aber ich bin Mathe leider ne gerundete Null ☺ Allerdings könnte es eventuell mit Grundschulmathematik schon klappen, das müsstest selbst testen.
Wahrscheinlich habe ich mich mathematisch wieder selbst ausgetrickst, aber ich komme damit bei 0,X5 auf nen "Restwert" von 0 und bei 0,X0 auf nen Restwert > 0. Den könnte man ja als simple if(Restwert)-Bedingung abfragen. Getestet habe ich das Ganze allerdings wenig programmiertechnisch in einer Calc-Tabelle ☺
Name | Bsp1 | Bsp2 | Bsp3 | Wert1 | 0,95 | 0,96 | 0,98 | Wert2 | 1,05 | 1,04 | 1,02 | Faktor | 4 | 4 | 4 | Step(Wert2-Wert1) | 0,025 | 0,020 | 0,010 | Float(Integer(Step*1000))%Step | 0,000000 | 0,020000 | 0,010000 |
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6241
Wohnort: Hamburg
|
... aber ich bin Mathe leider ne gerundete Null
Der Ausdruck gefällt mir. Ich hoffe dass Du dir den noch nicht als Wortmarke hast eintragen lassen 😉 Meine Mathe Kenntnisse sind im Bereich der höheren Mathematik auch irgendwann stark eingebrochen und dann nach etlichen Jahren wegen nicht Gebrauch eingerostet. Viele wichtige Dinge konnte ich bislang noch nicht wiederbeleben, aber daran arbeite ich noch. Aber die Tatsache, das Du meinem Gedankengang im wesentlichen folgen konntest, lässt mich vermuten, das dies ein möglicher Ausweg ist (auch wenn ich der letzten Zeile in der Calc-Table nicht ganz folgen kann). Übrigens, einen Teil der Zerlegungsschritte, die Du anführst erledigt die Funktion modf(), indem sie den Vorkomma- und Nachkomma- Anteil getrennt abliefert. In mir reift so langsam die Idee, das ich die Anzahl der Unterteilungen nicht fest vorgeben sollte, sondern nur den Mindestabstand der Markierungen in Pixels (der dann möglicherweise auch von der verwendeten Schriftgröße abhängig ist). So gesehen ist allerdings der Thread Titel nicht mehr treffend. Ich muss da nochmal eine Nacht drüber schlafen. Aber weitere Denkanstöße sind durchaus willkommen.
|
ChickenLipsRfun2eat
Anmeldungsdatum: 6. Dezember 2009
Beiträge: 12067
|
Dakuan schrieb: ... aber ich bin Mathe leider ne gerundete Null
Der Ausdruck gefällt mir. Ich hoffe dass Du dir den noch nicht als Wortmarke hast eintragen lassen 😉
Nein, den kannst du gerne haben ☺
Aber die Tatsache, das Du meinem Gedankengang im wesentlichen folgen konntest, lässt mich vermuten, das dies ein möglicher Ausweg ist (auch wenn ich der letzten Zeile in der Calc-Table nicht ganz folgen kann).
Letzte Zeile mal als Calc-Formel:
| A | B | 1 | Wert1 | 0,9 | 2 | Wert2 | 1,14 | 3 | Faktor | 4 | 4 | Step | =(B2-B1)/B3 | 5 | | | 6 | Formel: | =REST(RUNDEN(B4;3)*1000;B4) |
Bedeutet:
Der Step-Wert wird auf drei Nachkommastellen gerundet, dann mit dem Faktor 1000 multipliziert (gibt quasi den Integer). Danach wird Modulo dem Originalwert gerechnet
Übrigens, einen Teil der Zerlegungsschritte, die Du anführst erledigt die Funktion modf(), indem sie den Vorkomma- und Nachkomma- Anteil getrennt abliefert.
Das wäre dann der "Runden"-Teil, wobei der ja nicht einfach abschneidet, sondern auch rundet, fällt mir eben auf. Ich muss da nochmal eine Nacht drüber schlafen. Aber weitere Denkanstöße sind durchaus willkommen.
Das ist immer ne gute Idee ☺
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6241
Wohnort: Hamburg
|
Das wäre dann der "Runden"-Teil, wobei der ja nicht einfach abschneidet, sondern auch rundet, fällt mir eben auf.
Das habe ich aus der Doku jetzt nicht herauslesen können, werde das aber im Auge behalten. Ich versuche gerade die oben gepostete Methode zur automatischen Skalierung auch auf Nachkommastellen zu erweitern. Auf dem Papier und mit dem Taschenrechner scheint das auch zu funktionieren. Allerdings muss ich noch einen Weg finden, wie ich damit umgehe, wenn die Nulllinie außerhalb des Darstellungsbereichs liegt oder auch negative Werte auftauchen. Außerdem muss ich noch irgendwie den gewünschten Wertebereich mit dem gefundenen Raster synchronisieren. Im angeführten Beispiel sollten die Beschriftungen dann etwa bei:
erscheinen und die Enden nur als unbeschriftete Markierungen auftauchen. Vielleicht bekomme ich das am Wochenende noch irgendwie hin.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12536
|
Was ist denn an meinem Vorschlag so schlecht?
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6241
Wohnort: Hamburg
|
Möglicherweise nur dass ich den nicht richtig verstanden habe. So wie ich den verstanden habe, würde er bei meinem gezeigten Bild nach 2 Kommastellen nicht weiter machen, da sich in der letzten Stelle keine Ziffern wiederholen. Aber ich hatte ja schon angedeutet, das dies möglicherweise nicht der optimale Ansatz ist. Ich versuche jetzt so etwas wie eine optimierte Einteilung zu finden. Um meine momentanen Gedanken besser nachvollziehbar zu machen, hier mal etwas zum spielen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 | /* autoscale.c (demo version)
**
** gcc -Wall -lm -o autoscale autoscale.c
*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int num_steps = 4; /* Ausgangswert für die Skalenunterteilung (Empfehlung)
** sollte der Länge der Skala angepasst werden
*/
double scale( double min, double max, int * l, int * r )
{
double step_width;
double scale_unit;
int frac_cnt = 0;
step_width = (max - min) / num_steps; /* Ausgangswert */
while( step_width < 1.0 ) {
step_width *= 10.0;
frac_cnt++; /* Kommastellen zählen */
}
scale_unit = pow( 10, floor( log10( step_width ) ) );
/* neue Schrittweite berechnen */
step_width = scale_unit * floor( step_width / scale_unit );
if( frac_cnt ) {
step_width /= pow( 10.0, frac_cnt );
}
*l = (int)(floor( log10(max) )); /* kann auch negativ werden! */
*r = frac_cnt;
return step_width;
}
int main( int argc, char * argv[] )
{
double step;
double min;
double max;
int l_digits;
int r_digits;
char format_string[16];
char output_string[64];
if( argc != 3 ) {
printf( "Usage: %s min_value max_value\n\n", argv[0] );
exit( -1 );
}
min = atof( argv[1] );
max = atof( argv[2] );
if( min >= max ) {
printf( "Input error\n" );
exit( -1 );
}
step = scale( min, max, &l_digits, &r_digits );
if( l_digits < 1 )
l_digits = 1;
sprintf( format_string, "%%%d.%df", l_digits + r_digits, r_digits );
sprintf( output_string, "scale step is: %s\n", format_string );
printf( "format is: \"%s\"\n", format_string );
printf( output_string, step );
return 0;
}
|
Diese Methode würde allerdings 5 Intervalle zu 0.02 liefern und wenn man diese Intervalle stur anwendet, würde die 1.00 keine Beschriftung und keine Hilfslinie bekommen. Das finde ich unschön.
|