Archives d’Auteur

Google Sheets : faire des critères complexes pour SUMIF() et autres

Une fonction comme SUMIF (SOMME.SI si vous utilisez des formules en français) permet de faire une somme des éléments d’une plage répondant à un critère. On retrouve le même concept pour AVERAGEIF, SUMIFS ou encore COUNTIF. Sauf que quand on regarde la documentation, les critères semblent très basiques… Par exemple, si ont veut sommer les valeurs positives de la colonne A, la formule est SUMIF(A:A, ">0"). Si on veut sommer les lignes de la colonne A pour lesquelles les lignes de la colonne B sont « Validé », c’est SUMIF(B:B, "Validé", A:A). Mais comment faire si on souhaite appliquer une formule à une plage et utiliser le résultat pour valider le critère ?

Prenons cet exemple de données d’entrée :

Ce sont des simples opérations financières, avec une date et une valeur. Imaginons maintenant qu’on souhaite faire la somme des valeurs par mois. De manière intuitive, on se dit que pour chaque mois, il faut un SUMIF de la colonne B si le mois de la colonne A est celui du mois considéré. Par exemple, pour Janvier, on aurait envie de faire quelque chose comme SUMIF(MONTH(A:A), 1, B:B) mais ça ne marche pas. On se dit qu’il faudrait créer un range avec les mois de la colonne A, sans forcément créer une colonne pour ça (car oui, créer une colonne intermédiaire est une solution simple efficace). Bonne nouvelle, il existe la fonction qu’il nous faut ! C’est ARRAYFORMULA ! Si dans une cellule, vous mettez ARRAYFORMULA(DATE(A:A)), vous verrez la colonne se remplir avec les mois des dates. Il suffit donc d’injecter ça dans notre SUMIF.

Voici ce que ça pourrait donner :

La colonne D et E sont créées manuellement. La formule en pour la colonne F est SUMIF(ARRAYFORMULA(MONTH(A:A));D:D;B:B). L’utilisation de la colonne D (qu’on peut masquer, car il est vrai que visuellement c’est pas foufou) permet d’avoir la même formule dans toutes les cellules de la colonne F sans devoir incrémenter manuellement le numéro du mois dans la formule (le D:D remplace le numéro du mois). Essayez de le coder, vous verrez que c’est plus facile 😉


Google Apps Script : la valeur cachée de ItemType

Si vous faites joujou avec des Google Forms et du Apps Script, vous êtes sans doute tombé sur l’énumération ItemType. L’interface Item représente un élément d’un formulaire et il y a plusieurs classes qui répondent à cette interface, comme TextItem qui permet de saisir du texte ou DateItem qui permet de choisir une date. Sans juger de la qualité OO du design, il faut noter que Item possède une méthode getType() qui renvoie une valeur de l’énumération ItemType. Cela permet de caster ensuite un Item dans le « bon » type pour le traiter selon son type réel (je n’invente rien, ce mode opératoire est proposé au début de la description de l’interface).

Voici la liste des valeurs de l’énumération d’après sa documentation :

  1. CHECKBOX
  2. CHECKBOX_GRID
  3. DATE
  4. DATETIME
  5. DURATION
  6. GRID
  7. IMAGE
  8. LIST
  9. MULTIPLE_CHOICE
  10. PAGE_BREAK
  11. PARAGRAPH_TEXT
  12. SCALE
  13. SECTION_HEADER
  14. TEXT
  15. TIME
  16. VIDEO

Cela veut dire que la méthode ordinal() sur un membre de cette énumération renvoie une valeur entre 0 et 15.

Moi, j’ai un formulaire avec un champ pour uploader un fichier :

google forms

Je ne vois aucun type correspondant dans l’énumération, pas grave, je vais tester. J’installe un trigger pour exécuter le script suivant quand une réponse est postée. Que pensez-vous que je vais obtenir dans mes logs lors de la prochaine réponse ?

function onFormSubmitted(event) {
var itemResponse = event.response.getItemResponses()[0];
var title = itemResponse.getItem().getTitle();
var response = itemResponse.getResponse();
console.log(title + ' : ' + response);
var type = itemResponse.getItem().getType();
console.log(type);
console.log(type.toString());
console.log(type.ordinal());
}

Dans le journal des exécutions, je vois :

13 juin 2019 à 15:59:07 DÉBOGAGE Mon fichier : 1rTvMQfzkp3UgVn4aj4-BOlf_6s2rB4
13 juin 2019 à 15:59:07	DÉBOGAGE FILE_UPLOAD
13 juin 2019 à 15:59:07 DÉBOGAGE FILE_UPLOAD
13 juin 2019 à 15:59:07 DÉBOGAGE 16.0

« Surprise, motherf*cker!« 

Attends, je teste un truc ! J’exécute cette fonction :

function testEnum() {
  Logger.log(FormApp.ItemType.VIDEO);
  Logger.log(FormApp.ItemType.FILE_UPLOAD);
}

Que vois-je dans les journaux ?

[19-06-13 16:19:34:803 CEST] VIDEO
[19-06-13 16:19:34:803 CEST] undefined

