Rechnen mit map und unsigned long long

Hier im Forum habe ich erklärt bekommen, wie ich mit unsigned long long weiter rechnen kann.
Theoretisch zumindest.

Jetzt ist aber map() mit ihm Spiel und ich komme einfach nicht darauf, wo mein Fehler ist.
Wo muss ich welche Variablenart einsetzen? Mit Versuch und Irrtum komme ich auch nicht weiter.
Und kann ich innerhalb der map Funktion einen Mindeswert für das Ergebnis festlegen, oder nehme ich dazu eine If() Funktion?
In diesem Fall sollte Soll nicht kleiner werden als Sollmin,
Könnt Ihr mir mit diesen beiden Dingen auf die Sprünge helfen?

//sinkende Temperatur über lange Zeit
unsigned long long Rest   = 0;
unsigned long long Start  = 0;
unsigned long long Soll   =  0; 
unsigned long Sollmin     = 2400;    //24.00°C 
unsigned long Sollmax     = 2950;   //29.50°C 
unsigned long long Programmdauer = 15*(60*60*1000ULL); //15 Stunden
float Solltemperatur = 0;

void setup()  
    { 
      Serial.begin(9600);
      Serial.println("Serial Start");
      Start = millis(); // nur der Vollständigkeit halber
    }

void loop() 
{
  Rest = (Programmdauer - (millis()-Start));  
  Soll = map(Rest, 0, Programmdauer, Sollmin, Sollmax);
  Solltemperatur = Soll/100.00;
  Serial.println(Solltemperatur);
  delay(5000);
}

map arbeitet intern mit long.

die formel gibs in der Beschreibung. Kannst dir ja eine eigene Funktion machen für long long.

wobei überleg dir mal ob das wirkich in millisekunden sein muss. Rechne es in Sekunden aus und multiplizier mit 1000 wenn du dein delay od. ähnliches setzt.

Das ist die originale map-Funktion:

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

Wenn Du überall unsigned long long einsetzt und die Funktion unter neuem Namen erstellst. erhälst Du:

