hier eine leicht optimierte Version! Knapp 10% schneller, benötigt keine Array-Struct defintion!
Code: Alles auswählen
Procedure.i IsPrime_x64(Value.q)
; ============================================================================
; NAME: IsPrime_x64
; DESC: Attention! Only for ASM Backend and x64
; DESC: Performes a full deterministic Miller Rabin Test for signed
; DESC: 64 Bit Intger.
; DESC: Because Miller Rabin use x², the caculation reaches
; DESC: 128Bit results. The CPU internal computes x64 MUL, DIV
; DESC: with 128Bit in RAX:RDX, so we can use 128Bit in ASM-Code.
; DESC: In normal PB-Code this isn't possible without a BigInt Lib.
; VAR(Value.q) : The Number to chrunch
; RET.i : #True: if Number is Prime
; ============================================================================
; based on Code from PB-Forum from STARGATE
; https://www.purebasic.fr/german/viewtopic.php?p=361719&hilit=Primzahlen#p361719
; https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test
; https://en.wikipedia.org/wiki/Exponentiation_by_squaring
; https://www.purebasic.fr/german/viewtopic.php?f=8&t=30523
; Thanks to NicknameFJ und Helle
; hand made module is a littele faster on x64 than PB Modulo % (why???)
Macro _MOD(Number, div)
(Number-(Number/div)*div)
EndMacro
Protected ret = #True
Protected *Try.Integer
Protected.i Loops, Exponent, Base
If Value < 0
Value = -Value
EndIf
#_BranchPrediction = #True ;#False/#True For 2 different versions
CompilerIf #_BranchPrediction
; ----------------------------------------------------------------------
; This Version with the seperate Modulo functions performs aprox.
; 10% better. The Reason might be the BranchPrediction which
; precalculates the ElseIf so the CPU don't have to do all the Modulos
; ----------------------------------------------------------------------
If Value <=30 ; Values up to 30 (2*3*5) , We handle direct
Select Value
Case 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ; all Primes <30
ProcedureReturn #True
Default
ProcedureReturn #False
EndSelect
; ElseIf Value&1=0 Or Value%3=0 Or Value%5=0 Or Value%7=0 Or Value%11=0 Or Value%13=0 Or Value%17=0 Or Value%19=0 Or Value%23=0 Or Value%29=0
ElseIf Value&1=0 Or _MOD(Value, 3) = 0 Or _MOD(Value, 5) = 0 Or _MOD(Value, 7) = 0
ProcedureReturn #False
ElseIf _MOD(Value, 11) = 0 Or _MOD(Value, 13) = 0 Or _MOD(Value, 17) = 0
ProcedureReturn #False
ElseIf _MOD(Value, 19) = 0 Or _MOD(Value, 23) = 0 Or _MOD(Value, 29) = 0
ProcedureReturn #False
EndIf
CompilerElse
; ----------------------------------------------------------------------
; This Version looks like to be the better and faster Code.
; But it isn't. I tested the 2 Versions on new and older CPU
; from 2011, 2016, 2020 and this version is always slower!
; ----------------------------------------------------------------------
If Value <=30 ; Values up to 30 (2*3*5) , We handle direct
Select Value
Case 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ; all Primes <30
ProcedureReturn #True
Default
ProcedureReturn #False
EndSelect
ElseIf (Value & 1) = 0 ; If Even(Value)
ProcedureReturn #False ; Not a Prime!
Else ; from here on we have: Even Values > 30
Select (Value % 30) ; Check Mod30 RestClasses: (Value Mod 5 =0) or (Value Mod 3 =0)
Case 3,5,9,15,21,25,27 ; Prime not possible! Value IsNotElementOf Mod30 RestClass for PirmeCandidates
ProcedureReturn #False
; Case 1,7,11,13,17,19,23,29 ; Prime possible! Value IsElementOf Mod30 RestClass for PrimeCandidates [1,7,11,13, 17,19,23,29]
EndSelect
EndIf
; ----------------------------------------------------------------------
CompilerEndIf
; with unrolling the loop we get a little faster code
If Value < 2047
*Try = ?Lower_2047
ElseIf Value < 1373653
*Try = ?Lower_1373653
ElseIf Value < 9080191
*Try = ?Lower_9080191
ElseIf Value < 25326001
*Try = ?Lower_25326001
ElseIf Value < 3215031751
*Try = ?Lower_3215031751
ElseIf Value < 4759123141
*Try = ?Lower_4759123141
ElseIf Value < 1122004669633
*Try = ?Lower_1122004669633
ElseIf Value < 2152302898747
*Try = ?Lower_2152302898747
ElseIf Value < 3474749660383
*Try = ?Lower_3474749660383
ElseIf Value < 341550071728321
*Try = ?Lower_341550071728321
ElseIf Value < 3825123056546413051
*Try = ?Lower_3825123056546413051
Else
*Try = ?Lower_18446744073709551616
EndIf
; because for MillerRabin we need 128Bit, this is not possible in standard PB-Code
; used Registers
; RAX : operating Register MUL, DIV
; RCX : Counter
; RDX : operating Register MUL, DIV
; R8 : Exponent
; R9 : Value
; R10 : -
; Solve: 2^Loops * Exponent + 1 = Value
!MOV RAX, [p.v_Value] ; RAX = Value
!DEC RAX ; RAX - 1
!BSF RCX, RAX ; Find LSB [0..63]
!MOV [p.v_Loops], RCX ; Loops = LSB(Value)
!SHR RAX, cl ; RAX = (Value-1) >> LSB(Value)
!MOV [p.v_Exponent], RAX ; Exponent = (Value-1) >> LSB(Value)
; Witness loop
!_WhileTry:
!MOV RAX, QWORD [p.p_Try] ; RAX = *Try
!MOV RAX, [RAX] ; RAX = *Try\i
!TEST RAX, RAX ; RAX == 0
!JZ _Return ; Jump _Retrun if 0
; While *Try\i
;Base = *Try\i
!MOV RAX, QWORD [p.p_Try] ; RAX = *Try
!MOV RAX, [RAX] ; RAX = *Try\i
!MOV QWORD [p.v_Base],RAX ; Base = *Try\i
; RAX = Base^Exponent % Value
!MOV R8, [p.v_Exponent] ; R8 = Exponent
!MOV R9, [p.v_Value] ; R9 = Value
!MOV RAX, 1 ; RAX = 1
!BSR RCX, R8 ; RCX = MSB(Exponent)
;Loop:
!_InnerLoop:
!MUL RAX ; RAX = RAX * RAX
!DIV R9 ; RAX:RDX = RAX:RDX / Value
!MOV RAX, RDX ; RAX = RAX % Value : Move Devision Remainder to RAX
!BT R8, RCX ; BitTest(Exponent, RCX), starts with RDX = MSB(Exponent)
!JNC @F
!MUL QWORD [p.v_Base] ; RAX:RDX * Base
!DIV R9 ; RAX:RDX / Value
!MOV RAX, RDX ; RAX = Devision Remainder
!@@:
!DEC RCX ; RCX-1 : Bitcounter - 1
!JNS _InnerLoop
; RAX = 1 or RAX = Value-1 ?
!MOV R8, R9 ; R8 = Value
!DEC R8 ; R8 = Value -1
!CMP RAX, 1 ; If RAX = 1
!JE _Continue ; Continue
!CMP RAX, R8 ; If RAX = Value -1
!JE _Continue ; Continue
; Square-Mod-Loop: RAX = RAX^2 % Value and check for RAX = Value-1
!MOV RCX, [p.v_Loops] ; RCX = Loops
!@@: ; Repeat
!MUL RAX ; RAX * RAX
!DIV R9 ; RAX:RDX / Value
!MOV RAX, rdx ; RAX = Devsion Remainder
!CMP RAX, R8 ; If RAX = VAlue -1
!JE _Continue ; Countinue
!CMP RAX, 1 ; If RAX = 1
!JBE _NoPrime ; NoPrime
!DEC RCX ; RCX-1 : Bitcounter - 1
!JNZ @B ; Repeat If RCX<> 0
; noprime:
!_NoPrime: ; NoPrime
!XOR RAX, RAX ; RAX = 0
!MOV [p.v_ret], RAX ; ret = 0
!JMP _Return ; Jump to Return
!_Continue: ; Continue
;*Try + SizeOf(Integer)
!MOV RAX, QWORD [p.p_Try] ; RAX = *Try
!ADD RAX,8 ; RAX + 8
!MOV QWORD [p.p_Try],RAX ; *Try = *Try + 8
!JMP _WhileTry ; Repeat While Loop
;Wend
!_Return:
ProcedureReturn ret
; Witness bases
DataSection
Lower_2047: : Data.q 2, 0
Lower_1373653: : Data.q 2, 3, 0
Lower_9080191: : Data.q 31, 73, 0
Lower_25326001: : Data.q 2, 3, 5, 0
Lower_3215031751: : Data.q 2, 3, 5, 7, 0
Lower_4759123141: : Data.q 2, 7, 61, 0
Lower_1122004669633: : Data.q 2, 13, 23, 1662803, 0
Lower_2152302898747: : Data.q 2, 3, 5, 7, 11, 0
Lower_3474749660383: : Data.q 2, 3, 5, 7, 11, 13, 0
Lower_341550071728321: : Data.q 2, 3, 5, 7, 11, 13, 17, 0
Lower_3825123056546413051: : Data.q 2, 3, 5, 7, 11, 13, 17, 19, 23, 0
Lower_18446744073709551616: : Data.q 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 0
EndDataSection
EndProcedure
OpenConsole()
Define N.i
Define Count.i = 0 ; the first Prime is 2
Define Time.q = ElapsedMilliseconds()
For N = 0 To 10000000 ;Step 2
If IsPrime_x64(N)
Count + 1
EndIf
Next
PrintN("Prime numbers: "+Str(Count))
PrintN("Evaluation time: "+Str(ElapsedMilliseconds()-Time))
Input()