Bim ! C’est ça qui est bon ! Que de fun !

Du coup, si dans ton script, tu veux savoir si tu traites un Item de type FILE_UPLOAD, et bien il faut ruser… Voici par exemple une fonction pour générer des liens vers les fichiers uploadés :

function onFormSubmitted(event) {
	var itemResponse = event.response.getItemResponses()[0];
	var response = itemResponse.getResponse();

	var type = itemResponse.getItem().getType();
	if (type == "FILE_UPLOAD") {
		console.log(response);
		console.log(typeof response);

		for(var u = 0; u < response.length; u++) {
			var url = 'https://drive.google.com/open?id=' + response[u];
			console.log('Uploaded file URL = ' + url);
		}
	}
}

On obtient dans les logs :

13 juin 2019 à 16:28:29 DÉBOGAGE [1tLkWu6ij1qcb3ZFeCl7ZTnRYKL1zK7]
13 juin 2019 à 16:28:29 DÉBOGAGE object
13 juin 2019 à 16:28:29 DÉBOGAGE Uploaded file URL = 
      https://drive.google.com/open?id=1tLkWu6ij1qcb3ZFeCl7ZTnRYKL1zK7

Ah oui ! N’oublions pas cette magie du JavaScript pour qui un tableau est de type Object. La réponse à un tel Item est bien un tableau des ID des fichiers téléchargés, même s’il n’y a qu’un seul fichier.


Les joies des conversions d’entiers en C et C++

Hier, ce blog a eu 7 ans. Quand je l’ai créé, je ne m’étais pas vraiment posé la question de sa durée de vie… Je n’avais sans doute pas pensé que ça durerait aussi longtemps (7 ans donc), que je posterai autant d’articles (142) qui seraient vus par autant de visiteurs (170 812 pour un total de 226 395 vues). Pour fêter cet anniversaire, j’ai choisi un sujet qui a animé de nombreux articles depuis la création de ce blog : le C et ses écueils (et par extension le C++ est concerné). Aujourd’hui, on va parler d’un résultat inattendu d’une division et on va faire un tour de la norme pour tirer les choses au clair !

Question à 1 dollar : que retourne le code suivant ?

int main() {
    int a = -6;
    unsigned int b = 3;
    int c = a / b;
    return c;
}

Puisque je pose la question, vous vous doutez bien que ça ne retourne pas -2. Non, sur Windows 10 avec mingw64, ça retourne 1431655763.

Mais alors pourquoi ? Lors de la division, a est promu en unsigned int. Le calcul est donc fait entre deux entiers non signés puis remis dans un signé. Voilà, c’est aussi simple que ça.

Vous n’êtes pas contents ? Vous pensiez que les options -Wall -Wextra verraient ce genre de problèmes ? Je l’ai dit et je le répète encore : ces options sont le minimum vital mais il y en a bien d’autres à activer pour se protéger. Et surtout, c’est un comportement parfaitement normé du langage. Il suffit de se rendre à la section 6.3.1.8 Usual arithmetic conversions de la norme C99 (document n1256) pour y lire :

if the operand that has unsigned integer type has rank greater or
equal to the rank of the type of the other operand, then the operand with
signed integer type is converted to the type of the operand with unsigned
integer type

En fait, il y a moyen d’avoir un avertissement du compilateur. Il suffit de rajouter l’option -Wsign-conversion pour obtenir deux warnings sur la même ligne :

warning: conversion to 'unsigned int' from 'int' may change
 the sign of the result [-Wsign-conversion]
warning: conversion to 'int' from 'unsigned int' may change
 the sign of the result [-Wsign-conversion]

Le premier correspond à la conversion de b en unsigned int ; le second à la conversion du résultat en int pour l’affecter à c.

cppcheck dit aussi de faire attention :

cppcheck: (warning) Suspicious code: sign conversion of a in calculation,
 even though a can have a negative value

Notez au passage que a + b, a -b et a * c produisent les bons résultats (au moins sur mon PC, avec ma version de compilateur). Les 4 opérations génèrent des warnings avec GCC mais seules la multiplication et la division génèrent un warning de cppcheck.

PS : merci à Pulkomandy pour m’avoir posé cette colle :p


printf() et scanf() sur MCU

Bon nombre de développeurs embarqués pensent que printf() et scanf() sont réservés au monde PC et qu’ils doivent déboguer à la LED et aux combinaisons de boutons-poussoirs. Il est pourtant souvent facile d’avoir une UART réservée au debug pour lire et écrire des bytes et ainsi utiliser printf() et scanf(). Plus que printf() et scanf(), il s’agit en fait d’avoir une sortie standard (stdout) et une entrée standard (stdin) et ainsi d’avoir accès aux fonctions de la bibliothèque standard. Si stdin n’est pas forcément utile tous les jours, stdout l’est vraiment pour mettre des logs et ainsi suivre l’exécution de son programme.

Comment faire ?

En voilà une bonne question ! Il n’y a pas de réponse type malheureusement… Ça dépend de votre toolchain et surtout de la libc utilisée. Il faut fouiller dans la documentation et / ou sur Internet pour trouver quoi faire. En général, il va s’agir de redéfinir deux fonctions, une pour la lecture et l’autre pour l’écriture.

