Page 1 of 1

L'épopée du fameux PP08

Unread postPosted: 19 May 2013, 23:18
by Excale
Bonjour à tous,

Suite à ma participation au concours Palprem qui allait étrangement trop vite par rapport à ce qu'il est possible de faire en Basic, beaucoup de personnes m'ont demandé d'expliquer comment j’avais fait pour calculer si rapidement le 5000è palindrome premier.

La réponse est pourtant simple, j'ai utilisé ndless.
Le classeur est en fait un programme ndless qui va lui-même ouvrir un "vrai" classeur Nspire.

En pratique voilà ce qu'il se passe:
  • L'utilisateur ouvre le faux classeur, qui est en fait un programme ndless
  • Le programme copie le vrai classeur dans un fichier bidon
  • Le programme demande à l'OS d'ouvrir le classeur bidon en question
  • Le programme va créer un hook dans la fonction qui évalue une expression mathématique (c'est à dire qu'il prend la main dans cette fonction, fait quelque chose, et laisse la fonction continuer à s’exécuter normalement).
  • L'utilisateur voit un classeur Nspire ouvert
  • Il tape palprem(42)
  • L'OS arrive dans la fonction qui évalue l'expression mathématique, ce qui donne la main à mon programme
  • Le programme regarde si l'expression rentrée est de la forme palprem(nombre) (si il ne trouve pas ça, il ne fait rien et redonne la main à l'OS qui va évaluer le calcul)
  • Si il trouve le motif, il récupère le nombre et calcule le nombrième palindrome premier, et ce bien plus rapidement qu'en Basic
  • Le programme place ensuite le nombrième palindrome à la place de "palprem(nombre)", et redonne la main à l'OS
  • L'OS se retrouve alors avec un nombre à évaluer, et renvoie tout simplement ce nombre
  • L'utilisateur voit ce nombre et va alors pouvoir l'utiliser pour.... (zut, ça sert à quoi les palindromes premiers?)

Le code pour ceux que ça intéresse:
Show/Hide spoilerAfficher/Masquer le spoiler
Quelques définitions dont l'adresse du hook
Code: Select all
#include <os.h>
#include <libndls.h>
#include <stdint.h>
#include <inttypes.h>

static const unsigned int addr_exdoc_openclass[] = {0x10009B48, 0x10009B20, 0x10009AE8, 0x10009AE8};
#define EXDOC_OPENCLASS ((int(*)(char*, char*, int))nl_osvalue((int*)addr_exdoc_openclass, sizeof(addr_exdoc_openclass)/sizeof(addr_exdoc_openclass[0])))
static const unsigned int hook_addr_newclass[] =   {0x10027558, 0x100274C4, 0x10026C54, 0x10026BEC};
#define HOOK_NEWCLASS nl_osvalue((int*)hook_addr_newclass, sizeof(hook_addr_newclass)/sizeof(hook_addr_newclass[0]))
static const unsigned int hook_addr_calc[] =       {0x1006CBBC, 0x1006CAEC, 0x1006C2B8, 0x1006C210};
#define HOOK_ADDR_CALC nl_osvalue((int*)hook_addr_calc, sizeof(hook_addr_calc)/sizeof(hook_addr_calc[0]))

Le "vrai" classeur, qu'on inclut directement dans le binaire
Code: Select all
extern void* __file_start__;
extern void* __file_end__;
asm(".globl __file_start__          \n"
    ".globl __file_end__            \n"
    "__file_start__:                \n"
    ".incbin \"palprem_class.tns\"  \n"
    "__file_end__:                  \n");

