Forum: Mikrocontroller und Digitale Elektronik wert mappen, wie casten?


von Lars (Gast)


Lesenswert?

tach,
ich habe mal ne frage zum Code.

Ich habe einen Wert [0..100] und möchte diesen auf [0..250] mappen. Das 
ganze für einen AVR.

was wäre das bessere vorgehen?
1
static uint8_t mapToPWM(uint8_t speed)
2
{
3
  uint8_t speedPWM;
4
  if(100 > speed)
5
  {
6
    speed = 100;
7
  }
8
  speedPWM = (uint8_t)((float)speed * 2.5);
9
  speedPWM = (uint8_t)(((uint16_t)speed * 25U) / 10U);
10
  return speedPWM;
11
}

einmal caste ich speed auf float und berechne dann speedPWM.

beim zweiten mal umgehe ich float.

Welches wäre schneller/besser?

Wie kann man solche sachen rausfinden was schneller ist (auch gerne am 
PC).

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Lars schrieb:
> Ich habe einen Wert [0..100] und möchte diesen auf [0..250] mappen.
Diese Werte sind echt ungeschickt: 101 Werte werden auf 251 Werte 
abgebildet, das gibt einen echt üblen Teiler...

> Das ganze für einen AVR.
Ich würde hier sowas probieren:
1
speedPWM = (uint8_t)(((uint16_t)speed * 640) / 256);
Denn so eine unsigned Division durch 256 lässt sich vom Compiler super 
optimieren: er ignoriert einfach das untere Byte.

: Bearbeitet durch Moderator
von Georg G. (df2au)


Lesenswert?

Lars schrieb:
> if(100 > speed)
>   {
>     speed = 100;
>   }

Bist du sicher, dass es nicht (speed > 100) heissen sollte?

von Lars (Gast)


Lesenswert?

Lothar M. schrieb:
>> Ich habe einen Wert [0..100] und möchte diesen auf [0..250] mappen.
> Diese Werte sind echt ungeschickt: 101 Werte werden auf 251 Werte
> abgebildet, das gibt einen echt üblen Teiler...