Voici quelques exemples :

Si vous utilisez newlib comme lib C, vous devriez lire Howto: Porting newlib – A Simple Guide.

Ça va me coûter quoi ?

La réponse est malheureusement la même que pour la question précédente. Ça dépend aussi bien sûr de ce que vous utilisez.

De simples puts() et fgets() ne seront pas très coûteux. Un printf("%d", value) coûtera un peu plus. Un printf("%f", value) coûtera encore plus. Et bien sûr, quand vous commencez à faire #include <iostream> et à jouer avec std::cout et std::cin, alors là, ça peut monter très vite… Je vous encourage à faire des essais, à voir ce qui coûte un peu, beaucoup, trop et trouver des compromis entre réutiliser des fonctions standards et implémenter certains fonctions simplifiées par vous même. Certains linkers sont plus intelligents que d’autres et ont des flags particuliers pour embarqué ou pas le code correspondant à un formateur particulier. Par exemple, ld (le linker de GCC) a l’option -u _printf_float pour pouvoir utiliser %f.

Avec la toolchain GNU pour ARM que j’utilise sur STM32, les printf() coûtent peu, en flash comme en RAM, y compris avec le formateur %f. En revanche, le simple fait d’inclure iostream (sans rien toucher à ce qu’il y a dedans) coûte 140k de flash et 6k de RAM. J’ai donc créé mes propres fonctions avec plusieurs surcharges pour avoir avec une syntaxe type stream << value << otherValue et j’utilise printf() de la bibliothèque standard. J’ai trouvé un bon compromis entre temps de développement, occupation mémoire et fonctionnalités.

Des fois, vous n’avez aucune contrainte. Dans certains applications particulières, par exemple des logiciels de tests ou de configuration qui ne sortent pas de l’usine, j’utilise à fond std::cout et std::cin. J’utilise la bibliothèque standard C++ de manière décomplexée, avec notamment std::getline et std::map. Oui, j’ai un terminal sur mon système embarqué, ça marche nickel et ça tient en quelques lignes de code !

Enfin, soyez conscients que toutes les fonctionnalités des bibliothèques standard C comme C++ peuvent utiliser de l’allocation dynamique (sauf quelques unes qui le précisent explicitement comme std::array). J’ai par exemple constaté avec ma toolchain qu’inclure iostream allouait de la mémoire (avant main() donc) et que le première appel (mais pas les suivants) à printf() faisait aussi une allocation.

Un exemple d’implémentation ?

Oui, avec la toolchain GNU pour ARM, il faut implémenter deux fonctions que le linker prendra magiquement. Attention, elles doivent avoir un linkage C !

#include "drivers/Uart.hpp"

static drivers::Uart uart(USART3);
extern "C" {

int _write(int /*fd*/, const void *buf, size_t count) {
	uart.sendSeveral(buf, count);
	return count;
}

int _read(int /*fd*/, const void* buf, size_t count) {
   std::size_t received = 0;
   auto line = (std::uint8_t*) buf;

   while (received &lt; count) {
      auto c = uart.read();
      line[received] = c;
      ++received;

      if (c == '\n') {
         // Stop when '\n' is received
         break;
      }
   }

   return received;
}

Mesure précise du temps d’exécution sur Cortex-M

Sur Cortex-M, il est possible d’utiliser le registre DWT CYCCNT pour avoir une mesure d’être précise du temps. Enfin, t’as tout à fait du temps : du nombre de cycles CPU écoulés. Un savant calcul avec la fréquence processeur redonne un temps… C’est utile pour mesurer les performances d’un morceau de code, en ayant conscience que le nombre de cycles nécessaires pour l’exécution doit tenir dans ce registre de 32 bits (ça laisse de la marge).

Voici comment s’en servir :

volatile int a;
volatile int b;
volatile int c;

void run() {

   std::cout << "Application starts" << std::endl;

   // Check it DWT is present
   if ((DWT->CTRL | DWT_CTRL_NOCYCCNT_Msk) == 1) {
      std::cout << "ERROR: DWT CYCCNT is not supported" << std::endl;

   } else {

      // Enable
      DWT->CTRL |= 1;

      // Restart counter
      DWT->CYCCNT = 0;

      // Do something
      c = (a + b) * (a - b) * 2;

      // Get cycles
      auto cycles = DWT->CYCCNT;
      std::cout << "Execution took " << (int) cycles << " cycles" << std::endl;
   }

   while (1) {
   }
}

En exécutant ce code sur une carte Nucleo de ST, équipée d’un STM32F413 (Cortex-M4), j’obtiens :

Application starts
Execution took 15 cycles

Si je remplace le calcul par un __NOP(), alors le nombre de cycle est de 1 et si je ne met rien, il est de… 0. Si je fais un délai de 1 seconde, avec HAL_Delay(1000), le nombre de cycles est de 32.028.150, ce qui est cohérent avec le fait que mon CPU tourne à 32 MHz.

Pour plus de détails, vous pouvez consulter le Reference Manual – ARM® v7-M Architecture.