Méthode print() d'un entier sur un nombre de chiffres fixes

Hop

J'en avais un peu marre de cette fonctionnalité manquante dans la méthode print() fournie avec l'IDE.

Ca me pique les doigts de polluer mes programmes avec des trucs comme

//print sur 3 chiffres
if (nombre < 100) Serial.print('0'); 
if (nombre <  10) Serial.print('0'); 
Serial.print(nombre);

Alors maintenant je fais

#include "printInt.h"
...
Serial.print(I(nombre, 3));

C'est tellement anecdotique que cela ne mérite pas sa place dans les tutos, mais si ça peut servir à d'autres, cette petite lib est en pièce jointe.

Codé vite fait ce soir, ça a l'air de bien fonctionner.
Ca marche aussi avec LCD.print(...), client.print(...), nimportequoi.print(...)

printInt.zip (1.59 KB)

1 Like

Tres bonne idée :slight_smile:

Merci pour le partage :slight_smile:

Comme le code est petit, vous auriez pu le poster directement ici non? (Difficile de lire un zip sur smartphone)

J’ai une demande d’amélioration: si je veux mettre des espaces au lieu d’un 0 devant, comment je fais ?

Avez vous envisagé d’étendre la classe Print ?

Belle initiative. Merci
Est il possible de prévoir un affichage avec des espaces entre les milliers ?

On leur donne la main, ils veulent le bras :grin:

Je vais m'y mettre moi aussi : et pourquoi pas un printf ?
J'ai cru comprendre que c'est gourmand en mémoire mais printf existe dans Mbed avec des micro qui n'ont pas plus de mémoire qu'un avr.

Alors ?

Alors voilà les sources en clair

#ifndef printInt_h
#define printInt_h

#include <stdint.h>
#include <Printable.h>

class printInt : public Printable {
  private :
    bool _neg;
    uint32_t _value;
    uint8_t _digits;
    void setSigned(int32_t value, uint8_t digits);
    void setUnsigned(uint32_t value, uint8_t digits);
  public :
    printInt(int16_t  value, uint8_t digits) {setSigned(value, digits);}
    printInt(int32_t  value, uint8_t digits) {setSigned(value, digits);}
    printInt(uint16_t value, uint8_t digits) {setUnsigned(value, digits);}
    printInt(uint32_t value, uint8_t digits) {setUnsigned(value, digits);}
    virtual size_t printTo(Print& p) const;
};

inline printInt I(int16_t  value, uint8_t digits=2) {return printInt::printInt(value, digits);}
inline printInt I(int32_t  value, uint8_t digits=2) {return printInt::printInt(value, digits);}
inline printInt I(uint16_t value, uint8_t digits=2) {return printInt::printInt(value, digits);}
inline printInt I(uint32_t value, uint8_t digits=2) {return printInt::printInt(value, digits);}

#endif
#include <Print.h>
#include "printInt.h"

void printInt::setUnsigned(uint32_t value, uint8_t digits) {
  _digits = digits > 10 ? 10 : digits;
  _neg = false;
  _value = value;
}

void printInt::setSigned(int32_t value, uint8_t digits) {
  _digits = digits > 10 ? 10 : digits;
  if (value < 0) {
    _neg = true;
    _value = -value;
  } else {
    _neg = false;
    _value = value;
  }
}

size_t printInt::printTo(Print& p) const {
  size_t n = 0;
  if (_neg) n += p.print('-');
  uint32_t mask = 1;
  for (uint8_t i = 1; i<_digits; i++) mask *= 10;
  while (mask > _value) {
    n += p.print('0');
    mask /= 10;
  }
  if (_value > 0) n += p.print(_value);
  return n;
}

Pas certain que ce soit complètement académique mais c'est fonctionnel (aussi sur ESP).

Le plus simple aurait été de modifier directement la classe Print, mais celle-ci fait partie du core arduino relivré à chaque nouvelle version.