unsigned long long mapUL(unsigned long long x, unsigned long long in_min, unsigned long long in_max, unsigned long long out_min, unsigned long long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

Gruß Tommy

Bedeutet das, mit unsigned long oder unsigned long long kann ich in map() nichts machen?
Auf Sekunden hochbrechen wäre natürlich eine Möglichkeit.

Da kam die Antwort vor meiner Frage. Das probier ich gleich mal aus.

Ich verstehe nicht, was Du mit ULL da willst?
millis() ist ein 4-byte Wert.
In den passen 49 Tage.

Machst Du. In Deinem Fall ist Sollmin und Sollmax Deine Grenze.
Wenn Du weisst, dass Du die auslösende Grenze unter- bzw. überschreitest, passe das an.
Entweder in dem Du die jeweiligen Werte anpasst oder aber Du benutzt vorher bzw. nachher constrain()

map() arbeitet mit ganzzahligen Werten mit long (32 Bit), daher können Sie problemlos in Tausendstel (oder mehr) Grad arbeiten, wenn Sie möchten.

Jedoch führt die map-Funktion Berechnungen mit Multiplikationen durch, und daher könnte es innerhalb der map-Funktion zu einem Überlauf kommen ➜ Deshalb sollte eine map-Funktion verwendet werden, bei der die Parameter auf long long aufgewertet werden, um die Berechnung durchzuführen, und es wird ein long zurückgegeben.

Sie möchten in der map-Funktion kein unsigned long long verwenden: Wenn Sie eine Berechnung mit vorzeichenlosen und vorzeichenbehafteten Ganzzahlen durchführen, erfolgt die gesamte Berechnung mit vorzeichenlosen Ganzzahlen, was bei negativen Temperaturen zu Problemen führen könnte.

millis() ist ein uint32_t und das reicht aus, um ~50 Tage darzustellen. Solange Ihre Wartezeit also weniger als ~50 Tage beträgt, benötigen Sie kein uint64_t (unsigned long long). 15 Stunden oder 15 Tage sind überhaupt kein Problem.

Versuchen Sie das

// ---------- einige Definitionen ----------
constexpr long skala = 10000L;
constexpr unsigned long eineSekunde = 1000;                        // 1000 ms = 1 s
constexpr unsigned long long eineMinute = eineSekunde * 60;        // 60 s
constexpr unsigned long long eineStunde = eineMinute * 60;         // 60 min
constexpr unsigned long long einTag = eineStunde * 24;             // 24 Stunden

// EDITED AS PER @combie post 15
long mapL(long long x, long long in_min, long long in_max, long long out_min,  long long out_max) {
  return long((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
}

// ---------- Die Parameter (achten Sie auf die Typen) ----------
double startSoll = 24.00;                                          // 24.00°C
double endSoll   = 29.50;                                         // 29.50°C
constexpr unsigned long long programmDauer = eineStunde * 15;     // 15 Stunden

// ---------- DER CODE ----------
// Überprüfen, ob programmDauer kleiner ist als der größte darstellbare positive long-Wert auf 32 Bit
static_assert(programmDauer < 4294967295ull, "Die Programmdauer ist zu lang (> ~50 Tage), um auf 32 Bit dargestellt zu werden.");

constexpr unsigned long wahreProgrammdauer = programmDauer; // 32 bits
long wahreStartSoll = 24.00 * skala;
long wahreEndSoll   = 29.50 * skala;

unsigned long startZeit;
long vorherigesSoll = wahreStartSoll - 1;

void setup() {
  Serial.begin(115200);
  Serial.println("Serial Start"); Serial.flush();
  startZeit = millis();
}

void loop() {
  unsigned long aktuelleZeit = millis();
  long soll = mapL(aktuelleZeit - startZeit, 0, wahreProgrammdauer, wahreStartSoll, wahreEndSoll);
  if (soll != vorherigesSoll) {
    double sollTemperatur =  soll / double(skala);
    Serial.print((aktuelleZeit - startZeit) / eineSekunde);
    Serial.print(F("s\t"));
    Serial.println(sollTemperatur, 6);
    vorherigesSoll = soll;
  }
}

Ich habe den TO schon gefragt und frage Dich auch:
Wozu brauchst Du ULL?

Für den Fall, dass der Benutzer nach 50 Tagen fragen möchte und In diesem Fall wird der statische assert den Fehler behandeln.

Ich persönlich würde die Variablengröße nicht größer machen, als notwendig.
Die 49Tage-Grenze lässt sich doch bestimmt auch zur Laufzeit anders abhandeln.
Zumal ich keinen Zwang sehe Start und Stopp in ms angeben zu müssen.
Unixtime geht ja auch über 50 Tage :slight_smile:

Ich dachte, es hat Spaß gemacht.

Alle constexpr werden wahrscheinlich verschwinden, nachdem der Optimierer seine Arbeit erledigt hat, und nur constexpr unsigned long wahreProgrammdauer bleibt erhalten, sodass Sie wirklich nur ein unsigned long verwenden.

1 Like

Ich habe die verschiedenen Wege mal durchgespielt und beschlossen, alle Werte auf Minuten runter zu brechen. Dann wird die ganze Sache wieder machbar und frisst auch weniger Speicher.

Vielen Dank euch allen nochmal!
Tiff

Du kannst auch mit Sekunden rechnen und brauchst noch immer kein ULL!
Minuten machen UL, Sekunden machen UL.

Das LL, sollte das nicht ULL heißen?
Oder der Return Wert ebenfalls long long sein?
Ich sehe da eine Inkonsistenz.

Davon mal abgesehen:
Es ist nicht notwendig das (U)LL an den Bezeichner der Funktion zu hängen.
In C war das noch nötig.
In C++ können wir die Funktion ungestraft überladen.

C++ sucht sich selber die best passende Variante aus.
Kann es das nicht, meldet es das.

Die Aufrufparameter sind signed long long, es werden Differenzen gebildet, die also durchaus negativ werden können. Das Rechenergenbis wird auf signed long verkürzt und mit dem DatenTyp unsigned long zurückgeliefert.

Na ja, wenn es Spaß macht, soll's mir Recht sein.

Da das Ergebnis hier eigentlich ein double sein soll, kann man sich auch eine entsprechende map-Funktion bauen.
double map(long input, long in_min, long in_max, double out_min, double out_max);

Und --je nach Geschmack-- das gewünschte constrain gleich integrieren.
Wobei es philosophisch gesehen, böse ist wenn überladene Funktionen je nach Datentyp etwas ganz anderes machen. Wir wollen ja eigentlich templates bauen :slight_smile:

template<typename IN, typename OUT>
OUT map(IN val,IN in_min, IN in_max, OUT out_min, OUT out_max) {
  return( val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
};

void setup() {
Serial.begin(115200); 
Serial.println(
  map<unsigned long, float> (101, 50, 150, 2.0F, 3.0F)  // knapp über der Mitte 
     );  // liefert 2.51
}
void loop() { }

Sie haben recht, ich wollte schreiben

long mapL(long long x, long long in_min, long long in_max, long long out_min,  long long out_max) {
  return long((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
}

unter der Annahme, dass Sie nur long an die Funktionen übergeben und nicht long long.

Daher benötige ich einen neuen Namen, weil der Code long verwendet und durch die Standardmapfunktion verwirrt werden könnte. (Kann mich nicht auf die Überladung der Funktion verlassen)

Interessant!
Wenn ich ehrlich bin, möchte ich gar nicht wissen, was du damit meinen könntest.

Es tut mir leid, wenn mein Deutsch nicht gut genug ist.

Ich möchte sagen, dass die Signatur, die ich verwendet habe (unsigned long, int, long, long, long), nicht erlaubt hätte, meine spezielle Funktion mit der Signatur (long long, long long, long long, long long, long long) aufzurufen, wenn ich sie auch map() genannt hätte. Der Compiler hätte die eingebaute Funktion map() gewählt.

Das hätte zu einem Fehler geführt (Überlauf des long bei den Berechnungen der integrierten Funktion).

Hier ist ein Beispiel, um zu verdeutlichen, was ich meine.

// Diese Funktion wird nicht aufgerufen.
long map(long long x, long long in_min, long long in_max, long long out_min,  long long out_max) {
  Serial.println("Hallo von der benutzerdefinierten map Funktion");
  return long((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
}

long mapl(long long x, long long in_min, long long in_max, long long out_min,  long long out_max) {
  Serial.println("Hallo von der benutzerdefinierten MAPL Funktion");
  return long((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
}

void setup() {
  Serial.begin(115200);

  unsigned long aktuelleZeit = 650000;
  unsigned long startZeit = 0;
  long wahreProgrammdauer = 100000000;
  long wahreStartSoll = 23500;
  long wahreEndSoll = 35500;
  long soll = map(aktuelleZeit - startZeit, 0, wahreProgrammdauer, wahreStartSoll, wahreEndSoll);
  Serial.println(soll);

  long soll_l = mapl(aktuelleZeit - startZeit, 0, wahreProgrammdauer, wahreStartSoll, wahreEndSoll);
  Serial.println(soll_l);

}

void loop() {}

23493 <<=== Fehler
Hallo von der benutzerdefinierten MAPL Funktion
23578

Dein Problem ist nicht dein Deutsch!

Sondern, dass du KEINEN einzigen Wert in dem Datentype definiert hast, der die automatische Auswahl der Funktion ermöglichen würde!
Ein einziger würde schon reichen.... (damit du Warnungen bekommst)
Aber nein, das tust du nicht.
Drückst der automatischen Konvertierung alle Arbeit auf, und wunderst dich, dass sie nicht das tut, was du dir erwünscht. Dabei arbeitet sie doch voll Regelkonform.

Nein, dein Problem ist nicht dein deutsch, das verstehe ich, es ist eher dein etwas schlampiger Umgang mit Datentypen.

Klarer:
Du gibst dem Compiler keine Chance das richtige zu tun!

// Diese Funktion wird doch aufgerufen.
long map(long long x, long long in_min, long long in_max, long long out_min,  long long out_max) {
  Serial.println("Hallo von der benutzerdefinierten map Funktion");
  return long((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
}

void setup() {
  //Serial.begin(115200);
  Serial.begin(9600);

  unsigned long aktuelleZeit = 650000;
  unsigned long startZeit = 0;
  long long wahreProgrammdauer = 100000000;
  long long wahreStartSoll = 23500;
  long long wahreEndSoll = 35500;
  long soll = map(aktuelleZeit - startZeit, 0ull, wahreProgrammdauer, wahreStartSoll, wahreEndSoll);
  Serial.println(soll);

}

void loop() {}

Die Alternative:
Erzwinge, dass die automatische Konvertierung das richtige tut!

// Diese Funktion wird doch aufgerufen.
template<typename T> T map(T x, T in_min,T in_max, T out_min,  T out_max) {
  Serial.println(__PRETTY_FUNCTION__);
  return long((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
}

void setup() {
  //Serial.begin(115200);
  Serial.begin(9600);

  unsigned long aktuelleZeit = 650000;
  unsigned long startZeit = 0;
  long wahreProgrammdauer = 100000000;
  long wahreStartSoll = 23500;
  long wahreEndSoll = 35500;
  long soll = map<long long>(aktuelleZeit - startZeit, 0, wahreProgrammdauer, wahreStartSoll, wahreEndSoll);
  Serial.println(soll);

}

void loop() {}

Ich wundere mich über nichts, und ich habe die natürlichen Typen für die betrachteten Werte verwendet (unsigned long für die Zeit, long für die Temperaturen, da sie mit einem Skalierungsfaktor multipliziert werden).

Es ist die dümmste Idee, die ich seit Langem von einem erfahrenen Programmierer gelesen habe, einfach nur, weil er die Möglichkeit der Funktionsüberladung nutzen will oder vielleicht nicht erkennt, dass er falsch liegt...

Aber nun gut, jeder hat das Recht auf seine Meinung. Machen Sie, was Sie wollen, es ist mir völlig egal. Ich bleibe bei meiner Meinung, dass die Verwendung natürlicher Typen und die Vergabe eines spezifischen Namens für die Funktion der beste Weg ist ➜ keine Unklarheiten beim Lesen des Codes und vernünftige Verwendung des Speichers.

PS: Das Template könnte eine gute allgemeine Idee sein, die darauf hinausläuft, eine spezifische Funktion zu schreiben. Das erfordert jedoch, zu verstehen, dass die mathematischen Operationen innerhalb der Funktion mit "long long" durchgeführt werden müssen, obwohl wir nur 32-Bit-Variablen haben. Das ist nicht unbedingt offensichtlich. (Das war das Hauptproblem von @Tiff, der das Überlaufen innerhalb der Funktion "map" nicht erkannte.)