{
our
$VERSION
=
'0.004'
; }
has
order
=> (
is
=>
'ro'
);
has
p
=> (
is
=>
'ro'
);
has
n
=> (
is
=>
'ro'
);
has
order_is_prime
=> (
is
=>
'ro'
);
has
element_class
=> (
is
=>
'ro'
);
has
sum_table
=> (
is
=>
'ro'
);
has
prod_table
=> (
is
=>
'ro'
);
sub
additive_neutral {
return
$_
[0]->e(0) }
sub
all {
my
$self
=
shift
;
my
$eclass
=
$self
->element_class;
my
$order
=
$self
->order;
map
{
$eclass
->new(
field
=>
$self
,
v
=>
$_
) } 0 .. (
$order
- 1);
}
sub
import_builder {
my
(
$package
,
$order
) =
splice
@_
, 0, 2;
my
%args
= (
@_
&&
ref
(
$_
[0]) eq
'HASH'
) ? %{
$_
[0]} :
@_
;
my
$field
=
$package
->new(
order
=>
$order
);
my
$builder
=
sub
{
return
$field
->e(
@_
) };
my
$callpkg
=
caller
(
$args
{level} // 0);
my
$name
=
$args
{name} // (
$field
->order_is_prime
?
"GF_$order"
:
join
(
'_'
,
'GF'
,
$field
->p,
$field
->n)
);
no
strict
'refs'
;
*{
$callpkg
.
'::'
.
$name
} =
$builder
;
return
;
}
sub
e {
my
$self
=
shift
;
my
$ec
=
$self
->element_class;
return
$ec
->new(
field
=>
$self
,
v
=>
$_
[0])
unless
wantarray
;
return
map
{
$ec
->new(
field
=>
$self
,
v
=>
$_
) }
@_
;
}
sub
multiplicative_neutral {
return
$_
[0]->e(1) }
sub
BUILDARGS {
my
(
$class
,
%args
) =
@_
;
ouch 500,
'missing order'
unless
exists
$args
{order};
my
$order
=
$args
{order};
ouch 500,
'undefined order'
unless
defined
$order
;
ouch 500,
'order MUST be integer and greater than 1'
unless
$order
=~ m{\A(?: [2-9] | [1-9]\d+)\z}mxs;
my
(
$p
,
$n
) = __prime_power_decomposition(
$order
);
ouch 500,
'order MUST be a power of a prime'
unless
defined
$p
;
$args
{p} =
$p
;
$args
{n} =
$n
;
$args
{order_is_prime} = (
$n
== 1);
if
(
$n
== 1) {
$args
{order_is_prime} = 1;
$args
{element_class} =
'Math::GF::Zn'
;
delete
@args
{
qw< sum_table prod_table >
};
}
else
{
$args
{order_is_prime} = 0;
$args
{element_class} =
'Math::GF::Extension'
;
@args
{
qw< sum_table prod_table >
} = __tables(
$order
);
}
return
{
%args
};
}
sub
__tables {
my
$order
=
shift
;
my
(
$p
,
$n
) = __prime_power_decomposition(
$order
);
my
$Zp
= Math::GF->new(
order
=>
$p
);
my
@Zp_all
=
$Zp
->all;
my
(
$zero
,
$one
) = (
$Zp
->additive_neutral,
$Zp
->multiplicative_neutral);
my
$pirr
= __get_irreducible_polynomial(
$Zp
,
$n
);
my
$polys
= __generate_polynomials(
$Zp
,
$n
);
my
%id_for
=
map
{;
"$polys->[$_]"
=>
$_
} 0 ..
$#$polys
;
my
(
@sum
,
@prod
);
for
my
$i
(0 ..
$#$polys
) {
my
$I
=
$polys
->[
$i
];
push
@sum
, \
my
@ts
;
push
@prod
, \
my
@tp
;
for
my
$j
(0 ..
$i
) {
my
$J
=
$polys
->[
$j
];
my
$sum
= (
$I
+
$J
);
push
@ts
,
$id_for
{
"$sum"
};
my
$prod
= (
$I
*
$J
) %
$pirr
;
push
@tp
,
$id_for
{
"$prod"
};
}
}
return
(\
@sum
, \
@prod
);
}
sub
__generate_polynomials {
my
(
$field
,
$degree
) =
@_
;
ouch 500,
'irreducible polynomial search only over Zn field'
unless
$field
->order_is_prime;
my
$zero
=
$field
->additive_neutral;
my
$one
=
$field
->multiplicative_neutral;
my
@coeffs
= (
$zero
) x (
$degree
+ 1);
my
@retval
;
while
(
$coeffs
[-1] ==
$zero
) {
push
@retval
, Math::Polynomial->new(
@coeffs
);
for
(
@coeffs
) {
$_
=
$_
+
$one
;
last
unless
$_
==
$zero
;
}
}
return
\
@retval
;
}
sub
__get_irreducible_polynomial {
my
(
$field
,
$degree
) =
@_
;
ouch 500,
'irreducible polynomial search only over Zn field'
unless
$field
->order_is_prime;
my
$zero
=
$field
->additive_neutral;
my
$one
=
$field
->multiplicative_neutral;
my
@coeffs
= (
$one
, ((
$zero
) x (
$degree
- 1)),
$one
);
while
(
$coeffs
[-1] ==
$one
) {
my
$poly
= Math::Polynomial->new(
@coeffs
);
return
$poly
if
__rabin_irreducibility_test(
$poly
);
for
(
@coeffs
) {
$_
=
$_
+
$one
;
last
unless
$_
==
$zero
;
}
}
ouch 500,
"no monic irreducibile polynomial!"
;
}
sub
__to_poly {
my
(
$x
,
$n
) =
@_
;
my
@coeffs
;
while
(
$x
) {
push
@coeffs
,
$x
%
$n
;
$x
= (
$x
-
$coeffs
[-1]) /
$n
;
}
push
@coeffs
, 0
unless
@coeffs
;
return
Z_poly(
$n
,
@coeffs
);
}
sub
__rabin_irreducibility_test {
my
$f
=
shift
;
my
$n
=
$f
->degree;
my
$one
=
$f
->coeff_one;
my
$pone
= Math::Polynomial->monomial(0,
$one
);
my
$x
= Math::Polynomial->monomial(1,
$one
);
my
$q
=
$one
->n;
my
$ps
= __prime_divisors_of(
$n
);
for
my
$pi
(
@$ps
) {
my
$ni
=
$n
/
$pi
;
my
$qni
=
$q
*
*$ni
;
my
$h
= (Math::Polynomial->monomial(
$qni
,
$one
) -
$x
) %
$f
;
my
$g
=
$h
->gcd(
$f
,
'mod'
);
return
if
$g
->degree > 1;
}
my
$t
= (Math::Polynomial->monomial(
$q
*
*$n
,
$one
) -
$x
) %
$f
;
return
$t
->degree == -1;
}
sub
__prime_power_decomposition {
my
$x
=
shift
;
return
if
$x
< 2;
return
(
$x
, 1)
if
$x
< 4;
my
$p
= __prime_divisors_of(
$x
,
'first only please'
);
return
(
$x
, 1)
if
$x
==
$p
;
my
$e
= 0;
while
(
$x
> 1) {
return
if
$x
%
$p
;
$x
/=
$p
;
++
$e
;
}
return
(
$p
,
$e
);
}
sub
__prime_divisors_of {
my
(
$n
,
$first_only
) =
@_
;
my
@retval
;
return
if
$n
< 2;
for
my
$p
(2, 3) {
next
if
$n
%
$p
;
return
$p
if
$first_only
;
push
@retval
,
$p
;
$n
/=
$p
until
$n
%
$p
;
}
my
$p
= 5;
my
$top
=
int
(
sqrt
(
$n
) + MARGIN);
my
$d
= 2;
while
(
$p
<=
$top
) {
if
(
$n
%
$p
== 0) {
return
$p
if
$first_only
;
unshift
@retval
,
$p
;
$n
/=
$p
until
$n
%
$p
;
$top
=
int
(
sqrt
(
$n
) + MARGIN);
}
$p
+=
$d
;
$d
= (
$d
== 2) ? 4 : 2;
}
return
$n
if
$first_only
;
push
@retval
,
$n
if
$n
> 1;
return
\
@retval
;
}
1;