A propos de nombres premiers
Publié : ven. 23/sept./2016 10:25
				
				Bonjour à tous.
C'est un plaisir d'écrire quelques mots ici.
Je travaille, entre autres choses, sur un projet consistant à éditer des résultats de calculs sur les «écarts entre nombres premiers». Les écarts entre les nombres premiers ont une importance particulière pour certains scientifiques de ma connaissance, et on m'a demandé de l'aide pour concevoir un processus plus rapide pour intégrer des résultats dans leurs bases de données.
Le projet est limité dans l'immédiat à une arithmétique 64 bits, ce qui signifie que nous ne calculons sur des nombres que jusqu'à 1e18, ce qui est suffisant por le moment, pour les résultats finaux que nous voulons obtenir.
De cette façon, j'ai écrit un bout de code pour permettre de calculer les nombres premiers, compter les écarts entre les nombres premiers, et éditer des statistiques sur les quantités des écarts, les résultats normalisés (% vs quantités), et ainsi de suite.
J'ai aussi imaginé un moyen de diviser un gros calcul en tranches, car si on veut par exemple couvrir toute la plage de 0 à 1e18 c'est inaccessible aux humains travaillant avec des PC.
En fait, j'ai limité la première étape de résultats à 1e15 comme borne supérieure de calcul.
Je l'ai testé sur un périmètre 0 à 1e12, par exemple, dans les premières versions de code, et il n'a pas été possible d'obtenir une fin traitement en moins d'un mois. Maintenant 1e12 est traité en une heure sur mon PC fixe.
Le programme utilise le multithreading pour profiter des avantages des processeurs multicœurs.
Pour traiter de très grandes étendues, comme 1e15, il faudrait environ 200 jours. Ceci est la raison principale qui m'a fait choisir la fragmentation du travail en parties plus raisonnables, la reconstitution des résultats consolidés étant faite à la fin de toutes les tranches de calcul.
Le programme dont le code figure ci-dessous contient ce qui est utile pour démarrer, demandant à l'utilisateur d'entrer une plage de calcul (par défaut il propose une plage raisonnable 1-1E9).
Ils y a quelques options supplémentaires, mais c'est un autre débat pas vraiment utile dans la section Trucs et astuces.
Si vous avez un quelconque intérêt sur le sujet et souhaitez plus d'informations, ou si vous voulez des explications plus spécifiques, sauf sur le code qui peut être commenté ici, vous pouvez me contacter en PM. Et si vous pensez que vous avez une bonne tranche de temps disponible sur vos PC à partager avec moi, je peux fournir une partie de traitement dans ce projet (juste à vous rappeler qu'il n'y a ni gloire, ni argent à gagner
 ).
Voilà, je ne sais pas si ce préambule est bien clair et compréhensible, mais voici le code! Je metrais peut-être à jour, ici, à l'avenir pour les codeurs PureBasic.
Si vous souhaitez tester l'application, j'ai posté un zip ici : https://drive.google.com/open?id=0BwEUr ... lRVQ05WdGc
Amusez-vous bien.
			C'est un plaisir d'écrire quelques mots ici.
