#!/usr/bin/perl my \$usage = < [] Find the crystal (and integral divisor(s)) to come within Hz of . Specify frequency as #.# or #m# for MHz, or #k# for kHz. If you don't specify , 5% is assumed. EOHDR # # 20130216 MCMason: Added tolerance, sort by error (asc) and funkiness within error. # Find *all* divs within TOL range # 20120429 MCMason: original version # # TODO: Trim report: If there are multiple ways to get same frequency, keep only # the top N of best one(s) (based on funkiness) # TODO: Improve funkiness calculation: e.g. try to get something proportional # to # dividers & difficulty. Perhaps something like \$e * (log(\$f)/log(2)) # use strict; use warnings; use autodie; my \$dbg_funkiness=0; my \$Freq = shift or die \$usage; \$Freq = txt_2_MHz(\$Freq); my \$Tol = shift; if (defined \$Tol) { \$Tol = txt_2_MHz(\$Tol); } else { \$Tol = \$Freq * .05; } my @crystals = read_crystals(); my @results; sub compute { my (\$Fx, \$d) = @_; return undef unless \$d; my \$f = int(\$Fx / \$d); my \$err = abs(\$Freq - \$f); return undef if \$err > \$Tol; my \$ar = [ factorize(\$d) ]; my \$funk = factor_funkiness(@\$ar); return [ \$Fx, \$d, \$f, \$err, \$ar, \$funk ]; } for my \$Fx (@crystals) { # 'perfect' divisor my \$div = \$Fx / \$Freq; # Surrounding integral divisors my \$d = int \$div; while (my \$ar = compute(\$Fx, \$d)) { last if ! defined \$ar; my (\$xFx, \$xd, \$xf, \$xerr, \$xar, \$xfunk) = @\$ar; print "Fx:\$xFx, d:\$xd, f:\$xf, err:\$xerr, funk:\$xfunk (div:\$div)\n" if \$dbg_funkiness; push @results, \$ar; --\$d; } \$d = int \$div+1; while (my \$ar = compute(\$Fx, \$d)) { last if ! defined \$ar; my (\$xFx, \$xd, \$xf, \$xerr, \$xar, \$xfunk) = @\$ar; print "Fx:\$xFx, d:\$xd, f:\$xf, err:\$xerr, funk:\$xfunk (div:\$div)\n" if \$dbg_funkiness; push @results, \$ar; ++\$d; } } print "\n\n"; @results = sort { \$\$a[3] <=> \$\$b[3] or \$\$a[5] <=> \$\$b[5] } @results; my \$prev_freq = -1; for my \$ar (@results) { my \$funk = \$dbg_funkiness ? " \tfunkiness=\$\$ar[5]" : ""; if (\$prev_freq ne \$\$ar[2]) { printf "%11.11s (%7s) = %11s / %4s div = %-16s\$funk\n", commify(\$\$ar[2]), commify(\$\$ar[3]), commify(\$\$ar[0]), commify(\$\$ar[1]), join(" * ", @{\$\$ar[4]}); \$prev_freq = \$\$ar[2]; } else { printf " = %11s / %4s div = %-16s\$funk\n", commify(\$\$ar[0]), commify(\$\$ar[1]), join(" * ", factorize(\$\$ar[1])); } } sub txt_2_MHz { my \$t = shift; my \$orig = \$t; # Multiplier (default is MHz for /\d+\.\d*/) my \$mult = 1000000; if (\$t =~ /m/i) { # 1M8432 => 1,843,000 \$t =~ s/m/./; \$mult = 1000000; } elsif (\$t =~ /k/i) { # 32k768 => 32,768 \$t =~ s/k/./; \$mult = 1000; } if (\$t =~ /\d+(\.\d*)?|\.\d+/) { return \$t * \$mult; } die "txt_2_MHz: Unexpected input '\$orig' => '\$t', can't determine frequency."; } sub read_crystals { open my \$FH, '<', 'crystals.txt'; my %freqs; while (<\$FH>) { next if /^\s*#/; next if /^\s*\$/; if (/([.0-9]+)/) { \$freqs{\$1*1_000_000}=0; } } return sort { \$a <=> \$b } keys %freqs; } sub commify { my \$s = shift; \$s =~ s/(\d)(\d\d\d)\$/\$1,\$2/; \$s =~ s/(\d)(\d\d\d,)/\$1,\$2/g; return \$s; } sub factor_funkiness { my @factors = @_; my \$funkiness = 0; print "funkiness(",join(", ", @factors),")\n" if \$dbg_funkiness; for my \$t (@factors) { my (\$f, \$e) = split /\^/, \$t; \$e=1 if ! defined \$e; if (\$f == 2) { \$funkiness += .05*\$e; # no change } elsif (\$f == 3) { \$funkiness += 0.5 * \$e; } elsif (\$f == 5) { \$funkiness += 0.6 * \$e; } elsif (\$f < 32) { \$funkiness += 1 * \$e; } else { \$funkiness += 2 * \$e; } print "\t\$t => \$funkiness\n" if \$dbg_funkiness; } print "\tfinal == \$funkiness\n" if \$dbg_funkiness; return \$funkiness; } sub factorize { my \$num = shift; my @factors = (); my \$factor=2; my \$exp=0; while (\$num%\$factor == 0) { ++\$exp; \$num /= \$factor; } if (\$exp > 1) { push @factors, "\$factor^\$exp" } elsif (\$exp) { push @factors, \$factor } \$factor=3; while (\$factor*\$factor <= \$num) { \$exp = 0; while (\$num%\$factor == 0) { ++\$exp; \$num /= \$factor; } if (\$exp > 1) { push @factors, "\$factor^\$exp" } elsif (\$exp) { push @factors, \$factor } \$factor += 2; } push @factors, \$num if \$num>1; return @factors; }