Je me suis donc appuyé sur la seule possibilité qui nous est offerte (déjà c'est bien que ce soit prévu) : passer par la classe abstraite Printable, gérée par la méthode print() de la classe Print.

Sur le principe il suffit de créer sa propre classe dérivée de Printable, et de lui définir la méthode virtuelle printTo().

C'est en zieutant "comment ils ont fait dans IPAddress.h pour qu'on puisse imprimer une adresse ip complète avec un simple print()?" que j'ai découvert cette possibilité.

Bon d'accord tout ça nécessite de connaître un peu la poo...
Mais mettre des espaces ou des séparateurs de milliers, c'est juste une histoire de code dans printTo(). D'ailleurs le code de cette fonction est certainement améliorable.

Comme là je n'ai pas trop le temps de m'en occuper, vous pouvez jouer aussi et partager le code ici.

A la fin on arrivera peut-être à un patch collégial qui répond au maximum de besoins.

J'aurais aussi pu passer bêtement par un String, mais ma position dogmatique sur ce point me l'interdit :smiling_imp:

Et puis même si la lib serait nettement plus simple à écrire, il me semble que les mécanismes mis en oeuvre sont plus lourds.

Au final la seule chose qui compte est d'avoir un I(nombre, ) qui s'exécute de manière efficace.

J-M-L:
Avez vous envisagé d’étendre la classe Print ?

Ce n'est pas tout à fait la même chose, bien que mécanisme d'abstraction soit le même.

Dans le lien ci-dessus, il s'agit de la marche à suivre pour qu'une classe puisse bénéficier des méthodes print.

Exemple : si je veux pouvoir faire toto.print(1) ou toto.print("test") il faut créer une classe (dont toto est une instance) dérivée de la classe Print, et lui définir la méthode virtuelle write() sur laquelle s'appuient toutes les méthodes print().

C'est ce qui a été fait pour Serial, LCD et d'autres.

Dans notre cas, on est à l'autre bout de la chaussette : il s'agit de définir la méthode print(classe spécifique) qui sera embarquée dans toutes les classes dérivées de Print.

oui - mauvais lien - j'ai fait ça rapidement.

je voulais dire sous-classer Print et rajouter quelques signatures bien pensées

Oui j'ai vu ça aussi, mais de ma compréhension (pas sûr à 100%) :

Il s'agit de la classe Print.h qui se trouve dans le core ESP8266.
C'est-à-dire quelle se substitue totalement au Print.h du core avr.

Dans l'IDE arduino, lors de la compil, l'utilisation de l'un ou l'autre core est conditionnée par le choix de la carte de destination.

En gros pour l'ESP8266 ils ont réécrit toutes (?) les bibliothèques du core avr, en compatibilité ascendante : un programme arduino compile avec un ESP.

En ajoutant au passage des fonctions supplémentaires, comme par exemple ici la méthode printf().

Et on retrouve la méthode size_t println(const Printable&), ce qui fait que ma petite lib doit fonctionner avec les deux core.

oui mauvais lien encore (suis sur mon tel :slight_smile: ) mais l'idée est la même

bon bien sûr reste l'option d'intégrer directement la fonction [url=http://www.cplusplus.com/reference/cstdio/printf/]printf()[/url] avec toute sa puissance (et son coût mémoire exorbitant pour un UNO - c'est pour cela que ça n'a pas été fait)

Effectivement
Il est logique que printf() apparaisse sur ESP nettement plus capacitaire qu'un atmega.
Sur arduino "de base" il faut se contenter un pauvre sprintf() pas aussi complet que le vrai.

Bonjour

Le brouillon commence à prendre forme.
J'ai mis à jour le fichier joint au premier post.

Serial.print(I(nombre, taille))
Serial.print(I(nombre, taille, separateur))

taille = le nombre de caractères souhaités au global (incluant éventuellement signe et séparateur)

Le nombre est complété à gauche par des zéro
Si le nombre est signé, le signe est systématiquement imprimé (même un +)
Si le nombre est trop grand pour la taille désirée, il est quand même imprimé dans son intégralité

separateur est un paramètre facultatif. Il permet de définir la taille des paquets de chiffres à séparer.

Par défaut ce paramètre est à zéro.
La valeur 3 sépare par milliers.
La valeur 2 peut être pratique pour afficher un numéro de téléphone stocké dans un unsigned long.
Le caractère de séparation est l'espace.

Serial.print(N(nombre, taille))
Serial.print(N(nombre, taille, separateur))

C'est tout pareil que I(), sauf que le nombre est complété à gauche par des blancs au lieu de zero.
Et du coup le signe des nombres signés est collé au premier chiffre significatif.

et puis comme j'y étais, j'ai aussi complété avec

Serial.print(B(nombre))

Pour imprimer en binaire propre, précédé de "0b", pour ne jamais avoir d’ambiguïté dans l’interprétation de ce qu'on a sous les yeux.

Le nombre de bits imprimés est 8, 16 ou 32, en fonction du type de la variable nombre (signée ou non)
La encore je trouve ça plus pratique que le Serial.print(nombre, BIN)

Marche aussi très bien pour le type char

Serial.print(H(nombre))

Même principe que B(), mais en hexadécimal

Tout ceci devrait fonctionner aussi bien sur ESP que sur atmega.
J'ai dû ajuster un peu le header pour l'adapter aux petites différences subtiles entre les deux cores.

N'hésitez pas à expérimenter la bestiole, et/ou suggérer des fonctionnalités supplémentaires.

cool :slight_smile:

peut-être à déplacer dans les projets finis ?

Je vais déjà l'éprouver un peu de mon côté, pour vérifier qu'il n'y a pas de coquille.

J'en suis aux premières utilisations, et j'ai l'impression que très bientôt je n'arriverai plus à m'en passer.

c'est génial, ça !

il me semble aussi que ça devrait être en section tutos ou projet finis, sinon ça risque de se perdre dans les abysses du forum, ce qui serait vraiment dommage.

Ça mériterait sans doute aussi un petit article sur le playground, vu le nombre de gens que ça concerne potentiellement :slight_smile:

Attends attends on va peut-être le faire converger avec le printf (cf section tutos).

Là déjà, ce qui serait bien, c'est que plusieurs personnes du forum expérimentent cette biliothèque et fournissent un retour (ça marche bien, y a un bug là..., j'aurais besoin qu'on puisse aussi avoir telle ou telle option..).

Ces contributions permettraient de mieux consolider les besoins.

pour le séparateur de N(), tu pourrais rajouter la possibilité de le choisir en passant un char comme dernier paramèrtre. Un espace n'est pas toujours adéquat.