Je travaille, entre autres choses, sur un projet consistant à éditer des résultats de calculs sur les «écarts entre nombres premiers». Les écarts entre les nombres premiers ont une importance particulière pour certains scientifiques de ma connaissance, et on m'a demandé de l'aide pour concevoir un processus plus rapide pour intégrer des résultats dans leurs bases de données.
Le projet est limité dans l'immédiat à une arithmétique 64 bits, ce qui signifie que nous ne calculons sur des nombres que jusqu'à 1e18, ce qui est suffisant por le moment, pour les résultats finaux que nous voulons obtenir.
De cette façon, j'ai écrit un bout de code pour permettre de calculer les nombres premiers, compter les écarts entre les nombres premiers, et éditer des statistiques sur les quantités des écarts, les résultats normalisés (% vs quantités), et ainsi de suite.
J'ai aussi imaginé un moyen de diviser un gros calcul en tranches, car si on veut par exemple couvrir toute la plage de 0 à 1e18 c'est inaccessible aux humains travaillant avec des PC.
En fait, j'ai limité la première étape de résultats à 1e15 comme borne supérieure de calcul.
Je l'ai testé sur un périmètre 0 à 1e12, par exemple, dans les premières versions de code, et il n'a pas été possible d'obtenir une fin traitement en moins d'un mois. Maintenant 1e12 est traité en une heure sur mon PC fixe.
Le programme utilise le multithreading pour profiter des avantages des processeurs multicœurs.
Pour traiter de très grandes étendues, comme 1e15, il faudrait environ 200 jours. Ceci est la raison principale qui m'a fait choisir la fragmentation du travail en parties plus raisonnables, la reconstitution des résultats consolidés étant faite à la fin de toutes les tranches de calcul.
Le programme dont le code figure ci-dessous contient ce qui est utile pour démarrer, demandant à l'utilisateur d'entrer une plage de calcul (par défaut il propose une plage raisonnable 1-1E9).
Ils y a quelques options supplémentaires, mais c'est un autre débat pas vraiment utile dans la section Trucs et astuces.
Si vous avez un quelconque intérêt sur le sujet et souhaitez plus d'informations, ou si vous voulez des explications plus spécifiques, sauf sur le code qui peut être commenté ici, vous pouvez me contacter en PM. Et si vous pensez que vous avez une bonne tranche de temps disponible sur vos PC à partager avec moi, je peux fournir une partie de traitement dans ce projet (juste à vous rappeler qu'il n'y a ni gloire, ni argent à gagner
Voilà, je ne sais pas si ce préambule est bien clair et compréhensible, mais voici le code! Je metrais peut-être à jour, ici, à l'avenir pour les codeurs PureBasic.
Code : Tout sélectionner
;
; Primes Gaps project was started to help some scientists I know to compute primes, and gaps between primes,
; giving the users the possibility to process large ranges, up to 1e12, in the shortest possible time on a single
; PC.
;
; There is no competition with large computers or distributed computing. The first requirement in this project
; is to count primes and gaps between prime numbers with a single usual PC.
;
; A second requirement was to make possible to use multithreading to accelerate results rendering. As most of
; PCs have at least 2 or 4 cores in a single chip, it makes sense to propose a 64 bits, multithreaded processing.
;
; Also the scope of primes to process was intended to reach 1e12 ... in the prime numbers. Two reasons about
; this : the first is that primality tests are heavy enough to make this limit far in rendering counts for such scopes
; in reasonable delay. The other is that many students and researchers use hardware and software with 
; such performances that they do not often walk around larger bounds.
;
; By the way when writing first tests, I could not imagine to solve 1e12 in less than a couple of days on my 4cores
; 2,8 GHz PC. As it is, this program has a 1e14 limitation. But all arithmetics make possible to process easily (but
; not at a glance) up to 1e18.
;
; I will not detail the whole wlak I did, but now this program shows, when using a good PC, that 1e12 full range is
; processed in about one hour and half.
;
; I made this program a bit more detailed in possible usage to let users see the differences in using either regular,
; optimized, with or without multithreading, 32 or 64 bits releases, etc ... this is for fun. But interesting indeed.
;
; I dedicate all this work especially to 2 persons, my father, Gérard Weil and a relative of our family, Gilbert Stork,
; because if I love to write programs, I cannot forget we carry genes of science.  I would like also to thank
; much much much Patrice Brouers for asking me some help on his research, and François Brouers to support
; us in some ways.
;
;
; The sieve of Erathostenes is not a recent discovery. It is also not exactly the way we can imagine in making
; light and fast primality tests when parsing large quantities of integers. I will not explain / describe it here, but
; if you want to check a large integer to be a prime or not, you will probably not start with this method.
;
; Fast explained, you have to dress a sheet with your integers lists in the first column, and fill rows with all
; matches with multiples of 2, 3, 4, 5, 6, 7, 8, 9 ... (simple counters indeed with as many rows as the square root
; of the highest integer listed in the sheet.
;
; Thinking furthermore you will use 2, 3, 5, 7, 11, 13, ... prime numbers only in your sheet header. Well if your
; list of numbers to test is very large, the number of columns will finally be large also.
;
; The number of primes between 0 and n is known to be close to n / (ln(n) - 1).
;
; If you want to check all cells of your sheet (array) having n rows and n / (ln(n) - 1) columns, you will never
; get results probably for centuries. Using n = 1e12 would make necessary to use a memory humankind does not
; have build.
;
; So the only possible way to go on is to split your problem in smaller pieces of work.
;
; The big trick here, to process a large range of integers, is the following :
;
; - split the full range in slices
; - split each slice in pages
; - collect results from pages to slice
; - collect results from slices to range.
; - ... you got it.
;
;
;
;
Structure NOTHING
EndStructure
;
; As the application program is there to render primes' gaps counts, #maxGaps constant
; helps to dim arrays to store gaps counts. 2000 preset is because considering the limit
; of eventually 1e18 (64 bits int numbers) will not generate gaps over 2000 (less in fact).
;
#maxGaps = 2000
;
; 2 constants to identify if the user works with a regular or optimized algorithm.
;
#SliceSoE2 = 1
#SliceSoE2_opt = 2
Structure SLICE ; SLICE structure formats a record containing all needed information for a slice.
  nmin.q                                        ; nmin / nmax are lower and upper bounds of the slice
  nmax.q                                       ;
  n.q                                            ; 
  nPrimes.q                                    ; the number of primes found in the slice
  FirstPrime.q                                 ; the first prime integer found in the slice
  LastPrime.q                                 ; ... and the last
  Gaps.q[#maxGaps]                        ; a static array to collcet gaps' counts
  FirstPrimeGaps.q[#maxGaps]           ; a static array remembering the first prime found having a given gap
  StartTime.q                                 ; statistic information about slice processing duration
  EndTime.q                                   ;
  ID.q
  Status.q
EndStructure
Structure Q2                                 ; small structure to facilitate final checks of results
  p.q
  q.q
  check.s
EndStructure
Define.NOTHING
;
; A set of paragraphs here are placed to understand and report about running PC characteristics.
; This is of no use except for interested users.
;
  Select #PB_Compiler_OS
    Case #PB_OS_Windows
      OSType.s = "Windows"
    Case #PB_OS_Linux
      OSType.s = "Linux"
    Case #PB_OS_MacOS
      OSType.s = "Mac OS X"
    Default
      OSType.s = "Unknown OS type"
  EndSelect
  Select #PB_Compiler_Processor
    Case #PB_Processor_x86
      ProcessorType.s = "x86"
    Case #PB_Processor_x64
      ProcessorType.s = "x64"
    Default
      ProcessorType.s = "Unknown processor type"
  EndSelect
  
  
  Select OSVersion()
    Case #PB_OS_Windows_NT3_51
      sOSVersion.s = "Windows NT 3.51"
    Case #PB_OS_Windows_95
      sOSVersion.s = "Windows 95"
    Case #PB_OS_Windows_NT_4
      sOSVersion.s = "Windows NT 4"
    Case #PB_OS_Windows_98
      sOSVersion.s = "Windows 98"
    Case #PB_OS_Windows_ME
      sOSVersion.s = "Windows ME"
    Case #PB_OS_Windows_2000
      sOSVersion.s = "Windows 2000"
    Case #PB_OS_Windows_XP
      sOSVersion.s = "Windows XP"
    Case #PB_OS_Windows_Server_2003
      sOSVersion.s = "Windows Server 2003"
    Case #PB_OS_Windows_Vista
      sOSVersion.s = "Windows Vista"
    Case #PB_OS_Windows_Server_2008
      sOSVersion.s = "Windows Server 2008"
    Case #PB_OS_Windows_7
      sOSVersion.s = "Windows 7"
    Case #PB_OS_Windows_Server_2008_R2
      sOSVersion.s = "Windows Server 2008 R2"
    Case #PB_OS_Windows_8
      sOSVersion.s = "Windows 8"
    Case #PB_OS_Windows_Server_2012
      sOSVersion.s = "Windows Server 2012"
    Case #PB_OS_Windows_8_1
      sOSVersion.s = "Windows 8.1"
    Case #PB_OS_Windows_Server_2012_R2
      sOSVersion.s = "Windows Server 2012 R2"
    Case #PB_OS_Windows_10
      sOSVersion.s = "Windows 10"
    Case #PB_OS_Windows_Future
      sOSVersion.s = "New Windows version (not existing when the program was written)"
    Case #PB_OS_Linux_2_2
      sOSVersion.s = "Linux 2.2"
    Case #PB_OS_Linux_2_4
      sOSVersion.s = "Linux 2.4"
    Case #PB_OS_Linux_2_6
      sOSVersion.s = "Linux 2.6"
    Case #PB_OS_Linux_Future
      sOSVersion.s = "New Linux version (not existing when the program was written)"
    Case #PB_OS_MacOSX_10_0
      sOSVersion.s = "MacOSX 10.0"
    Case #PB_OS_MacOSX_10_1
      sOSVersion.s = "MacOSX 10.1"
    Case #PB_OS_MacOSX_10_2
      sOSVersion.s = "MacOSX 10.2"
    Case #PB_OS_MacOSX_10_3
      sOSVersion.s = "MacOSX 10.3"
    Case #PB_OS_MacOSX_10_4
      sOSVersion.s = "MacOSX 10.4"
    Case #PB_OS_MacOSX_10_5
      sOSVersion.s = "MacOSX 10.5"
    Case #PB_OS_MacOSX_10_6
      sOSVersion.s = "MacOSX 10.6"
    Case #PB_OS_MacOSX_10_7
      sOSVersion.s = "MacOSX 10.7"
    Case #PB_OS_MacOSX_10_8
      sOSVersion.s = "MacOSX 10.8"
    Case #PB_OS_MacOSX_10_9
      sOSVersion.s = "MacOSX 10.9"
    Case #PB_OS_MacOSX_10_10
      sOSVersion.s = "MacOSX 10.10"
    Case #PB_OS_MacOSX_10_11
      sOSVersion.s = "MacOSX 10.11"
    Case #PB_OS_MacOSX_Future
      sOSVersion.s = "New MacOS X version (not existing when the program was written)"
    Default
      sOSVersion.s = "Unknown OS version return code"
  EndSelect
  
  PB_Compiler_Version.s = Str(#PB_Compiler_Version)
  PB_Version.s = Mid(PB_Compiler_Version, 1, 1) + "." + Mid(PB_Compiler_Version, 2)
  EnvHeader.s = #PB_Compiler_Filename + " generated " + FormatDate("%yyyy/%mm/%dd %hh:%ii:%ss", #PB_Compiler_Date) + " with PureBasic " + PB_Version + #CRLF$ +
                "Available for " + OSType + " on " + ProcessorType + " architectures" + #CRLF$ + 
                "running on : " + UserName() + "@" + ComputerName() + #CRLF$ +
                "CPU name : " + CPUName() + #TAB$ + "Processors count : " + Str(CountCPUs(#PB_System_CPUs)) + #TAB$ + "Avail. processors : " + Str(CountCPUs(#PB_System_ProcessCPUs)) + #CRLF$ +
                "Total phys. mem : " + FormatNumber(MemoryStatus(#PB_System_TotalPhysical), 0, ",", ".") + #TAB$ + "Total free. mem (@prog start) : " + FormatNumber(MemoryStatus(#PB_System_FreePhysical), 0, ",", ".") + #CRLF$ + 
                "OS version on running device : " + sOSVersion
  
;
; lower / upper bounds of range to test
;
  nmin.q = 1
  nmax.q = 1E9
  
;
; Set some initial values and get user customized parameters
;
  ProgramParameters.s
  CountProgramParameters.q = CountProgramParameters()
  If CountProgramParameters
      i.q = 1
      Repeat
        ProgramParameters + " " + ProgramParameter()
        i + 1
      Until i > CountProgramParameters
    Else
      DirectoryNumber.q = ExamineDirectory(#PB_Any, GetCurrentDirectory(), "*.*")
      If DirectoryNumber
        While NextDirectoryEntry(DirectoryNumber)
          If DirectoryEntryAttributes(DirectoryNumber) & #PB_FileSystem_Hidden = 0
              ProgramParameters_FileName.s = DirectoryEntryName(DirectoryNumber)
              FileNumber.q = ReadFile(#PB_Any, ProgramParameters_FileName)
              If FileNumber
                  Tag.s = "Primes Gaps automation "
                  lTag.q = Len(Tag)
                  If Left(ProgramParameters_FileName, lTag) = Tag
                      ProgramParameters = ReadString(FileNumber, #PB_Ascii)
                      CloseFile(FileNumber)
                      SetFileAttributes(ProgramParameters_FileName, #PB_FileSystem_Hidden)
                      Break
                  EndIf
                  CloseFile(FileNumber)
              EndIf
          EndIf
        Wend
        FinishDirectory(DirectoryNumber)
      EndIf
      
  EndIf
  
  If ProgramParameters <> ""
      q.s = ProgramParameters
    
    Else
      q.s = InputRequester("upper bound value, or h for quick help", "Enter the min-max values of range", Str(nmin) + "-" + Str(nmax))
      If q = "?" Or LCase(q) = "h" Or LCase(q) = "help"
        MessageRequester("Some help ?", "Just give a min-max value to parse for primes and primes' gaps" + #LF$ +
                                        "If the program is launched from console, arguments are expected in the command line." + #LF$ + #LF$ +
                                        "You can provide some more parameters after min-max values, using /name=value :" + #LF$ +
                                        "If possible argument value is a choice, defaults are displayed here first, and alternatives in second." + #LF$ +#LF$ +
                                        "file=[yes/no] to generate a file with results." + #LF$ +
                                        "file=open  optionally you can ask the file to open at program end." + #LF$ +
                                        "algo=[opt/regular] to specify which algorithm you prefer." + #LF$ +
                                        "thread=[yes/no] to enable / disable multithreading." + #LF$ +
                                        "wait=[yes/no] wait / don't wait for user close console at end.", #PB_MessageRequester_Ok)
          End
      EndIf
  EndIf
  
  If q <> ""
      If Trim(LCase(q)) = "quit"
          End
      EndIf
      If FindString(q, "-", 1)
          LHS.s = Trim(StringField(q, 1, "-"))
          RHS.s = Trim(StringField(q, 2, "-"))
          If FindString(RHS, " ", 1)
              RHS = StringField(RHS, 1, " ")
          EndIf
          NewList Args.s()
          iField.q = 2
          While StringField(q, iField, "/") <> ""
            AddElement(Args())
            Args() = StringField(q, iField, "/")
            iField + 1
          Wend
          nmin = Val(LHS)
          nmax = Val(RHS)
        Else
          nmax = Val(q)
      EndIf
  EndIf
  
  maxStoredPrimes.q = Sqr(nmax)
  ThreadedFlag.q = #True
  Algorithm.q = #SliceSoE2_opt
  WriteDocument.q = #True
  OpenDocument.q = #False
  Wait_At_End.q = #True
  
  ForEach Args()
    LHS.s = LCase(Trim(StringField(Args(), 1, "=")))
    RHS.s = LCase(Trim(StringField(Args(), 2, "=")))
    Select LHS
      Case "file"
        If RHS = "yes"
          WriteDocument.q = #True
        EndIf
        If RHS = "no"
          WriteDocument.q = #False
        EndIf
        If RHS = "open"
          OpenDocument.q = #True
        EndIf
      Case "algo"
        If RHS = "regular"
          Algorithm.q = #SliceSoE2
        EndIf
        If RHS = "opt"
          Algorithm.q = #SliceSoE2_opt
        EndIf
      Case "thread"
        If RHS = "no"
          ThreadedFlag.q = #False
        EndIf
        If RHS = "yes"
          ThreadedFlag.q = #True
        EndIf
      Case "wait"
        If RHS = "no"
          Wait_At_End.q = #False
        EndIf
        If RHS = "yes"
          Wait_At_End.q = #True
        EndIf
      Default
    EndSelect
  Next
  
  If Algorithm = #SliceSoE2_opt
      sAlgorithm.s = "#SliceSoE2_opt"
    ElseIf Algorithm = #SliceSoE2
      sAlgorithm.s = "#SliceSoE2"
  EndIf
  
  If Not WriteDocument
    OpenDocument = #False
  EndIf
  
;
; Inform optimistics that tomorrow is another day
;
  If nmax > 1E15
      MessageRequester("Sorry !", "This app. can't process over " + FormatNumber(1E15, 0, ",", ".") + " upper bound", #PB_MessageRequester_Ok)
      End
  EndIf
  
;
; Some variables and memory space to run process
;
; Threads() is a linked list that will be used to get and manage threads
; 
Global NewList Threads.q()
Global DoublePrimeTestCheck.q = #False
Global PageSizeLimit.q = 500000 ; The page size limit (a page is a part of a slice here).
    nSlicesMaxLimit.q = 1024 * 32 ; The limit number of slices in which the range is splitted.
    ThDelay1.q = 0 ; In case of multithreading use, ThDelay1/2 allows to adapt theory to reality.
    ThDelay2.q = 0 ; These delays should not be necessary, but in case of frequent crash, just place
                         ; a short delay like 1 or 5. Here are milliseconds.
    maxConcurrentThreads.q = 16 ; This parameter indicates how many concurrent threads should
                                             ; be sent to CPU at the same moment. The CPU will process  only
                                             ; only a thread by core / CPU at a time but each core / CPU can
                                             ; manage a queue. Depending on hardware, this parameter may
                                             ; help to tune performances.
PI_Primes.q = 1.2 * maxStoredPrimes / (Log(maxStoredPrimes) - 1)
Global Dim Primes.q(PI_Primes)
Global nStoredPrimes.q
Global nSlicesStarted.q
Global nSlicesDone.q
    If nmax < 1E6
        nSlices.q = 1
      ElseIf nmax < 1E7
        nslices = 4
      ElseIf nmax < 1E8
        nslices = 8
      ElseIf nmax < 1E9
        nslices = 16
      ElseIf nmax < 1E10
        nslices = 32
      ElseIf nmax < 1E11
        nslices = 64
      ElseIf nmax < 1E12
        nslices = 128
      ElseIf nmax < 1E13
        nslices = 256
      ElseIf nmax < 1E14
        nslices = 512
      Else
        nslices = 1024
    EndIf
    
;     nSlices = 256
;     maxConcurrentThreads = 256
;     PageSizeLimit.q = 0
;     
    
;
; A small piece of code to get reference data. This to check results if possible.
; PI_n_ref will collect PI(n) for a limited set of values and also the corresponding gaps counts.
;
NewList PI_n_ref.Q2()
Repeat
  Read.q range.q
  Read.q value.q
  If range = 999999999 And value = 999999999
      Break
  EndIf
  AddElement(PI_n_ref())
  PI_n_ref()\p = range
  PI_n_ref()\q = value
ForEver
ForEach PI_n_ref()
  Read.q i.q
  If i = 999999999
      Break
  EndIf
  If i = PI_n_ref()\p
      Read.s PI_n_ref()\check
  EndIf
Next
;
; Here is the trick ! A sieve of Erathostenes algorithm to parse ranges of integers with counters.
; The most important trick is to parse any range with a n upper bound using sqr(n) counters.
; Also each counters must be initialized to the appropriate value starting with lower bound.
;
; To perform this as fast as possible, the program uses a stored primes table, containing a set
; of precalculated primes up to the value of sqr(max), where max is the highest possible upper bound.
;
; That's it and nothing more. But the way to split a large range in slices, and each slice in pages makes
; the difference.
;
; SliceSoE2 receives a data record, SLICE structure, containing bounds of slice, and some space for
; results.
; The record will be fullfilled with primes count, gaps counts, first and last primes of slice.
;
; Collecting all the slices, the main program code will process a global merge of all data.
;
; The following procedure is the regular code one, available for both x86 and x64 CPUs, and processing
; 64 bits arithmetic. It means that performances are poor on x86.
;
Procedure SliceSoE2(*Slice.SLICE)
  Shared ID.q
  nSlicesStarted + 1
  *Slice\StartTime = ElapsedMilliseconds()
  *Slice\ID = ID
  *Slice\Status = 1
  StartDate.q = Date()
  ID + 1
  Gnmin.q = *Slice\nmin
  Gnmax.q = *Slice\nmax
  nPrimes.q = 0
  PageSize.q = Sqr(Gnmax)
  If PageSizeLimit
      If PageSize > PageSizeLimit
          PageSize = PageSizeLimit
      EndIf
  EndIf
  FirstPrime.q = 0
  LastPrime.q = 0
  nSoE.q = 0
  Gn.q = Gnmin
  Repeat
    nmin.q = Gn
    nmax.q = Gn + PageSize - 1
    If nmax > Gnmax
        nmax = Gnmax
    EndIf
    maxSoE.q = Sqr(nmax)
    While nSoE <= nStoredPrimes
      If Primes(nSoE) > maxSoE
          Break
      EndIf
      nSoE + 1
    Wend
    nSoE - 1
    If nmin <= 1
        nmin = 1
        nPrimes + 1
        FirstPrime = 2
        LastPrime = 2
    EndIf
    If nmin & 1 = 0
        nmin + 1
    EndIf
    iSoE.q = 0
    If nmax & 1 = 0
        nmax - 1
    EndIf
    ThisPageSize.q = nmax - nmin
    If ThisPageSize > 0
        Dim Page.q(ThisPageSize)
        If nmin = 1
            Page(0) = 1
        EndIf
        iSoE.q = nSoE
        Repeat
          thisprime.q = Primes(iSoE)
          If nmin < thisprime
              ptr.q = thisprime << 1 - nmin
            Else
              r.q = nmin % thisprime
              If r = 0
                  ptr.q = 0
                Else
                  ptr.q = thisprime - r
              EndIf
          EndIf
          While ptr <= ThisPageSize
            If ptr & 1 = 0
                Page(ptr) = 1
            EndIf
            ptr + thisprime
          Wend
          iSoE - 1
        Until iSoE < 0
        i.q = 0
        Repeat
          If Page(i) = 0
              nPrimes + 1
              If FirstPrime = 0
                  FirstPrime = i + nmin
                  LastPrime = FirstPrime
                Else
                  Gap.q = i + nmin - LastPrime
                  If *Slice\Gaps[Gap] = 0
                      *Slice\FirstPrimeGaps[Gap] = LastPrime
                  EndIf
                  *Slice\Gaps[Gap] + 1
                  LastPrime = i + nmin
              EndIf
          EndIf
          i + 2
        Until i > ThisPageSize ; Next
    EndIf
    Gn + PageSize
  Until Gn > Gnmax
  *Slice\FirstPrime = FirstPrime
  *Slice\LastPrime = LastPrime
  *Slice\nPrimes + nPrimes
  *Slice\EndTime = ElapsedMilliseconds()
  *Slice\Status = 2
  nSlicesDone + 1
EndProcedure
;
; Optimized code is proposed in a deep rework for x64 and lighter one for x86 because the whole code
; is definitely a 64 bits one.
;
; In the 64 bits part, FASM is used to make all scopes of code as fast as possible using all registers to
; have calculations as much as possible inside the core (register to register processing eveywhere it is possible).
;
; Assuming this, the optimized code does exactly the same compared to the regular part.
;
Procedure SliceSoE2_opt(*Slice.SLICE)
  Shared ID.q
  nSlicesStarted + 1
  *Slice\StartTime = ElapsedMilliseconds()
  *Slice\ID = ID
  *Slice\Status = 1
  StartDate.q = Date()
  ID + 1
  Gnmin.q = *Slice\nmin
  Gnmax.q = *Slice\nmax
  nPrimes.q = 0
  PageSize.q = Sqr(Gnmax)
  If PageSizeLimit
      If PageSize > PageSizeLimit
          PageSize = PageSizeLimit
      EndIf
  EndIf
  FirstPrime.q = 0
  LastPrime.q = 0
  nSoE.q = 0
  Gn.q = Gnmin
  Repeat
    nmin.q = Gn
    nmax.q = Gn + PageSize - 1
    If nmax > Gnmax
        nmax = Gnmax
    EndIf
    maxSoE.q = Sqr(nmax)
    
    !  MOV     r11, qword [p.v_nSoE]
    !  MOV     r12, r11
    !  SAL      r12, 3
    !  ADD      r12, qword [a_Primes]
    !  MOV     r13, qword [p.v_maxSoE]
! SliceSoE2_opt_while_nSoE_le_nStoredPrimes_while:
    !  CMP     r11, qword [v_nStoredPrimes] ; While nSoE <= nStoredPrimes
    !  JG        SliceSoE2_opt_while_nSoE_le_nStoredPrimes_endloop
      !  CMP     qword [r12], r13 ; If Primes(nSoE) > maxSoE
      !  JLE       @f
          !  JMP      SliceSoE2_opt_while_nSoE_le_nStoredPrimes_endloop ; Break
! @@: ; EndIf
      !  INC      r11 ; nSoE + 1
      !  ADD     r12, 8
    !  JMP     SliceSoE2_opt_while_nSoE_le_nStoredPrimes_while
! SliceSoE2_opt_while_nSoE_le_nStoredPrimes_endloop:
    !  DEC      r11 ; nSoE - 1
    !  MOV     qword [p.v_nSoE], r11
    
    If nmin <= 1
        nmin = 1
        nPrimes + 1
        FirstPrime = 2
        LastPrime = 2
    EndIf
    If nmin & 1 = 0
        nmin + 1
    EndIf
    iSoE.q = 0
    If nmax & 1 = 0
        nmax - 1
    EndIf
    ThisPageSize.q = nmax - nmin
    
    *pGaps = @*Slice\Gaps[0]
    *pFirstPrimeGaps = @*Slice\FirstPrimeGaps[0]
    
    If ThisPageSize > 0
        Dim Page.q(ThisPageSize)
        
; ptr => r8   thisprime => r9   iSoE => r10   nmin => r11   ThisPageSize => r12   @Primes() => r13    @Page() => r14
        !  MOV     r11, qword [p.v_nmin]
        !  MOV     r14, qword [p.a_Page]
        !  CMP     r11, 1 ; If nmin = 1
        !  JNE      @f
            !  MOV     qword [r14], 1 ; Page(0) = 1
! @@: ; EndIf
        !  MOV     r10, qword [p.v_nSoE] ; iSoE.q = nSoE
        !  MOV     r12, qword [p.v_ThisPageSize]
        !  MOV     r13, qword [a_Primes]
! SliceSoE2_opt_iSoE_nSoE_0_loop: ; Repeat
          !  MOV     rax, r10 ; thisprime.q = Primes(iSoE)
          !  SAL      rax, 3
          !  MOV     r9, qword [r13+rax]
          !  MOV     rax, r11 ; If nmin < thisprime
          !  CMP     rax, r9
          !  JGE      @f
              !  MOV     rax, r9 ; ptr.q = thisprime << 1 - nmin
              !  SAL      rax, 1
              !  SUB      rax, r11
              !  MOV     r8, rax
              !  JMP      SliceSoE2_opt_while_ptr_le_ThisPageSize_loop
! @@: ; Else
              !  MOV     rax, r11 ; r.q = nmin % thisprime
              !  MOV     rcx, r9
              !  CQO
              !  IDIV     rcx
              !  CMP     rdx, 0 ; If r = 0
              !  JNZ      @f
                  !  MOV     r8, 0 ; ptr.q = 0
                  !  JMP     SliceSoE2_opt_while_ptr_le_ThisPageSize_loop
! @@: ; Else
                  !  MOV     r8, r9 ; ptr.q = thisprime - r
                  !  SUB      r8, rdx
! SliceSoE2_opt_while_ptr_le_ThisPageSize_loop: ; While ptr <= ThisPageSize
          !  CMP     r8, r12
          !  JG        SliceSoE2_opt_while_ptr_le_ThisPageSize_endloop
            !  MOV     rax, r8 ; If ptr & 1 = 0
            !  AND      rax, 1
            !  JNZ       @f
                !  MOV     rax, r8 ; *Page = *pPage + ptr << 3
                !  SAL      rax, 3
                !  INC      qword [r14+rax] ; *Page\q = 1 ; Page(ptr) = 1
! @@: ; EndIf
            !  ADD     r8, r9 ; ptr + thisprime
          !  JMP      SliceSoE2_opt_while_ptr_le_ThisPageSize_loop ; Wend
! SliceSoE2_opt_while_ptr_le_ThisPageSize_endloop:
          !  DEC     r10 ; iSoE - 1
        !  CMP     r10, 0 ; Until iSoE < 0
        !  JGE      SliceSoE2_opt_iSoE_nSoE_0_loop
! SliceSoE2_opt_iSoE_nSoE_0_endloop:
; i => r8   Gap => r9   nmin => r11   ThisPageSize => r12   @*Slice\Gaps[0] => r13   @Page() => r14   @*Slice\FirstPrimeGaps[0] => r15
        !  MOV     r8, 0
        !  MOV     r13, qword [p.p_pGaps]
        !  MOV     r15, qword [p.p_pFirstPrimeGaps]
! SliceSoE2_repeat_i_0_ThisPageSize_loop: ; Repeat
          !  CMP      qword [r14], 0 ; If *Page\q = 0 ; Page(i) = 0
          !  JNZ       SliceSoE2_opt_Page_i_eq_0_endtest
              !  INC      qword [p.v_nPrimes] ; nPrimes + 1
              !  CMP     qword [p.v_FirstPrime], 0 ; If FirstPrime = 0
              !  JNZ      @f
                  !  MOV     rax, r8 ; FirstPrime = i + nmin
                  !  ADD      rax, r11 ; qword [p.v_nmin]
                  !  MOV     qword [p.v_FirstPrime], rax
                  !  MOV     qword [p.v_LastPrime], rax ; LastPrime = FirstPrime
                  !  JMP      SliceSoE2_opt_FirstPrime_eq_0_endtest
! @@: ; Else
                  !  MOV     r9, r8 ; Gap.q = i + nmin - LastPrime
                  !  ADD      r9, r11 ; qword [p.v_nmin]
                  !  SUB      r9, qword [p.v_LastPrime]
                  !  SAL      r9, 3 ; *Gaps = *pGaps + Gap << 3
                  !  CMP     qword [r13+r9], 0 ; If *Gaps\q = 0 ; *Slice\Gaps[Gap] = 0
                  !  JNZ      @f
                      !  MOV     rbx, qword [p.v_LastPrime]  ; *FirstPrimeGaps = *pFirstPrimeGaps + Gap << 3 ; *FirstPrimeGaps\q = LastPrime ; *Slice\FirstPrimeGaps[Gap] = LastPrime
                      !  MOV     qword [r15+r9], rbx
! @@: ; EndIf
                  !  INC      qword [r13+r9] ; qword [p.p_Gaps] ; *Gaps\q + 1 ; *Slice\Gaps[Gap] + 1
                  !  MOV     rax, r8 ; LastPrime = i + nmin
                  !  ADD      rax, r11 ; qword [p.v_nmin]
                  !  MOV     qword [p.v_LastPrime], rax
! SliceSoE2_opt_FirstPrime_eq_0_endtest: ; EndIf
! SliceSoE2_opt_Page_i_eq_0_endtest: ; EndIf
          !  ADD     r8, 2 ; i + 2
          !  ADD     r14, 16 ; *Page + 16
        !  CMP      r8, r12 ; qword [p.v_ThisPageSize] ; Until i > ThisPageSize ; Next
        !  JLE       SliceSoE2_repeat_i_0_ThisPageSize_loop
    EndIf
    Gn + PageSize
  Until Gn > Gnmax
  *Slice\FirstPrime = FirstPrime
  *Slice\LastPrime = LastPrime
  *Slice\nPrimes + nPrimes
  *Slice\EndTime = ElapsedMilliseconds()
  *Slice\Status = 2
  nSlicesDone + 1
EndProcedure
;
; Main starts here
;
SetPriorityClass_(GetCurrentProcess_(), #BELOW_NORMAL_PRIORITY_CLASS)
If OpenConsole()
    Text.s = ""
    Env.s = ProgramFilename() + #CRLF$ +
            EnvHeader + #CRLF$ + #CRLF$ + 
            "nmin = " + FormatNumber(nmin, 0, ",", ".") + #CRLF$ +
            "nmax = " + FormatNumber(nmax, 0, ",", ".") + #CRLF$ +
            "maxStoredPrimes = " + FormatNumber(maxStoredPrimes, 0, ",", ".") + #CRLF$ +
            "ThreadedFlag = " + FormatNumber(ThreadedFlag, 0, ",", ".") + #CRLF$ +
            "Algorithm = " + sAlgorithm + #CRLF$ +
            "WriteDocument = " + FormatNumber(WriteDocument, 0, ",", ".") + #CRLF$ +
            "OpenDocument = " + FormatNumber(OpenDocument, 0, ",", ".") + #CRLF$ +
            "PageSizeLimit = " + FormatNumber(PageSizeLimit, 0, ",", ".") + #CRLF$ +
            "nSlicesMaxLimit = " + FormatNumber(nSlicesMaxLimit, 0, ",", ".") + #CRLF$ +
            "maxConcurrentThreads = " + FormatNumber(maxConcurrentThreads, 0, ",", ".")
    
    PrintN(Env)
    Text + Env + #CRLF$
    ConsoleTitle(FormatNumber(nmin, 0, ",", ".") + " - " + FormatNumber(nmax, 0, ",", "."))
    StartDate.q = Date()
    tz.q = ElapsedMilliseconds()
    nStoredPrimes.q = 0
    n.q = 0
    Repeat
      Select n
        Case 0, 1
          n + 1
        Case 2
          Primes(nStoredPrimes) = n
          nStoredPrimes + 1
          n + 1
        Case 3, 5, 7
          Primes(nStoredPrimes) = n
          nStoredPrimes + 1
          n + 2
        Default
          If n & 1 = 0
              n + 1
          EndIf
          If n > 5
              b.d = 1 / 3
              c.q = n * b
              If n - 3 * c = 0
                  IsPrime.q = #False
                  Goto IsPrime_test_point
              EndIf
              dmax.q = Sqr(n)
              d.q = 5
              While d <= dmax
                b.d = 1 / d
                c.q = n * b
                If n - d * c = 0
                    IsPrime.q = #False
                    Goto IsPrime_test_point
                EndIf
                d + 2
                b.d = 1 / d
                c.q = n * b
                If n - d * c = 0
                    IsPrime.q = #False
                    Goto IsPrime_test_point
                EndIf
                d + 4
              Wend
              IsPrime.q = #True
              Goto IsPrime_test_point
            Else
              Select n
                Case 3, 5
                  IsPrime.q = #True
                  Goto IsPrime_test_point
                Default
                  IsPrime.q = #False
                  Goto IsPrime_test_point
              EndSelect
          EndIf
IsPrime_test_point:          
          If IsPrime
            Primes(nStoredPrimes) = n
            nStoredPrimes + 1
          EndIf
          n + 2
      EndSelect
      If n & $FFFFF = $FFFFF
          PrintN(StrD(100 * n / maxStoredPrimes, 3) + " %" + #TAB$ + Str(nStoredPrimes))
      EndIf
      
    Until n > maxStoredPrimes
    nStoredPrimes - 1
    
    Time.d = (ElapsedMilliseconds() - tz) / 1000
    Line.s = "Stored primes array fetched with : " + FormatNumber(nStoredPrimes + 1, 0, ",", ".") + " primes in " + StrD(Time, 3) + " s"
    PrintN(Line)
    Text + Line + #CRLF$
    
    SliceSize.q = (nmax - nmin) / nSlices
    
    nSlices - 1
    Dim Slice2.SLICE(nSlices)
    Dim FirstPrimes.q(nSlices)
    Dim LastPrimes.q(nSlices)
    Dim Gaps.q(#maxGaps - 1)
    Dim GapsMod3.q(2, #maxGaps - 1)
    Dim FirstPrimeGaps.q(#maxGaps - 1)
    
    Line.s = "Slice2SoE - starting range" + #TAB$ + FormatNumber(nmin, 0, ",", ".") + " - " + FormatNumber(nmax, 0, ",", ".") + #TAB$ + "Using " + Str(nSlices + 1) + " threads" + #TAB$ + FormatDate("%yyyy/%mm/%dd %hh:%ii:%ss", Date())
    PrintN(Line)
    Text + Line + #CRLF$
    
    tz.q = ElapsedMilliseconds()
    
    PrintN("Effective slice size : " + FormatNumber(SliceSize, 0, ",", "."))
    nPrimes.q = 0
    nGaps.q = 0
    thisnmin.q = nmin
    thisnmax.q = nmin + SliceSize - 1
    
    kSlicesDone.d = 0
    iSlice.q = 0
    Repeat
      Slice2(iSlice)\nmin = thisnmin
      Slice2(iSlice)\nmax = thisnmax
      Slice2(iSlice)\Status = 0
      Slice2(iSlice)\ID = iSlice
      If ThreadedFlag
          AddElement(Threads())
          Select Algorithm
            Case #SliceSoE2
              Threads() = CreateThread(@SliceSoE2(), @Slice2(iSlice))
            Case #SliceSoE2_opt
              Threads() = CreateThread(@SliceSoE2_opt(), @Slice2(iSlice))
          EndSelect
        Else
          Select Algorithm
            Case #SliceSoE2
              SliceSoE2(Slice2(iSlice))
            Case #SliceSoE2_opt
              SliceSoE2_opt(Slice2(iSlice))
          EndSelect
      EndIf
      thisnmin = thisnmax + 1
      thisnmax = thisnmin + SliceSize - 1
      If iSlice = nSlices - 1
        thisnmax = nmax
      EndIf
      If ThreadedFlag
          Repeat
            nThreads.q = nSlicesStarted - nSlicesDone
            Delay(ThDelay1)
          Until nThreads < maxConcurrentThreads
      EndIf
      kSlicesDone.d = nSlicesDone / nSlices
      PastTime.d = (ElapsedMilliseconds() - tz) / 1000
      If ExpectedTime.d = #INFINITE Or PastTime = #INFINITE
          PrintN("Some cycles required to evaluate statistics")
        Else
          PrintN(StrD(kSlicesDone * 100, 3) + " %" + #TAB$ +
                 "Elaps. " + StrD(PastTime, 1) + " s    " +
                 "Est. " + sExPectedDate.s + " (" + StrD(ExpectedTime.d, 1) + " s " +
                 "/ still " + StrD(ExpectedTime - PastTime.d, 1) + " s)")
      EndIf
      ExpectedTime.d = PastTime * Pow(31, Log10(nmax - nmin) - Log10(nSlicesDone * SliceSize))
      sExPectedDate.s = FormatDate("%yyyy/%mm/%dd %hh:%ii:%ss", StartDate + ExpectedTime)
      iSlice + 1
    Until iSlice > nSlices
    
    If ThreadedFlag
        Repeat
          Ready.q = #True
          ForEach Threads()
            If IsThread(Threads())
              Ready = #False
            EndIf
          Next
          Delay(ThDelay2)
        Until Ready
    EndIf
    
    iSlice.q = 0
    Repeat
      FirstPrimes(iSlice) = Slice2(iSlice)\FirstPrime
      LastPrimes(iSlice) = Slice2(iSlice)\LastPrime
      iSlice + 1
    Until iSlice > nSlices
    
    iSlice.q = 0
    Repeat
      If iSlice > 0
        Gap.q = FirstPrimes(iSlice) - LastPrimes(iSlice - 1)
        Gaps(Gap) + 1
        If FirstPrimeGaps(Gap) > LastPrimes(iSlice - 1)
          FirstPrimeGaps(Gap) = LastPrimes(iSlice - 1)
        EndIf
      EndIf
      nPrimes + Slice2(iSlice)\nPrimes
      iGap.q = 0
      Repeat
        If Slice2(iSlice)\Gaps[iGap] <> 0
            nGaps + Slice2(iSlice)\Gaps[iGap]
            Gaps(iGap) + Slice2(iSlice)\Gaps[iGap]
            If FirstPrimeGaps(iGap) > Slice2(iSlice)\FirstPrimeGaps[iGap] Or FirstPrimeGaps(iGap) = 0
                FirstPrimeGaps(iGap) = Slice2(iSlice)\FirstPrimeGaps[iGap]
            EndIf
        EndIf
        iGap + 1
      Until iGap > #maxGaps - 1
      iSlice + 1
    Until iSlice > nSlices
    
    PrintN("")
    Time.d = (ElapsedMilliseconds() - tz) / 1000
    Line = "Slice2SoE - parsed range" + #TAB$ + FormatNumber(Slice2(0)\nmin, 0, ",", ".") + " - " + FormatNumber(Slice2(nSlices)\nmax, 0, ",", ".") + #TAB$ + "Done in " + StrD(Time, 3) + " s" + #TAB$ + FormatDate("%yyyy/%mm/%dd %hh:%ii:%ss", Date())
    PrintN(Line)
    Text + Line + #CRLF$
    Line = "Slice2SoE - found " + FormatNumber(nPrimes, 0, ",", ".") + " primes"
    PrintN(Line)
    Text + Line + #CRLF$
    PrintN("")
    Text + #CRLF$
    
    Line = "First prime in range : " + FormatNumber(Slice2(0)\FirstPrime, 0, ",", ".") + #CRLF$ + "Last prime in range : " + FormatNumber(Slice2(nSlices)\LastPrime, 0, ",", ".")
    PrintN(Line)
    Text + Line + #CRLF$
    
    nGaps.q = 0
    iGap.q = 0
    Repeat
      If Gaps(iGap) <> 0
          nGaps + Gaps(iGap)
        EndIf
        iGap + 1
    Until iGap > #maxGaps - 1
    
    nGaps.q = 0
    NewList sGaps.s()
    maxGap.q = 0
    
    Line = "Gaps [gap, qty]"
    Text + Line + #CRLF$
    PrintN(Line)
    Line = ""
    iGap.q = 0
    Repeat
      If Gaps(iGap) <> 0
          Line + "[" + Str(iGap) + ", " + Str(Gaps(iGap)) + "], "
          maxGap = FirstPrimeGaps(iGap)
          AddElement(sGaps())
          sGaps() = RSet(Str(FirstPrimeGaps(iGap)), 18, " ") + #TAB$ + RSet(Str(iGap), 6, " ")
          nGaps + Gaps(iGap)
      EndIf
      iGap + 1
    Until iGap > #maxGaps - 1
    ThisGaps.s = Line
    Text + Line + #CRLF$
    PrintN(Line)
    Text + #CRLF$
    PrintN("")
    
    Line = "Gaps [gap, ratio]"
    Text + Line + #CRLF$
    PrintN(Line)
    Line = ""
    iGap.q = 0
    Repeat
      If Gaps(iGap) <> 0
          Line + "[" + Str(iGap) + ", " + StrD(Gaps(iGap) / nGaps, 15) + "], "
      EndIf
      iGap + 1
    Until iGap > #maxGaps - 1
    Text + Line + #CRLF$
    PrintN(Line)
    Text + #CRLF$
    PrintN("")
    
    Line = "Cumulative gaps"
    Text + Line + #CRLF$
    PrintN(Line)
    Line = ""
    iGap.q = 0
    nGaps.q = 0
    Repeat
      If Gaps(iGap) <> 0
          nGaps + Gaps(iGap)
          Line + "[" + Str(iGap) + ", " + Str(nGaps) + "], "
      EndIf
      iGap + 1
    Until iGap > #maxGaps - 1
    Text + Line + #CRLF$
    PrintN(Line)
    Text + #CRLF$
    PrintN("")
    
    Line = "Normalized cumulative gaps"
    Text + Line + #CRLF$
    PrintN(Line)
    Line = ""
    iGap.q = 0
    n1Gaps.q = 0
    Repeat
      If Gaps(iGap) <> 0
          n1Gaps + Gaps(iGap)
          Line + "[" + Str(iGap) + ", " + StrD(n1Gaps / nGaps, 15) + "], "
      EndIf
      iGap + 1
    Until iGap > #maxGaps - 1
    Text + Line + #CRLF$
    PrintN(Line)
    Text + #CRLF$
    PrintN("")
    
    Line = "Gaps modulo 3"
    Text + Line + #CRLF$
    PrintN(Line)
    iMod3.q = 0
    Dim iModnGaps.q(2)
    Repeat
      iGap.q = 0
      sGapMod3.s = Str(iMod3) + " Mod 3 ["
      Repeat
        If (Gaps(iGap) <> 0) And ((iGap % 3) = iMod3)
          sGapMod3 + "[" + Str(iGap) + ", " + Str(Gaps(iGap)) + "], "
          iModnGaps(iMod3) + Gaps(iGap)
        EndIf
        iGap + 1
      Until iGap > #maxGaps - 1
      sGapMod3 + "]"
      Line = sGapMod3 + #CRLF$
      PrintN(Line)
      Text + Line + #CRLF$
      iMod3 + 1
    Until iMod3 > 2
    PrintN("")
    Text + #CRLF$
    Line = "0 : " + Str(iModnGaps(0)) + #TAB$ + "1 : " + Str(iModnGaps(1)) + #TAB$ + "2 : " + Str(iModnGaps(2)) + #TAB$ + "Total : " + Str(iModnGaps(0) + iModnGaps(1) + iModnGaps(2))
    PrintN(Line)
    PrintN("")
    Text + #CRLF$
    Text + Line + #CRLF$
    Text + #CRLF$
    
    iMod3.q = 0
    Repeat
      iGap.q = 0
      sGapMod3.s = Str(iMod3) + " Mod 3 ["
      Repeat
        If (Gaps(iGap) <> 0) And ((iGap % 3) = iMod3)
          sGapMod3 + "[" + Str(iGap) + ", " + StrD(Gaps(iGap) / iModnGaps(iMod3), 15) + "], "
        EndIf
        iGap + 1
      Until iGap > #maxGaps - 1
      sGapMod3 + "]"
      Line = sGapMod3 + #CRLF$
      PrintN(Line)
      Text + Line + #CRLF$
      iMod3 + 1
    Until iMod3 > 2
    PrintN("")
    Text + #CRLF$
    Text + #CRLF$
    
    Line = "Max gap trends"
    Text + Line + #CRLF$
    PrintN(Line)
    SortList(sGaps(), #PB_Sort_Ascending)
    ForEach sGaps()
      v1.q = Val(StringField(sGaps(), 1, #TAB$))
      v2.q = Val(StringField(sGaps(), 2, #TAB$))
      If v2 > oldv2.q Or oldv2 = 0
        Line = sGaps()
        Text + Line + #CRLF$
        PrintN(Line)
        oldv2 = v2
      EndIf
    Next
    PrintN("")
    Text + #CRLF$
    Line = "Slice2SoE - Total gaps : " + FormatNumber(nGaps, 0, ",", ".")
    Text + Line + #CRLF$
    PrintN(Line)
    Line = "Slice2SoE - " + FormatNumber(nPrimes, 0, ",", ".") + " primes in " + StrD(Time, 3) + " s"
    Text + Line + #CRLF$
    PrintN(Line)
    PrintN("===============================")
    ForEach PI_n_ref()
      If nPrimes = PI_n_ref()\q And nmin < 3 And nmax = Pow(10, PI_n_ref()\p)
        Line = "Matches PI(" + FormatNumber(Pow(10, PI_n_ref()\p), 0, ",", ".") + ") ... good guess"
        Text + Line + #CRLF$
        PrintN(Line)
        If PI_n_ref()\check = ThisGaps
          Line = "Matches gaps counts ... excellent"
          Text + Line + #CRLF$
          PrintN(Line)
        Else
          Line = "A PUPrimeGapAnalyzer is a PUPrimeGapAnalyzer, try a rich one."
          Text + Line + #CRLF$
          PrintN(Line)
          PrintN(PI_n_ref()\check)
          PrintN(ThisGaps)
        EndIf
      EndIf
    Next
    
    If WriteDocument
        FileName.s = "Slice2SoE " + RSet(FormatNumber(nmin, 0, ",", "."), 24, "_") + " - " + RSet(FormatNumber(nmax, 0, ",", "."), 24, "_") + " " + FormatDate("%yyyy%mm%dd%hh%ii%ss", Date()) + ".txt"
        FileNumber.q = CreateFile(#PB_Any, FileName)
        WriteStringN(FileNumber, Text)
;         WriteStringN(FileNumber, "")
;         WriteStringN(FileNumber, "")
;         For iSlice.q = 0 To nSlices
;           WriteStringN(FileNumber, "Slice " + Str(iSlice))
;           WriteStringN(FileNumber, "-----------------------")
;           For iGap.q = 0 To 2000
;             If Slice2(iSlice)\Gaps[iGap] <> 0
;               WriteStringN(FileNumber, Str(iGap) + #TAB$ + Str(Slice2(iSlice)\Gaps[iGap]))
;             EndIf
;           Next
;         Next
        CloseFile(FileNumber)
        Directory.s = GetCurrentDirectory()
        FileName = Directory + "\" + FileName
        If OpenDocument
            ShellExecute_(0, "open", @FileName, #Null, @Directory, #SW_SHOW)
        EndIf
    EndIf
    
    If Wait_At_End
      PrintN("")
      PrintN("Push ESC key to quit ...")
      
      While Inkey() = ""
        Delay(50)
      Wend
    EndIf
  CloseConsole()
EndIf
If ProgramParameters_FileName <> ""
    DeleteFile(ProgramParameters_FileName)
EndIf
RunProgram(ProgramFilename(), "", GetCurrentDirectory())
CallDebugger
End
;
; Data stored here are for check purpose only, but useful to confirm results looks good.
;
DataSection
  Data.q 0, 0
  Data.q 1,  4
  Data.q 2,  25
  Data.q 3,  168
  Data.q 4,  1229
  Data.q 5,  9592
  Data.q 6,  78498
  Data.q 7,  664579
  Data.q 8,  5761455
  Data.q 9,  50847534
  Data.q 10,  455052511
  Data.q 11,  4118054813
  Data.q 12,  37607912018
  Data.q 13,  346065536839
  Data.q 14,  3204941750802
  Data.q 15,  29844570422669
  Data.q 16,  279238341033925
  Data.q 17,  2623557157654233
  Data.q 18,  24739954287740860
  Data.q 19,  234057667276344607
  Data.q 20,  2220819602560918840
  Data.q 21,  21127269486018731928
  Data.q 22,  201467286689315906290
;   Data.q 23,  1925320391606803968923
;   Data.q 24,  18435599767349200867866
;   Data.q 25,  176846309399143769411680
;   Data.q 26,  1699246750872437141327603 
  Data.q 999999999, 999999999
  Data.q 0
  Data.s ""
  Data.q 1
  Data.s "[1, 1], [2, 2], "
  Data.q 2
  Data.s "[1, 1], [2, 8], [4, 7], [6, 7], [8, 1], "
  Data.q 3
  Data.s "[1, 1], [2, 35], [4, 40], [6, 44], [8, 15], [10, 16], [12, 7], [14, 7], [18, 1], [20, 1], "
  Data.q 4
  Data.s "[1, 1], [2, 205], [4, 202], [6, 299], [8, 101], [10, 119], [12, 105], [14, 54], [16, 33], [18, 40], [20, 15], [22, 16], [24, 15], [26, 3], [28, 5], [30, 11], [32, 1], [34, 2], [36, 1], "
  Data.q 5
  Data.s "[1, 1], [2, 1224], [4, 1215], [6, 1940], [8, 773], [10, 916], [12, 964], [14, 484], [16, 339], [18, 514], [20, 238], [22, 223], [24, 206], [26, 88], [28, 98], [30, 146], [32, 32], [34, 33], [36, 54], [38, 19], [40, 28], [42, 19], [44, 5], [46, 4], [48, 3], [50, 5], [52, 7], [54, 4], [56, 1], [58, 4], [60, 1], [62, 1], [64, 1], [72, 1], "
  Data.q 6
  Data.s "[1, 1], [2, 8169], [4, 8143], [6, 13549], [8, 5569], [10, 7079], [12, 8005], [14, 4233], [16, 2881], [18, 4909], [20, 2401], [22, 2172], [24, 2682], [26, 1175], [28, 1234], [30, 1914], [32, 550], [34, 557], [36, 767], [38, 330], [40, 424], [42, 476], [44, 202], [46, 155], [48, 196], [50, 106], [52, 77], [54, 140], [56, 53], [58, 54], [60, 96], [62, 16], [64, 24], [66, 48], [68, 13], [70, 22], [72, 13], [74, 12], [76, 6], [78, 13], [80, 3], [82, 5], [84, 6], [86, 4], [88, 1], [90, 4], [92, 1], [96, 2], [98, 1], [100, 2], [112, 1], [114, 1], "
  Data.q 7
  Data.s "[1, 1], [2, 58980], [4, 58621], [6, 99987], [8, 42352], [10, 54431], [12, 65513], [14, 35394], [16, 25099], [18, 43851], [20, 22084], [22, 19451], [24, 27170], [26, 12249], [28, 13255], [30, 21741], [32, 6364], [34, 6721], [36, 10194], [38, 4498], [40, 5318], [42, 7180], [44, 2779], [46, 2326], [48, 3784], [50, 2048], [52, 1449], [54, 2403], [56, 1072], [58, 1052], [60, 1834], [62, 543], [64, 559], [66, 973], [68, 358], [70, 524], [72, 468], [74, 218], [76, 194], [78, 362], [80, 165], [82, 100], [84, 247], [86, 66], [88, 71], [90, 141], [92, 37], [94, 39], [96, 65], [98, 29], [100, 36], [102, 34], [104, 21], [106, 12], [108, 26], [110, 11], [112, 11], [114, 11], [116, 7], [118, 4], [120, 10], [122, 3], [124, 4], [126, 8], [128, 2], [130, 1], [132, 5], [134, 1], [136, 2], [138, 2], [140, 2], [146, 1], [148, 2], [152, 1], [154, 1], "
  Data.q 8
  Data.s "[1, 1], [2, 440312], [4, 440257], [6, 768752], [8, 334180], [10, 430016], [12, 538382], [14, 293201], [16, 215804], [18, 384738], [20, 202922], [22, 175945], [24, 257548], [26, 119465], [28, 129567], [30, 222847], [32, 68291], [34, 71248], [36, 114028], [38, 51756], [40, 60761], [42, 86637], [44, 34881], [46, 29327], [48, 49824], [50, 27522], [52, 20595], [54, 33593], [56, 16595], [58, 14611], [60, 28439], [62, 8496], [64, 8823], [66, 15579], [68, 6200], [70, 8813], [72, 8453], [74, 4316], [76, 3580], [78, 6790], [80, 3281], [82, 2362], [84, 4668], [86, 1597], [88, 1637], [90, 3337], [92, 1083], [94, 971], [96, 1641], [98, 851], [100, 878], [102, 1059], [104, 494], [106, 404], [108, 711], [110, 454], [112, 330], [114, 487], [116, 191], [118, 181], [120, 433], [122, 131], [124, 145], [126, 204], [128, 76], [130, 78], [132, 132], [134, 50], [136, 40], [138, 93], [140, 57], [142, 30], [144, 51], [146, 22], [148, 34], [150, 37], [152, 20], [154, 13], [156, 23], [158, 10], [160, 11], [162, 8], [164, 5], [166, 1], [168, 8], [170, 6], [172, 1], [174, 3], [176, 5], [178, 4], [180, 4], [182, 1], [184, 1], [196, 1], [198, 1], [210, 2], [220, 1], "
  Data.q 9
  Data.s "[1, 1], [2, 3424506], [4, 3424679], [6, 6089791], [8, 2695109], [10, 3484767], [12, 4468957], [14, 2464565], [16, 1846097], [18, 3351032], [20, 1824043], [22, 1569679], [24, 2367474], [26, 1119585], [28, 1219243], [30, 2177991], [32, 683896], [34, 719531], [36, 1171524], [38, 548746], [40, 648780], [42, 954456], [44, 389634], [46, 334720], [48, 577247], [50, 328066], [52, 245804], [54, 410754], [56, 211462], [58, 181948], [60, 371839], [62, 115558], [64, 118951], [66, 216787], [68, 88396], [70, 125564], [72, 126663], [74, 62526], [76, 55113], [78, 105313], [80, 53522], [82, 37982], [84, 78077], [86, 27793], [88, 28878], [90, 58057], [92, 19282], [94, 17669], [96, 31078], [98, 16175], [100, 16900], [102, 22393], [104, 10310], [106, 8719], [108, 15459], [110, 9065], [112, 7139], [114, 10892], [116, 4710], [118, 4502], [120, 9621], [122, 2975], [124, 3136], [126, 5863], [128, 2043], [130, 2813], [132, 3510], [134, 1487], [136, 1297], [138, 2589], [140, 1516], [142, 953], [144, 1555], [146, 668], [148, 724], [150, 1486], [152, 506], [154, 583], [156, 798], [158, 306], [160, 360], [162, 534], [164, 247], [166, 182], [168, 443], [170, 198], [172, 158], [174, 255], [176, 128], [178, 106], [180, 200], [182, 80], [184, 89], [186, 101], [188, 33], [190, 63], [192, 74], [194, 32], [196, 41], [198, 73], [200, 28], [202, 23], [204, 46], [206, 13], [208, 18], [210, 48], [212, 12], [214, 11], [216, 15], [218, 6], [220, 12], [222, 11], [224, 6], [226, 6], [228, 3], [230, 3], [232, 1], [234, 13], [236, 5], [238, 1], [240, 5], [242, 4], [244, 2], [246, 4], [248, 4], [250, 4], [252, 1], [260, 1], [276, 1], [282, 1], "
  Data.q 10
  Data.s "[1, 1], [2, 27412679], [4, 27409998], [6, 49392723], [8, 22160841], [10, 28764495], [12, 37588207], [14, 20943953], [16, 15888305], [18, 29189691], [20, 16296072], [22, 13954281], [24, 21487314], [26, 10347754], [28, 11287965], [30, 20706430], [32, 6641319], [34, 6996756], [36, 11593345], [38, 5517814], [40, 6576432], [42, 9919519], [44, 4106969], [46, 3580023], [48, 6250795], [50, 3607750], [52, 2742998], [54, 4627165], [56, 2431627], [58, 2096211], [60, 4404886], [62, 1409677], [64, 1434957], [66, 2681236], [68, 1114470], [70, 1597388], [72, 1655084], [74, 821015], [76, 740952], [78, 1424800], [80, 741798], [82, 530510], [84, 1110789], [86, 405566], [88, 422019], [90, 865284], [92, 295784], [94, 271435], [96, 484048], [98, 256434], [100, 268933], [102, 362835], [104, 168126], [106, 147930], [108, 260372], [110, 159634], [112, 125859], [114, 193931], [116, 84057], [118, 82505], [120, 177747], [122, 57117], [124, 58876], [126, 113149], [128, 41252], [130, 56413], [132, 72711], [134, 31326], [136, 28577], [138, 53746], [140, 34102], [142, 20263], [144, 35563], [146, 15289], [148, 16689], [150, 34981], [152, 11307], [154, 14163], [156, 19972], [158, 8328], [160, 10075], [162, 13264], [164, 6398], [166, 5488], [168, 12220], [170, 5948], [172, 3953], [174, 7275], [176, 3337], [178, 3045], [180, 6809], [182, 2639], [184, 2286], [186, 3683], [188, 1503], [190, 2299], [192, 2588], [194, 1165], [196, 1254], [198, 2123], [200, 1102], [202, 822], [204, 1427], [206, 649], [208, 677], [210, 1531], [212, 383], [214, 356], [216, 645], [218, 300], [220, 416], [222, 495], [224, 250], [226, 204], [228, 370], [230, 203], [232, 137], [234, 303], [236, 114], [238, 134], [240, 214], [242, 76], [244, 79], [246, 96], [248, 73], [250, 56], [252, 128], [254, 31], [256, 41], [258, 95], [260, 43], [262, 29], [264, 53], [266, 32], [268, 23], [270, 42], [272, 15], [274, 16], [276, 24], [278, 9], [280, 16], [282, 18], [284, 8], [286, 12], [288, 15], [290, 13], [292, 6], [294, 7], [296, 3], [298, 1], [300, 12], [302, 4], [304, 3], [306, 6], [308, 2], [310, 2], [312, 3], [314, 2], [318, 1], [320, 3], [322, 2], [326, 2], [330, 2], [332, 1], [336, 2], [340, 1], [354, 1], "
  Data.q 11
  Data.s "[1, 1], [2, 224376048], [4, 224373160], [6, 408550278], [8, 185402143], [10, 241298621], [12, 319972455], [14, 179718000], [16, 137715130], [18, 255371697], [20, 145357801], [22, 124059643], [24, 194202685], [26, 94842255], [28, 103578486], [30, 194021497], [32, 63276295], [34, 66745265], [36, 112117864], [38, 54024702], [40, 64778166], [42, 99655858], [44, 41731672], [46, 36721672], [48, 64725830], [50, 37931633], [52, 29110017], [54, 49484726], [56, 26393893], [58, 22788376], [60, 48938947], [62, 15996645], [64, 16191513], [66, 30832085], [68, 13014024], [70, 18781144], [72, 19871913], [74, 9894134], [76, 9045284], [78, 17533020], [80, 9297902], [82, 6710732], [84, 14213581], [86, 5286056], [88, 5515262], [90, 11529753], [92, 3999903], [94, 3702704], [96, 6646911], [98, 3575334], [100, 3768377], [102, 5195880], [104, 2438342], [106, 2150689], [108, 3842700], [110, 2408320], [112, 1901120], [114, 2973328], [116, 1304852], [118, 1281209], [120, 2827970], [122, 932119], [124, 950525], [126, 1876467], [128, 688521], [130, 951137], [132, 1255967], [134, 539060], [136, 504434], [138, 946138], [140, 610589], [142, 369332], [144, 652975], [146, 287891], [148, 310055], [150, 663799], [152, 220450], [154, 274631], [156, 394607], [158, 168199], [160, 202074], [162, 269332], [164, 132725], [166, 117344], [168, 254517], [170, 126810], [172, 84647], [174, 160174], [176, 75607], [178, 68952], [180, 154009], [182, 62244], [184, 51620], [186, 87656], [188, 37289], [190, 53394], [192, 62838], [194, 29281], [196, 32118], [198, 53702], [200, 28100], [202, 20935], [204, 39009], [206, 16293], [208, 18087], [210, 41968], [212, 10936], [214, 10726], [216, 19193], [218, 8794], [220, 12305], [222, 14889], [224, 8229], [226, 5937], [228, 11803], [230, 6638], [232, 4830], [234, 8660], [236, 3596], [238, 4317], [240, 7773], [242, 2856], [244, 2522], [246, 4724], [248, 2099], [250, 2583], [252, 4021], [254, 1608], [256, 1397], [258, 2798], [260, 1694], [262, 1051], [264, 2085], [266, 1067], [268, 869], [270, 1873], [272, 579], [274, 655], [276, 1071], [278, 473], [280, 755], [282, 784], [284, 357], [286, 382], [288, 552], [290, 357], [292, 238], [294, 529], [296, 196], [298, 172], [300, 420], [302, 134], [304, 174], [306, 252], [308, 152], [310, 151], [312, 175], [314, 88], [316, 74], [318, 126], [320, 75], [322, 81], [324, 88], [326, 41], [328, 49], [330, 106], [332, 33], [334, 28], [336, 60], [338, 27], [340, 36], [342, 44], [344, 20], [346, 20], [348, 41], [350, 20], [352, 17], [354, 22], [356, 11], [358, 5], [360, 29], [362, 7], [364, 8], [366, 15], [368, 5], [370, 2], [372, 7], [374, 7], [376, 3], [378, 6], [380, 10], [382, 2], [384, 8], [386, 4], [390, 8], [394, 1], [396, 5], [398, 2], [400, 1], [402, 3], [406, 2], [410, 1], [414, 2], [420, 2], [432, 1], [444, 1], [450, 1], [456, 1], [464, 1], "
  Data.q 999999999, 999999999
EndDataSection
Amusez-vous bien.