Code pour trouver le n-ième palindrome premier. Adapté de : http://www.programmingsimplified.com/c/source-code/c-program-find-next-prime-palindrome
Code: Select all
uint32_t palprime(uint32_t m)
{
    uint32_t n, t, r = 0, c, z = 0;

    if(m<=5)
    {
        const int y[] = {0,2,3,5,7,11};
        return y[m];
    }
    else
    {
        n = 101-2;
        z = 5;
    }

    while(z<m)
    {
        printf("%" PRId32 " %" PRId32 "\n",z,n);
        while (TRUE)
        {
            n+=2;
            t = n;

            /* Calculating reverse of number */

            while(t)
            {
                r *= 10;  /* Compound assignment operator r*=10 => r=r*10 */
                r += t%10;
                t /= 10;
            }

            /* if reverse equals original then it is palindrome */

            if (r == n)
            {
                /* Checking prime */

                if ((n%2 == 0) || (n%3 == 0) || (n%5 == 0))
                goto suite;
                c = 7;
                while( c*c<=n)
                {
                    if ((n%c == 0) || (n%(c+4) == 0))
                    break;
                    c+=6;
                }
                if (c*c>n)
                break;
            }
            suite:
            r = 0;
        }
        z++;
    }

    return n;
}

Ce bout permet d'enlever le hook quand on quitte le classeur
Code: Select all
HOOK_DEFINE(newclass) {

    HOOK_UNINSTALL(HOOK_ADDR_CALC, calc_change);
    HOOK_UNINSTALL(HOOK_NEWCLASS , newclass);
    HOOK_RESTORE_RETURN(newclass);
}

Vérifie si l'utilisateur a tapé palstring(nombre), et appelle si besoin palprime()
Code: Select all
char resultstring[0x100];
const unsigned char palstring[] = {0x70, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x70, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6D, 0x00, 0x28, 0x00};

HOOK_DEFINE(calc_change) {

    char* calcul = (char *) HOOK_SAVED_REGS(calc_change)[2];
    if( !memcmp( calcul, &palstring, sizeof(palstring) ) )
    {
        uint32_t input = 0;
        int offset = 0;
        unsigned char currentn = calcul[sizeof(palstring)];
        while( currentn>=0x30 && currentn<=0x39 )
        {
            input = input*10 + (currentn-0x30);
            offset+=2;
            currentn = calcul[sizeof(palstring)+offset]; 
        }
        if( offset!=0 && calcul[sizeof(palstring)+offset]==')' && calcul[sizeof(palstring)+offset+2]==0)
        {
            uint32_t result = palprime(input);
            char resultstringbuf[0x80];
            memset(resultstring   , 0x0, 0x100);
            memset(resultstringbuf, 0x0, 0x80);
            sprintf(resultstringbuf, "%" PRId32 "", result);
            int i;
            for( i=0; i<0x80; i++ )
            {
                resultstring[2*i] = resultstringbuf[i];
            }
            HOOK_SAVED_REGS(calc_change)[2] = (int) resultstring;
        } else
        {
            HOOK_SAVED_REGS(calc_change)[2] = (int) palstring;
        }
    } else
    {
        int i;
        for( i=0; i<0x100; i++ )
        {
            if( calcul[2*i]=='p' && !memcmp( calcul+2*i, &palstring, sizeof(palstring)-2 ))
            {
                HOOK_SAVED_REGS(calc_change)[2] = (int) palstring;
                break;
            }
        }
    }
    HOOK_RESTORE_RETURN(calc_change);
}

Ouvre le classeur et définit les hooks.
Code: Select all
int main()
{
   nl_set_resident();
    FILE *tns_file;
    tns_file = fopen("/logs/palprem.tns","wb");
    fwrite (&__file_start__ , sizeof(char) , 4*(&__file_end__-&__file_start__), tns_file );
    fclose(tns_file);
    EXDOC_OPENCLASS("/logs","palprem.tns",1);
    HOOK_INSTALL(HOOK_ADDR_CALC, calc_change);
    HOOK_INSTALL(HOOK_NEWCLASS, newclass);
    return 0;
}

Je suis ouvert à toutes questions si besoin :).

...et si vous voulez télécharger le programme, c'est par ici: http://tiplanet.org/forum/archives_voir.php?id=14699

Re: L'épopée du fameux PP08

Unread postPosted: 19 May 2013, 23:30
by critor
En gros, si je traduis bien pour nos lecteurs:

