Compare commits

..

10 Commits

Author SHA1 Message Date
filifa d48e9932c1 go mod tidy 2025-11-16 22:38:32 -05:00
filifa faa58bb4dc add readme 2025-11-16 22:37:31 -05:00
filifa 6d74fdb5e3 add coprime check 2025-11-15 19:41:41 -05:00
filifa 73c750d317 move stirling methods to public package 2025-11-15 19:34:49 -05:00
filifa 85229bffe1 add multiplicative order command 2025-11-15 19:32:09 -05:00
filifa c842762ca9 adapt to update sieves for additive functions 2025-10-07 18:10:44 -04:00
filifa df317e0837 refactor updating prime powers 2025-10-07 18:06:24 -04:00
filifa 365b396db1 use common updateMultiples 2025-10-07 17:57:54 -04:00
filifa 30ad962cfd refactor updating multiples into separate common function 2025-10-07 17:54:02 -04:00
filifa 26c197318e rename functions 2025-10-07 17:28:52 -04:00
16 changed files with 185 additions and 55 deletions

36
README.md Normal file
View File

@ -0,0 +1,36 @@
# mathtools
mathtools is a program for computing various mathematical results that would be
tedious to compute by hand.
## Why?
Obviously, libraries, software packages, and websites exist for these sort of
calculations, but there are tradeoffs with any approach. Rather than needing to
write a script, use a REPL, or load a webpage, this allows for an approach more
like standard CLI utilities such as [GNU
factor](https://www.gnu.org/software/coreutils/factor).
Generally, I've opted to implement routines for problems that are best solved
with *algorithms*, rather than *formulas*. For instance, the quadratic formula,
while useful, is basically plug and chug, and thus isn't implemented here. On
the other hand, determining whether a number is prime is a little more tedious
to do by hand, so it's provided as a routine.
## Available routines
Available routines include:
* convergents of a periodic continued fraction
* solving systems of linear congruences with the Chinese remainder theorem
* discrete logarithm
* greatest common divisor
* primality testing
* Jacobi symbol
* modular inverse
* modular square root
* multiplicative order
* integer partitions
* solving Pell equations
* primitive root modulo n
* area of a simple polygon from vertex coordinates
* sieves for totient function, divisor function, Mobius function, and more
* repetend of the continued fraction of a square root
* Stirling numbers
* summatory functions

View File

@ -32,7 +32,7 @@ func divisors(cmd *cobra.Command, args []string) {
bufStdout := bufio.NewWriter(os.Stdout) bufStdout := bufio.NewWriter(os.Stdout)
defer bufStdout.Flush() defer bufStdout.Flush()
ch := sieve.DivisorsSieve(divisorsN, divisorsE, 1000) ch := sieve.Divisors(divisorsN, divisorsE, 1000)
for i := 0; ; i++ { for i := 0; ; i++ {
v, ok := <-ch v, ok := <-ch
if !ok { if !ok {

View File

@ -31,7 +31,7 @@ func mobius(cmd *cobra.Command, args []string) {
bufStdout := bufio.NewWriter(os.Stdout) bufStdout := bufio.NewWriter(os.Stdout)
defer bufStdout.Flush() defer bufStdout.Flush()
ch := sieve.MobiusSieve(mobiusN, 1000) ch := sieve.Mobius(mobiusN, 1000)
for i := 0; ; i++ { for i := 0; ; i++ {
v, ok := <-ch v, ok := <-ch
if !ok { if !ok {

View File

@ -0,0 +1,75 @@
/*
Copyright © 2025 filifa
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cmd
import (
"fmt"
"math/big"
"github.com/spf13/cobra"
"scm.dairydemon.net/filifa/mathtools/internal/lib"
)
var multiplicativeOrderBase string
var multiplicativeOrderModulus string
func multiplicativeOrder(cmd *cobra.Command, args []string) {
g, ok := new(big.Int).SetString(multiplicativeOrderBase, 10)
if !ok {
cobra.CheckErr("invalid base " + multiplicativeOrderBase)
}
m, ok := new(big.Int).SetString(multiplicativeOrderModulus, 10)
if !ok {
cobra.CheckErr("invalid modulus " + multiplicativeOrderModulus)
}
gcd := new(big.Int).GCD(nil, nil, g, m)
if gcd.Cmp(big.NewInt(1)) != 0 {
cobra.CheckErr("base " + multiplicativeOrderBase + " and modulus " + multiplicativeOrderModulus + " are not coprime")
}
k := lib.MultiplicativeOrder(g, m)
fmt.Println(k)
}
// multiplicativeOrderCmd represents the multiplicativeOrder command
var multiplicativeOrderCmd = &cobra.Command{
Use: "multiplicative-order",
Short: "Compute multiplicative order",
Long: `Compute the multiplicative order of a number given a modulus.`,
Run: multiplicativeOrder,
}
func init() {
rootCmd.AddCommand(multiplicativeOrderCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// multiplicativeOrderCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// multiplicativeOrderCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
multiplicativeOrderCmd.Flags().StringVarP(&multiplicativeOrderBase, "base", "g", "", "base")
multiplicativeOrderCmd.Flags().StringVarP(&multiplicativeOrderModulus, "modulus", "m", "", "modulus")
multiplicativeOrderCmd.MarkFlagRequired("base")
multiplicativeOrderCmd.MarkFlagRequired("modulus")
}

View File

@ -32,7 +32,7 @@ func primeOmega(cmd *cobra.Command, args []string) {
bufStdout := bufio.NewWriter(os.Stdout) bufStdout := bufio.NewWriter(os.Stdout)
defer bufStdout.Flush() defer bufStdout.Flush()
ch := sieve.PrimeOmegaSieve(primeOmegaN, primeOmegaMul, 1000) ch := sieve.PrimeOmega(primeOmegaN, primeOmegaMul, 1000)
for i := 0; ; i++ { for i := 0; ; i++ {
v, ok := <-ch v, ok := <-ch
if !ok { if !ok {

View File

@ -31,7 +31,7 @@ func radical(cmd *cobra.Command, args []string) {
bufStdout := bufio.NewWriter(os.Stdout) bufStdout := bufio.NewWriter(os.Stdout)
defer bufStdout.Flush() defer bufStdout.Flush()
ch := sieve.RadicalSieve(radicalN, 1000) ch := sieve.Radical(radicalN, 1000)
for i := 0; ; i++ { for i := 0; ; i++ {
v, ok := <-ch v, ok := <-ch
if !ok { if !ok {

View File

@ -22,7 +22,7 @@ import (
"strconv" "strconv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"scm.dairydemon.net/filifa/mathtools/internal/lib" "scm.dairydemon.net/filifa/mathtools/lib"
) )
var firstKind bool var firstKind bool

View File

@ -31,7 +31,7 @@ func totient(cmd *cobra.Command, args []string) {
bufStdout := bufio.NewWriter(os.Stdout) bufStdout := bufio.NewWriter(os.Stdout)
defer bufStdout.Flush() defer bufStdout.Flush()
for v := range sieve.TotientSieve(totientN, 1000) { for v := range sieve.Totient(totientN, 1000) {
if v == 0 { if v == 0 {
continue continue
} }

3
go.mod
View File

@ -2,8 +2,9 @@ module scm.dairydemon.net/filifa/mathtools
go 1.24.4 go 1.24.4
require github.com/spf13/cobra v1.9.1
require ( require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.6 // indirect
) )

View File

@ -0,0 +1,36 @@
/*
Copyright © 2025 filifa
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sieve
func updateMultiples(sieve []uint, p uint, n uint, additive bool) {
for q := p; ; q *= p {
// sieve[a*b] = sieve[a] * sieve[b] if gcd(a,b) = 1
for i := 2 * q; i < n; i += q {
if i%(p*q) != 0 {
if additive {
sieve[i] += sieve[q]
} else {
sieve[i] *= sieve[q]
}
}
}
if p*q >= n {
break
}
}
}

View File

@ -30,29 +30,10 @@ func pow(base uint, exp uint) uint {
return result return result
} }
func updateMultiples(sieve []uint, x uint, p uint, n uint) {
for q := p; ; q *= p {
// sigma_x(a*b) = sigma_x(a) * sigma_x(b) if gcd(a,b) = 1
for i := 2 * q; i < n; i += q {
if i%(p*q) != 0 {
sieve[i] *= sieve[q]
}
}
if p*q >= n {
break
}
println(q)
// sigma_x(p^k) = p^(kx) + sigma_x(p^(k-1))
sieve[p*q] = pow(p*q, x) + sieve[q]
}
}
/* /*
DivisorSieve computes sigma_x(k) for k=1 to n, where sigma_x is the divisor sum function. x sets the power each divisor is raised to. Divisors computes sigma_x(k) for k=1 to n, where sigma_x is the divisor sum function. x sets the power each divisor is raised to.
*/ */
func DivisorsSieve(n uint, x uint, buflen uint) chan uint { func Divisors(n uint, x uint, buflen uint) chan uint {
sieve := make([]uint, n) sieve := make([]uint, n)
sieve[0] = 0 sieve[0] = 0
for i := uint(1); i < n; i++ { for i := uint(1); i < n; i++ {
@ -69,7 +50,12 @@ func DivisorsSieve(n uint, x uint, buflen uint) chan uint {
} }
sieve[i] = pow(i, x) + 1 sieve[i] = pow(i, x) + 1
updateMultiples(sieve, x, i, n) for j := i; i*j < n; j *= i {
// sigma_x(p^k) = p^(kx) + sigma_x(p^(k-1))
sieve[i*j] = pow(i*j, x) + sieve[j]
}
updateMultiples(sieve, i, n, false)
ch <- sieve[i] ch <- sieve[i]
} }
}() }()

View File

@ -17,9 +17,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package sieve package sieve
/* /*
MobiusSieve computes mobius(k) for k=1 to n, where mobius is the Mobius function. Mobius computes mobius(k) for k=1 to n, where mobius is the Mobius function.
*/ */
func MobiusSieve(n uint, buflen uint) chan int { func Mobius(n uint, buflen uint) chan int {
sieve := make([]int, n) sieve := make([]int, n)
for i := 0; i < int(n); i++ { for i := 0; i < int(n); i++ {
sieve[i] = i sieve[i] = i

View File

@ -16,29 +16,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sieve package sieve
func primeOmegaUpdateMultiples(sieve []uint, p uint, n uint, multiplicity bool) { func updatePowers(sieve []uint, p uint, n uint) {
for q := p; ; q *= p { for q := p; p*q < n; q *= p {
// omega(a*b) = omega(a) + omega(b) if gcd(a,b) = 1
for i := 2 * q; i < n; i += q {
if i%(p*q) != 0 {
sieve[i] += sieve[q]
}
}
if p*q >= n {
break
}
if multiplicity {
sieve[p*q] = 1 + sieve[q] sieve[p*q] = 1 + sieve[q]
} }
} }
}
/* /*
PrimeOmegaSieve computes omega(k) for k=1 to n, where omega is the prime omega function. If multiplicity is true, factors are counted with multiplicity. PrimeOmega computes omega(k) for k=1 to n, where omega is the prime omega function. If multiplicity is true, factors are counted with multiplicity.
*/ */
func PrimeOmegaSieve(n uint, multiplicity bool, buflen uint) chan uint { func PrimeOmega(n uint, multiplicity bool, buflen uint) chan uint {
sieve := make([]uint, n) sieve := make([]uint, n)
for i := uint(0); i < n; i++ { for i := uint(0); i < n; i++ {
sieve[i] = 0 sieve[i] = 0
@ -54,7 +41,11 @@ func PrimeOmegaSieve(n uint, multiplicity bool, buflen uint) chan uint {
} }
sieve[i] = 1 sieve[i] = 1
primeOmegaUpdateMultiples(sieve, i, n, multiplicity) if multiplicity {
updatePowers(sieve, i, n)
}
updateMultiples(sieve, i, n, true)
ch <- sieve[i] ch <- sieve[i]
} }
}() }()

View File

@ -35,9 +35,9 @@ func radicalUpdateMultiples(sieve []uint, p uint, n uint) {
} }
/* /*
RadicalSieve computes rad(k) for k=1 to n, where rad(n) is the radical of n. Radical computes rad(k) for k=1 to n, where rad(n) is the radical of n.
*/ */
func RadicalSieve(n uint, buflen uint) chan uint { func Radical(n uint, buflen uint) chan uint {
sieve := make([]uint, n) sieve := make([]uint, n)
sieve[0] = 0 sieve[0] = 0
for i := uint(1); i < n; i++ { for i := uint(1); i < n; i++ {
@ -54,7 +54,12 @@ func RadicalSieve(n uint, buflen uint) chan uint {
} }
sieve[i] = i sieve[i] = i
radicalUpdateMultiples(sieve, i, n) for j := i; i*j < n; j *= i {
// rad(p^k) = rad(p)
sieve[i*j] = sieve[i]
}
updateMultiples(sieve, i, n, false)
ch <- sieve[i] ch <- sieve[i]
} }
}() }()

View File

@ -17,9 +17,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package sieve package sieve
/* /*
TotientSieve computes totient(k) for k=1 to n, where totient is Euler's totient function. buflen sets the buffer length of the returned channel. Larger buffer lengths can result in better performance at the cost of higher memory usage. Totient computes totient(k) for k=1 to n, where totient is Euler's totient function. buflen sets the buffer length of the returned channel. Larger buffer lengths can result in better performance at the cost of higher memory usage.
*/ */
func TotientSieve(n uint, buflen uint) chan uint { func Totient(n uint, buflen uint) chan uint {
totients := make([]uint, n) totients := make([]uint, n)
totients[0] = 0 totients[0] = 0
totients[1] = 1 totients[1] = 1