0..100 sind prozentangaben, welche dann auf 0..255 gemapt werden sollen.
die letzten 5 habe ich mir gespart, damit ich entweder nur mit 2,5 
multiplizerien muss (anstelle von 2,55) und bei der zweiten methode nur 
mit 25 multiplizieren muss und mit 10 dividieren (anstelle von 255 und 
100.

Lothar M. schrieb:
> speedPWM = (uint8_t)(((uint16_t)speed * 640) / 256);
> Denn so eine unsigned Division durch 256 lässt sich vom Compiler super
> optimieren: er lässt einfach das untere Byte weg.

ah ok, das muss ich mir merken.

Georg G. schrieb:
> Bist du sicher, dass es nicht (speed > 100) heissen sollte?

ja, da hast du recht

von Lars (Gast)


Lesenswert?

Lars schrieb:
> Georg G. schrieb:
>> Bist du sicher, dass es nicht (speed > 100) heissen sollte?
>
> ja, da hast du recht

Bzw. ich wollte
(100 <= speed) schreiben. Somit wäre die Konstante auf der linken seite.
(wurde mir gesagt das man das so macht, um eine Zuweisung zu vermeiden.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Lars schrieb:
> die letzten 5 habe ich mir gespart
Warum?
Beim float kostet das überhaupt nicht mehr und bei deinem Ansatz musst 
du doch nur eine Stelle dazunehmen:

speedPWM = (uint8_t)(((uint16_t)speed * 255U) / 100U);

Und bei meinem Ansatz kommt dann halt

speedPWM = (uint8_t)(((uint16_t)speed * 653) / 256);

heraus.

Lars schrieb:
> Georg G. schrieb:
>> Bist du sicher, dass es nicht (speed > 100) heissen sollte?
> ja, da hast du recht
Ich würde darüber hinaus nicht den Eingangswert begrenzen, sondern den 
Ausgabewert:
1
static uint8_t mapToPWM(uint8_t speed)
2
{
3
  uint16_t h;
4
  if (speed>100) h = 65535;
5
  else           h = (uint16_t)speed * 655;
6
7
  return h/256;
8
}
Lustigerweise sieht man hier, dass eigentlich nicht 653, sondern 655 der 
richtige Faktor ist. Das kommt, weil eben nicht 100 "gerade mal so" 255 
ergeben muss, sondern am besten schon 255,99 wobei dann die ,99 eben 
einfach abgeschnitten werden.

Lars schrieb:
> (wurde mir gesagt das man das so macht, um eine Zuweisung zu vermeiden.
Das passiert aber bei "kleiner" und "größer" nicht, sondern nur bei 
"gleich":
1
if (speed = 100) ...
Aus diesem Grund kann man da so schreiben:
1
if (100 == speed ) ...
Und dann kommen die "vereinfachenden" Generalisten und sagen 
stumpfsinning:
"Die Konstante muss bei Vergleichen immer nach links!"

: Bearbeitet durch Moderator
von W.S. (Gast)


Lesenswert?

Lothar M. schrieb:
> Lars schrieb:
>> Ich habe einen Wert [0..100] und möchte diesen auf [0..250] mappen.
> Diese Werte sind echt ungeschickt: 101 Werte werden auf 251 Werte
> abgebildet, das gibt einen echt üblen Teiler...
>
>> Das ganze für einen AVR.

Beide Versionen sind auf einem 8 Bitter eher aufwendig.

Probiert es doch mal mit:
Wert:= Wert * 5;   // macht 0..500
oder
Wert:= (Wert << 2) + Wert; // macht auch 0..500
                           // d.h. (0..400)+(0..100)
Und dann
Wert:= Wert >> 1;  // macht 0..250

oder gar
Wert:= (Wert << 1) + (Wert >> 1);
// macht (0..200) + (0..50)

Das wär's dann. Und mit etwas Nachdenken paßt das gerade so in ein Byte, 
da braucht man nicht mal 16 Bit Arithmetik.

W.S.

von Klaus W. (mfgkw)


Lesenswert?

Und falls du genug Speicher hast und dafür mehr Geschwindigkeit haben 
willst, geht für sowas auch eine Tabelle:
1
     uint8_t map2PWM[101] = { 0, 3, 6, 8, und so weiter..., 252, 255  };
2
     ...
3
     speedPWM = map2PWM[speed]; // sicherstellen, dass 0<=speed<=100 !!!

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Lothar M. schrieb:
> Lustigerweise sieht man hier, dass eigentlich nicht 653, sondern 655 der
> richtige Faktor ist. Das kommt, weil eben nicht 100 "gerade mal so" 255
> ergeben muss, sondern am besten schon 255,99 wobei dann die ,99 eben
> einfach abgeschnitten werden.

Hast Du das ausgerechnet, ob das keinen truncation-error gibt (kenne 
kein deutsches Wort)? Klassisch wäre ja h = ((uint16_t)speed + 128) * 
654 / 256;, aber es gibt ja durchaus Kombination, wo der 
Trunkierungsfehler (gibt es das Wort?) verschwindet.

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

Walter T. schrieb:
> Hast Du das ausgerechnet, ob das keinen truncation-error gibt

Da das Ganze in Einerschritten vorgegeben wird und schlußendlich 2.5er 
Schritte draus werden sollen, hat man dank der Rasterung auf 
Einerschritte immer mit im Detail ungleichmäßig erscheinenden 
Schrittweiten zu tun. Da hilft garnix, außer dem Übergang auf float. 
Aber der ist hier ja nicht gefragt.

W.S.

von W.S. (Gast)


Lesenswert?

Klaus W. schrieb:
> Und falls du genug Speicher hast und dafür mehr Geschwindigkeit haben
> willst,...

Man müßte hier mal einen Kenner der AVR-Assemblerebene fragen, ob der 
Zugriff auf ein Array nicht eventuell einen Tick langsamer ist als 2 
Verschiebungen und eine Addition - und beides in 8 Bit.

W.S.

von Klaus W. (mfgkw)


Lesenswert?

Der Kenner heißt gcc.
Mit gewünschter Optimierung kompilieren, und Code anschauen.

Mein Prognose, was am schnellsten ist,  will ich noch nicht verraten ...

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Walter T. schrieb:
> Hast Du das ausgerechnet, ob das keinen truncation-error gibt
Ja, da muss man aufpassen. Aber generell muss hier ja nichts gerundet 
werden, weil sich auf jede Art immer irgendein Stolperer im Verlauf 
ergibt.

W.S. schrieb:
> schlußendlich 2.5er Schritte draus werden sollen
Eigentlich sollen da korrekterweise 2,48er Schritte draus werden, denn 
wenn 101 mögliche Werte auf 251 mögliche Werte abgebildet werden sollen, 
dann kommt da 251/101 = 2,48 heraus. Mit dieser 0 am Anfang, die ja auch 
einen "Wert" hat (sonst bräuchte man sie nicht), tun sich Programmierer 
augenscheinlich irgendwie schwer.

Und ganz eigentlich sollte der Wertebereich ja auf 0..255 aufgeweitet 
werden und nur wegen der komplizierten Rechnung wurde auf die "letzten 
5" verzichtet.

von W.S. (Gast)


Lesenswert?

Lothar M. schrieb:
> Eigentlich sollen da korrekterweise 2,48er Schritte draus werden,...

aber so ein Wert ist etwas weniger gut in Integer ausdrückbar. Also 
schenken wir uns das.

W.S.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> aber so ein Wert ist etwas weniger gut in Integer ausdrückbar. Also
> schenken wir uns das.
Mir ist es im Prinzip schnurz, ob das Ding läuft oder nicht. Ich will 
einfach darauf hinweisen, dass man sich doch besser vorher Gedanken zu 
Wertebereichen machen sollte, damit sich der Rechner hinterher leichter 
tut. Der ist nun mal voll super in binären Dingen und schon das 
menschliche Zehnersystem macht ihm zu schaffen.

Oder man macht es wie der TO Lars, schneidet der Einfachheit halber 
hinten halt auch noch 5 ab und nimmt das Ganze als gottgegeben hin. 
Hauptsache "Meine Software läuft!" und ganz agil "Gut genug reicht aus!"

> Also schenken wir uns das.
Trotzdem macht sich Lars aber Gedanken ums Thema und ich zeige ihm den 
Tellerrand. Ob er drüber hinausschauen will, das liegt an ihm.

: Bearbeitet durch Moderator
von Εrnst B. (ernst)


Lesenswert?

Klaus W. schrieb:
> willst, geht für sowas auch eine Tabelle:

+1.

der Variablenname "speed" lässt darauf schließen, dass es wohl um einen 
Motor geht, der hinter der PWM hängt.
Dessen "Geschwindigkeit" hängt ganz sicher nicht linear vom PWM-Tastgrad 
ab, vermutlich läuft er bei den niedrigsten Werten noch nicht mal los.

mit der Tabelle kannst du das so hinbiegen, dass deine Stellwerte 0…100% 
auch ungefähr den prozentualen Motor-U/min entsprechen.

von HildeK (Gast)


Lesenswert?

Lothar M. schrieb:
> Das passiert aber bei "kleiner" und "größer" nicht, sondern nur bei
> "gleich":
1
if (speed = 100) ...
> Aus diesem Grund kann man da so schreiben:
1
if (100 == speed ) ...
Ergänzend möchte ich dem TO noch sagen, dass im ersten Fall immerhin 
eine Warnung kommt, z.B. derart (so mein WinAVR2010):

  warning: suggest parentheses around assignment used as truth value

denn der Compiler erkennt, dass diese Abfrage immer 'true'(bzw. 'false', 
bei der Abfrage auf Null) liefern wird und hält es für wahrscheinlich, 
dass man es so nicht meinte/wollte. Ein 'if' ist in dem Zusammenhang 
meist wenig sinnvoll ...

Während bei der anderen Schreibweise, die fälschlicherweise dann so 
aussieht:
1
if (100 = speed ) ...
führt das zu einem Error, denn einer Zahl ist kein Wert zuweisbar und 
damit ist der Code nicht kompilierbar.

Wenn man also einigermaßen diszipliniert ist und sich die Warnungen 
anschaut und diese beseitigt, dann fällt das auch bei der 'normalen' 
Schreibweise auf.

von jo (Gast)


Lesenswert?

Klaus W. schrieb:
> Der Kenner heißt gcc.
> Mit gewünschter Optimierung kompilieren, und Code anschauen.
>
> Mein Prognose, was am schnellsten ist,  will ich noch nicht verraten ...

So ist es! Optimiert wird ganz am Schluss - notfalls nimmt man einen 
schnelleren Prozessor.

Was man auf einen Controller (8bit) aber auf jeden Fall vermeiden sollte 
ist float oder gar double.

Aber auch eine Division ist recht teuer. Wenn es irgendwie geht, bildet 
man diese auf eine Multiplikation ab.

Ich musste mal Minuten auf ein Byte aufdehnen, also mit 256/60 
multiplizieren. Mein damaliger Kommentar hierzu:

// Skalierung *256/60 = 128/30 = 64/15 (= 4.2666...)
//
// Näherungsweise kann auch mit dem Wert 68/16 = 17/4
// (= 4.25 -> 0.392% Fehler) gerechnet werden.
// Die notwendige Divison lässt sich dann als >>2 sehr schnell
// ausführen.

Hab's dann aber mit einer Tabelleninterpolation realisiert (ich musste 
da noch was entzerren...).

Tabelleninterpolationen realisiert man vorteilhafterweise mit 9, 17 oder 
33 Stützstellen entsprechend 8, 16 oder 32 Intervallen. Denn dann 
vereinfacht sich die notwendige Division auch auf einfaches schieben. 
Aber  das wissen heute schon die Compiler.

von jo (Gast)


Lesenswert?

Ach ja, zum Thema "casten" wollt ich noch was sagen...

Casts in einem C-Programm sind der fast sichere Hinweis, dass irgend was 
ganz mächtig aus dem Ruder läuft. Nicht umsonst schmeißt da der Compiler 
eine Warning. Definiert man seine Datentypen richtig (und nicht 
irgendwie) brauchts keinen Cast, oft ist der auch noch so was von falsch 
(z.B. cast: int -> float).

Anbei noch der Beispiel-Code einer Tabelleninterpolation:
1
// I_table[] beschreibt die Sensor-Kennlinie.
2
3
int I_table[17] = {
4
  -355, -237, -119, -38, 29, 88, 143, 197, 251, 307, 368,
5
  436, 515, 615, 756, 1012, 1268
6
};
7
8
/**
9
* \brief    Konvertiert das ADC Ergebnis in einen Temperaturwert.
10
*
11
* \param    adc_value  Das gewandelte ADC Ergebnis
12
* \return              Die Temperatur in 0.1 °C
13
*/
14
15
int I_ADC2Temp(unsigned char adc_value){
16
 
17
  int p1,p2;
18
19
  /* Stützpunkt vor und nach dem ADC Wert ermitteln. */
20
  p1 = I_table[ (adc_value / 16) ];
21
  p2 = I_table[ (adc_value / 16) + 1 ];
22
 
23
  /* Zwischen beiden Punkten linear interpolieren. */
24
  return p1 + ( (p2-p1) * (adc_value & 0x0F) ) / 16;
25
};

von Uwe B. (Firma: TU Darmstadt) (uwebonnes)


Lesenswert?

Was spricht gegen
return (2 * speed + speed / 2)

von Klaus W. (mfgkw)


Lesenswert?

zu einfach!

von jo (Gast)


Lesenswert?

Uwe B. schrieb:
> Was spricht gegen
> return (2 * speed + speed / 2)

Ist ja sowas von aaalt, kuck da:

W.S. schrieb:
> oder gar Wert:= (Wert << 1) + (Wert >> 1);

von Sheeva P. (sheevaplug)


Lesenswert?

Lars schrieb:
> Welches wäre schneller/besser?

Was der Compiler daraus macht. Der ist nicht halb so dumm wie Du (und 
ich, nicht daß das jetzt der Punkt wäre).

Deine Frage betrifft die klassischen Prinzipien von "premature 
optimization is the root of all evil" (Donald E. Knuth) und "measure, 
don't guess" (Kirk Pepperdine).

Mein Rat an Dich ist mit dem Unfug aufzuhören. Beherzige die klassischen 
Tipps: "make it work, make it right, make it fast" (Kent Beck). Diese 
Reihenfolge.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Uwe B. schrieb:
> Was spricht gegen
> return (2 * speed + speed / 2)
Dagegen spricht eigentlich nur, dass die Skalierung eigentlich von 
0..100 auf 0..255 geschehen sollte und Lars zur "Vereinfachung" gleich 
mal 5 Inkremente weggelassen hat, denn
Lars schrieb:
>>> 0..100 sind prozentangaben, welche dann auf 0..255 gemapt werden sollen.
>>> die letzten 5 habe ich mir gespart, damit ich entweder nur mit 2,5
>>> multiplizerien muss
Und wenn man mit Murks anfängt, dann kommt auch nach einer optimalen 
Optimierung nur optimierter Murks heraus.

Was ich hier eigentlich laufend sagen will:
das Problem ist hier, dass Lars zwischendurch zum vereinfachten Rechnen 
seine allzu menschlichen 0..100 Prozent sehen will. Das macht die Arbeit 
für den µC dann eben umständlich.
Sinnvollerweise sollte er schon vorher nicht mit den auf dem 
Zehnfingersystem basierenden 0..100% herumrechnen, sondern gleich mit 
den binären 0..255, die dem µC so richtig reinlaufen.

jo schrieb:
> Anbei noch der Beispiel-Code einer Tabelleninterpolation:
Sowas ist super geeignet zum Umbiegen von nichtlinearen Frunktionen. 
Allerdings heben wir hier eine ganz einfache völlig lineare Funktion. In 
deinem Sinn also eigentlich eine Tabelle, die nur 2 Einträge 0 und 255 
hat, zwischen denen interpoliert wird. Aber eben ungeschickterweise mit 
einer Zehnerpotenz (statt wie bei dir mit einer Zweierpotenz):
1
  /* Tabellenstützpunkte */
2
  p1 = 0;
3
  p2 = 255;
4
5
  /* Zwischen beiden Punkten linear interpolieren. */
6
  return p1 + ((p2-p1) * speed) / 100;

: Bearbeitet durch Moderator
von Walter T. (nicolas)


Lesenswert?

Lothar M. schrieb:
> Was ich hier eigentlich laufend sagen will:
> das Problem ist hier, dass Lars zwischendurch zum vereinfachten Rechnen
> seine allzu menschlichen 0..100 Prozent sehen will

Jetzt bin ich allerdings neugierig: Bei mir sind die Stellen, bei denen 
solche Zahlen vorkommen, GUI und Konfigurationsdaten (.ini-Datei oder 
Einstellungsmenü). Meist in .5%- oder .1%-Schritten verstellbar.

Wie machst Du das? Mutest Du dem Benutzer Binärprozente zu, oder 
schluckst Du die Kröte, das Menschen Dezimalwerte intuitiver handhaben 
können?

: Bearbeitet durch User
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Walter T. schrieb:
> Wie machst Du das? Mutest Du dem Benutzer Binärprozente zu, oder
> schluckst Du die Kröte, das Menschen Dezimalwerte intuitiver handhaben
> können?
Am UserInterface darf der Bediener gerne seine geliebte 
Zehnfinger-Arithmetik sehen. Aber das wird im Programm gleich ins 
passende maschinenfreundliche Binärformat gewandelt. Und da sind dann 
100% eben 255 oder 65535 oder sonst welche 2^n-1 Werte.

> Mutest Du dem Benutzer Binärprozente zu
Bei Sachen, die sowieso nicht intuitiv erfassbar oder messbar sind (wie 
z.B. Zeitkonstanten, die man nicht mit der Stoppuhr messen kann oder 
Schaltschwellen, die man nicht mit dem Multimeter oder einem Maßband 
messen kann) da geht der Wertebereich dann durchaus auch mal von 
dimensionslosen 0..255 oder 0..1023.

Wenn aber an dieser obigen "speed" z.B. nur ein Poti als Eingabeelement 
am ADC hängt, dann ist es unsinnig, den 10-Bit ADC-Wert in 0..100% 
umzurechnen und dann die 0..100% wiederum in einem 8-Bit PWM-Wert.
Da hätte man mit ein wenig Nachdenken leichter die unteren 2 Bit 
abgeschnitten und den ADC-Wert direkt an die PWM gegeben.

: Bearbeitet durch Moderator
von jo (Gast)


Lesenswert?

Lothar M. schrieb:
> jo schrieb:
>> Anbei noch der Beispiel-Code einer Tabelleninterpolation:
> Sowas ist super geeignet zum Umbiegen von nichtlinearen Frunktionen.
> Allerdings heben wir hier eine ganz einfache völlig lineare Funktion. In
> deinem Sinn also eigentlich eine Tabelle, die nur 2 Einträge 0 und 255
> hat, zwischen denen interpoliert wird. Aber eben ungeschickterweise mit
> einer Zehnerpotenz (statt wie bei dir mit einer Zweierpotenz):
>  /* Tabellenstützpunkte */
>   p1 = 0;
>   p2 = 255;
>   /* Zwischen beiden Punkten linear interpolieren. */
>   return p1 + ((p2-p1) * speed) / 100;


Das hatte ich gezeigt, auch das WIESO, weil ich noch was entzerren 
musste.

jo schrieb:
> Hab's dann aber mit einer Tabelleninterpolation realisiert (ich musste
> da noch was entzerren...).

Im meinem Beitag davor (eben #6862088) hatte ich gezeigt, wie man ganz 
allgemein Näherungslösungen entwickeln kann - z.B. indem man statt mit 
255/100 mit 163/64 (~ 0,3% Fehler) rechnet - genau so, wie von Dir in 
der allerersten Antwort schon beispielhaft gezeigt.

Was ich ganz sicher NICHT tun werde, ist schlüsselfertige Lösungen für 
Lars' "Hobbykram" zu entwickeln.

just my 2 ct

von W.S. (Gast)


Lesenswert?

Uwe B. schrieb:
> Was spricht gegen
> return (2 * speed + speed / 2)

Mathematisch nichts, da könnte man auch Wert:= Wert * 2.55; schreiben.
Liest sich auch recht kurz.

Aber:
Das Ganze soll ein Aufspreizen von 0..100 auf 0..250 (eigentlich 0.255) 
sein, was bei einem Integer Ergebnis eben immer zu Rundungen führt. 
Eigentlich sehe ich beim Anliegen des TO nur das Übersetzen von einer 
Prozentzahl in
"aus-wenig-viel-ganz viel"
Mehr nicht.
Und was bei dir dagegen spricht, ist die Division. Die kostet auf einem 
8 Bit Controller viel mehr, als sie im vorliegenden Falle nützt.

W.S.

von Walter T. (nicolas)


Lesenswert?

W.S. schrieb:
> Und was bei dir dagegen spricht, ist die Division. Die kostet auf einem
> 8 Bit Controller viel mehr, als sie im vorliegenden Falle nützt.

Lassen wir die Kirche im Dorf. Division durch zwei ist billig. Selbst 
wenn 2 aus irgendeinem Grund volatile sein sollte.

von Hannes (Gast)


Lesenswert?

W.S. schrieb:
> Und was bei dir dagegen spricht, ist die Division. Die kostet auf einem
> 8 Bit Controller viel mehr, als sie im vorliegenden Falle nützt.

jo schrieb:
> vereinfacht sich die notwendige Division auch auf einfaches schieben.
> Aber  das wissen heute schon die Compiler.

Frage:
Und wenn man dem Compiler das nicht zu traut, schreibt man dann statt 
Uwe's "speed / 2" nicht besser gleich "speed >> 1" - so wie von W.S. 
vorgeschlagen?

W.S. schrieb:
> Und dann Wert:= Wert >> 1;

von Rolf M. (rmagnus)


Lesenswert?

Lothar M. schrieb:
> W.S. schrieb:
>> schlußendlich 2.5er Schritte draus werden sollen
> Eigentlich sollen da korrekterweise 2,48er Schritte draus werden, denn
> wenn 101 mögliche Werte auf 251 mögliche Werte abgebildet werden sollen,
> dann kommt da 251/101 = 2,48 heraus.

Aus 100 soll 250 werden. 100 * 2,48 ist aber nur 248. Das Ergebnis wäre 
also falsch. Der Fehler liegt hier darin, dass du auf der einen Seite 
die Anzahl der Werte nimmst, daraus dann aber die Größe der Schritte 
ableitest. Bei 101 Werten gibt es aber nur 100 Schritte. Daher muss hier 
jeder Schritt um Faktor 2,5 vergrößert werden.

> Mit dieser 0 am Anfang, die ja auch einen "Wert" hat (sonst bräuchte man
> sie nicht), tun sich Programmierer augenscheinlich irgendwie schwer.

QED ;-)

von W.S. (Gast)


Lesenswert?

Rolf M. schrieb:
> 100 * 2,48 ist aber nur 248. Das Ergebnis wäre
> also falsch.

Ähem, nein. Nicht wirklich. Bedenke mal, daß wir hier lediglich ein 
nicht sonderlich feines Einer-Raster von 0 bi 100 auf ein Einer-Raster 0 
bis 250 (oder 0 bis 255) abbilden wollen.

Daß dabei die jeweilige Schrittweite bzw. Grobheit so etwa 2.5 beträgt, 
bedeutet eben auch, daß mit eben dieser Grobheit 248 recht gut für 250 
durchgeht, es ist ja nur eine tatsächliche Abweichung von 2.0 und die 
liegt unter der Grobheit von 2.5 bzw. 2.48.

Also lassen wir hier die Haarspalterei, denn im Grunde ist das Ganze nur 
eine Einstellsache nach dem Motto "ein bissel mehr als dreiviertel" und 
keine mathematisch exakte Angelegenheit.

W.S.

von Rolf M. (rmagnus)


Lesenswert?

W.S. schrieb:
> Rolf M. schrieb:
>> 100 * 2,48 ist aber nur 248. Das Ergebnis wäre
>> also falsch.
>
> Ähem, nein. Nicht wirklich. Bedenke mal, daß wir hier lediglich ein
> nicht sonderlich feines Einer-Raster von 0 bi 100 auf ein Einer-Raster 0
> bis 250 (oder 0 bis 255) abbilden wollen.

Es entspricht jedenfalls nicht dem initialen Wunsch, auf 0-250 
abzubilden.

> Daß dabei die jeweilige Schrittweite bzw. Grobheit so etwa 2.5 beträgt,
> bedeutet eben auch, daß mit eben dieser Grobheit 248 recht gut für 250
> durchgeht, es ist ja nur eine tatsächliche Abweichung von 2.0 und die
> liegt unter der Grobheit von 2.5 bzw. 2.48.

Aber wozu auf 2,48 verfälschen?
Frage: Wenn der Bereich statt auf 0-250 auf 0-300 gemappt werden müsste, 
würdest du da 3 oder 2,98 als Faktor nehmen und warum? (Dass sich Faktor 
3 leichter berechnen lässt, da Integer-tauglich mal außen vor gelassen)

> Also lassen wir hier die Haarspalterei, denn im Grunde ist das Ganze nur
> eine Einstellsache nach dem Motto "ein bissel mehr als dreiviertel" und
> keine mathematisch exakte Angelegenheit.

Solange klar ist, dass dann nicht auf die angedachten 0-250, sondern auf 
0-248 gemappt wird und dass das keinerlei Vorteil bietet.
Auch wenn es für diesen Anwendungsfall wohl nicht so wichtig ist, halte 
ich es für sinnvoll, darauf hinzuweisen, dass der Faktor 2,48 für das 
gewünschte Mapping mathematisch nicht korrekt ist. Das wäre er auch dann 
nicht, wenn der Ergebnistyp alle Werte exakt darstellen könnte.

von W.S. (Gast)


Lesenswert?

Rolf M. schrieb:
> Aber wozu auf 2,48 verfälschen?
> Frage:

Also erstens: deine Frage gehört hier nicht hin, denn schließlich hat 
der TO eine Aufspreizung der Prozentzahlen von 0 bis 100 auf den Bereich 
eines Bytes vor und er hat zum Zwecke der Vereinfachung sich auf 0..250 
zurückgenommen.

Und zweitens: dieses 2.48 kommt von Lothar. Der hatte nämlich im Sinn, 
die Aufspreizung auf 255 anzuzielen.

Und eigentlich ist das Thema bereits komplett erledigt und was hier noch 
kommen kann, ist Haarspalterei, denn die erzielbare Schrittweite bzw. 
Grobheit ist hier in jedem Falle so etwa 2.5, was ja als solches nicht 
integer darstellbar ist.

W.S.

von Rolf M. (rmagnus)


Lesenswert?

W.S. schrieb:
> Rolf M. schrieb:
>> Aber wozu auf 2,48 verfälschen?
>> Frage:
>
> Also erstens: deine Frage gehört hier nicht hin, denn schließlich hat
> der TO eine Aufspreizung der Prozentzahlen von 0 bis 100 auf den Bereich
> eines Bytes vor und er hat zum Zwecke der Vereinfachung sich auf 0..250
> zurückgenommen.

Lothar hat behauptet, der Faktor müsse 2,48 betragen. Ich hatte 
lediglich angemerkt, dass seine Rechnung, mit der er darauf kommt, nicht 
stimmt. Dem hast du widersprochen. Und das ist erst mal ganz unabhängig 
davon, ob der TO nun auf 250 oder 255 skalieren will und ob er das nun 
"so ungefähr" oder genau will.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Lothar hat behauptet, der Faktor müsse 2,48 betragen.
Stimmt, aber es ist auch nicht ganz so einfach...

> Ich hatte lediglich angemerkt, dass seine Rechnung, mit der er darauf
> kommt, nicht stimmt.
Der Witz ist hier, dass ja tatsächlich diskrete 101 Werte (0..100 sind 
101 Werte) auf 251 Werte (0..250 sind 251 Werte) abgebildet werden 
sollen. Insofern stimmt meine Rechnung mit 101/251 im Grunde schon. 
Allerdings habe ich nicht fertig gedacht.

Wenn wir also mal den Eingangsbereich als feiner abgestuft mit 0,0 .. 
100,0 anschauen würden, dann hätten wir statt der 101 Werte auf einmal 
1001 Werte:
0,0 .. 0,1 .. 0,2 .... 99,8 .. 99,9 .. 100,0
Und auf der Ausgangsseite wären das dann 2501 mögliche Werte:
0,0 .. 0,1 .... 249,8 .. 249,9 .. 250,0
Das ergibt dann einen Faktor von 2501/1001 = 2,4985.

Oder wenn wir gar 2 Nachkommastellen mit anschauen, dann bekommen wir 
10001 Werte eingangseitig und 25001 Werte ausgangsseitig: 2,49985.

Eine Grenzwertbetrachtung liegt nahe und bringt den bekannten Faktor 
2,49999...99 oder eben gleich 2,5.

Aber wenn wir jetzt mal ansehen, was bei dieser Rechnung mit 2 
Nachkommastellen herauskommen kann, dann wird es interessant. Sehen wir 
mal als Resultat die 0 an: 0,01 ergibt bei der Umwandlung nach integer 0 
genauso wie 0,22 oder 0,76 oder 0,9999, weil bei der Umwandlung von 
float nach integer die Nachkommastellen einfach abgeschnitten werden.

Es gibt aber dank der "Obergrenze" von 250,00 eben kein 255,01 und auch 
kein 255,22 und auch kein 255,99, das "zurechtgestutzt" werden müsste.

Der Witz ist also die Asymmetrie dieser 101 bzw 251 Werte, denn der 
jeweils "höchste" Wert ist singulär. Es gibt nur 1 einzige Art, ihn zu 
erreichen: nur von genau 100,00000 kommt man auf genau 250,00000

Und Obacht: jetzt verlasse ich die 0..250-Welt.


Ich bin also selbst über den OffByOne-Stein gestolpert, denn eigentlich 
will der TO die 0..100 auf 0..256(kein Tippfehler!!) skalieren. Und 
falls da dann wirklich 256 herauskommt, dann muss man dieses eine 
Ergebnis auf hardwaretaugliche 255 zurechtstutzen.

Man tut sich da jetzt auf den ersten Blick etwas schwer, aber es wird 
einfacher, wenn man mal 0...1000 auf 0..255 skalieren will:
1
E    A
2
0 -> 0
3
1 -> 0
4
2 -> 0
5
3 -> 0
6
4 -> 1
7
5 -> 1
8
6 -> 1
9
:
10
:
So weit so klar...

Und jetzt am anderen Ende: was soll mit dem Wert 999 passieren?
Ergibt das dann  noch 254 oder schon 255?
Kann nur der Wert 1000 den Wert 255 ergeben, so wie hier:
1
:
2
993 -> 253
3
994 -> 253
4
995 -> 253
5
996 -> 254
6
997 -> 254
7
998 -> 254
8
999 -> 254
9
1000 -> 255 (nur auf 1 Art erreichbar??)

Oder sollte es nicht eher so aussehen:
1
:
2
993 -> 254
3
994 -> 254
4
995 -> 254
5
996 -> 255
6
997 -> 255
7
998 -> 255
8
999 -> 255
9
1000 -> 255 (eigentlich 256, aber Begrenzung auf hardwaretaugliche 255!!)

Rolf M. schrieb:
> Aus 100 soll 250 werden.
Genau das "wünscht" sich der To, es ist aber entweder a) fraglich, oder 
eben b) eigentlich falsch.
Denn wenn mit der richtigen Steigung gerechnet werden müsste, wäre dann 
im TO-Fall für 0...100 -> 0...250 nicht sogar der Faktor 2,50999 
richtiger (oder eben 2,51 mit anschließender Begrenzung auf den 
Maximalwert 250)?

: Bearbeitet durch Moderator
von W.S. (Gast)


Lesenswert?

Leute, laßt mal das Haarespalten.

Ich mach da nen Vorschlag zur Güte:
1
if (Wert < 100)
2
    Wert = (Wert << 1) + (Wert >> 1);
3
else
4
    Wert = 255;
Das kostet nur wenig Code und reicht für den (vermuteten) Zweck völlig 
aus.

W.S.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> Ich mach da nen Vorschlag zur Güte:
Der passt gut, wenns schnell gehen muss.

Aber ich würde auch gleich noch die Spreizung auf "fast" 255 reinpacken:
Erg = Wert + Wert + Wert/2 + Wert/16;
Oder eben den entsprechenden (Wert>>4).
Das gibt dann für 99 das Ergebnis 253, und der Grenzwert 100 wird ja 
explizit mit 255 abgevespert.

SCNR ;-)

von Walter T. (nicolas)


Lesenswert?

Lothar M. schrieb:
> Erg = Wert + Wert + Wert/2 + Wert/16;

Da werfe mir nochmal jemand overengineering vor. Der Ansatz gefällt mir.

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.