Tu viens d'inventer l'exécution d'un programme Ndless en tant que fonction depuis un classeur TI-Nspire, sans avoir à l'enregistrer/fermer! :bj:


C'est révolutionnaire et visionnaire, avec des utilités pour nombres de calculs mathématiques complexes! :bj:
Outre la recherche des nombres premiers qui est ici ultra-rapide et satellise le programme de Bisam, on pourrait imaginer bien d'autres choses, comme les décimales de Pi ou encore un moteur CAS...


Jusqu'à présent, Ndless ne pouvait pas aider aux calculs de l'OS, et son intérêt pédagogique était donc limité.
Ce temps est désormais révolu! :bj:

Re: L'épopée du fameux PP08

Unread postPosted: 19 May 2013, 23:38
by mdr1
Ce ne serait pas le contraire ? Un programme ndless qui demande à l'OS d'ouvrir un classeur.

PS : Petit farceur, Excale ! :p

Re: L'épopée du fameux PP08

Unread postPosted: 19 May 2013, 23:39
by critor
L'un implique l'autre ;)

Re: L'épopée du fameux PP08

Unread postPosted: 19 May 2013, 23:57
by Loulou 54
Oh oui, techniquement, c'est vraiment fort ! :bj:
ça offre de sacrées possibilités !

Re: L'épopée du fameux PP08

Unread postPosted: 20 May 2013, 06:30
by Lionel Debroux
Tout à fait, c'est fort :)

Maintenant, ce serait super de continuer sur ta lancée et de pouvoir descendre un niveau plus bas que la recherche / manipulation de chaînes de caractères dans le parser d'expressions: faire de la manipulation directe sur la pile d'expressions sous forme tokenizée, comme on a toujours pu le faire sur TI-68k/AMS, mieux que je l'avais fait à http://www.ticalc.org/archives/files/fi ... 43727.html .
Ce premier programme CAS Nspire avait montré que le code du CAS de TI avait très peu changé, dans les fondements, en 15 ans - mais il était non interactif, et les opérations qui stockaient vers des variables crashaient la machine, donc inutile en pratique.
Et j'ai toujours été convaincu que le crash était probablement dû au fait que le document n'était pas dans la forme qui convenait pour stocker des variables - il faudrait qu'on puisse le confirmer une bonne fois pour toutes, et des programmes comme le tien (et son prédécesseur de mai 2012 utilisant une extension Lua) sont la meilleure base dont nous disposons pour ce faire :)

La bonne solution serait évidemment que TI arrête de déconner et ouvre officiellement, après plus de six ans de grosse erreur, la machine à la programmation en code natif. Cependant, tout le monde sait très bien, hélas, qu'il n'y a quasiment aucune chance que ceci se produise, même avec l'arrivée de la HP Prime dont la puissance brute permettrait de faire pas mal mieux que les Nspire sans se fouler - et on sait que HP peut se fouler, vu que le CAS des HP-49G+ battait celui des 89 dans certains domaines il y a une décennie. TI est suffisamment bien implanté dans le marché de l'éducation pour que pas grand chose ne puisse arriver à leur position dominante, tant qu'ils ne font pas trop n'importe quoi.

Re: L'épopée du fameux PP08

Unread postPosted: 20 May 2013, 14:38
by Levak
La seule opportunité d'avoir modification du langage Nspire Basic intégré est de faire un projet qui est le seul à utiliser ce langage modifié. En effet, il offrira une interactivité avec le Nspire-Basic et le "classeur" sera donc l'environnement modifié. Par contre, si, ce qui est publié sont des classeurs-patchs pour rajouter telle ou telle fonction, on va très vite voir des problèmes tels que "fonction non définie" / "erreur invalide dans une fonction ou programme" etc ... faisant encore plus ressentir les compatibilités de versions qu'on connait déjà, ajouté aux incompatibilités de patchs que l'on ne connait pas encore très bien sur TI-Nspire (car trop peu nombreuses).