/* * palindrome - palindrome utilities * * Copyright (C) 2021 Landon Curt Noll * * Calc is open software; you can redistribute it and/or modify it under * the terms of the version 2.1 of the GNU Lesser General Public License * as published by the Free Software Foundation. * * Calc 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 Lesser General * Public License for more details. * * A copy of version 2.1 of the GNU Lesser General Public License is * distributed with calc under the filename COPYING-LGPL. You should have * received a copy with calc; if not, write to Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Under source code control: 2021/11/06 14:35:37 * File existed as early as: 2021 * * Share and enjoy! :-) http://www.isthe.com/chongo/tech/comp/calc/ */ /* * digitof - return the a digit of a value * * NOTE: We assume base 10 digits and place 1 is the units digit. * * given: * val value to find a digit of * place digit place * * returns: * value (>= 0 and < 10) that is the place-th digit of val * or 0 if place is not a digit of val */ define digitof(val, place) { local d; /* length of val in digits */ /* determine length */ d = digits(val); /* firewall - return 0 if digit place doesn't exist */ if (place < 1 || place > d) { return 0; } /* return the place-th digit of val as a single digit */ return (val // (10^(place-1))) % 10; } /* * copalplace - determine the other place in a palindrome * * NOTE: We assume base 10 digits and place 1 is the units digit. * * given: * d digits of a value * place digit place * * returns: * given palindrome val, the other digit paired with place * or 0 if place is not a digit of val */ define copalplace(d, place) { /* firewall - return 0 if digit place doesn't exist */ if (d < 1 || place < 1 || place > d) { return 0; } /* return digit coplace */ return d+1 - place; } /* * upperhalf - return the upper half of a palindrome * * NOTE: We assume base 10 digits and place 1 is the units digit. * * NOTE: When the value has an odd number of digits, the upper half * includes the middle digit. * * given: * val a value * * returns: * the upper half digits of a value */ define upperhalf(val) { local d; /* length of val in digits */ local halfd; /* length of upper hand of val */ /* determine length */ d = digits(val); halfd = d // 2; /* return upper half */ return (val // 10^halfd); } /* * mkpal - make a value into a palindrome * * NOTE: We assume base 10 digits and place 1 is the units digit. * * given: * val a value * * returns: * val as a palindrome with lower half being reverse digits of val */ define mkpal(val) { local d; /* length of val in digits */ local i; /* counter */ local ret; /* palindrome being formed */ /* determine length */ d = digits(val); /* insert digits in reverse order at the bottom */ ret = val; for (i=0; i < d; ++i) { ret = ret*10 + digit(val, i); } return ret; } /* * mkpalmiddigit - make a value into a palindrome with a middle digit * * NOTE: We assume base 10 digits and place 1 is the units digit. * * given: * val a value * digit the digit to put into the middle of the palindrome * * returns: * val as a palindrome with lower half being reverse digits of val * and digit as a middle digit */ define mkpalmiddigit(val, digit) { local d; /* length of val in digits */ local i; /* counter */ local ret; /* palindrome being formed */ /* determine length */ d = digits(val); /* insert digits in reverse order at the bottom */ ret = val*10 + digit; for (i=0; i < d; ++i) { ret = ret*10 + digit(val, i); } return ret; } /* * ispal - determine if a value is a palindrome * * NOTE: We assume base 10 digits and place 1 is the units digit. * * given: * val a value * * returns: * 1 ==> val is a palindrome * 0 ==> val is NOT a palindrome */ define ispal(val) { local half; /* upper half of digits of val */ local digit; /* middle digit */ /* case: val has an even number of digits */ if (iseven(digits(val))) { /* test palindrome-ness */ return (val == mkpal(upperhalf(val))); /* case: val can an odd number of digits */ } else { /* test palindrome-ness */ half = upperhalf(val); digit = half % 10; half //= 10; return (val == mkpalmiddigit(half, digit)); } } /* * palnextpal - return next palindrome from a known palindrome * * NOTE: We assume base 10 digits and place 1 is the units digit. * * given: * pal a palindrome * * returns: * next palindrome > pal */ define palnextpal(pal) { local paldigits; /* digits in pal */ local half; /* upper half of newval */ local newhalf; /* half+1 */ local newpal; /* new palindrome */ /* case: negative palindrome */ if (pal < 0) { return -(palprevpal(-pal)); } /* * start with upper half */ half = upperhalf(pal); /* * prep to find a larger palindrome */ newhalf = half+1; /* * form palindrome from new upper half * * We need to watch for the corner case where changing the * half changes the number of digits as this will impact * or even/odd number of digits processing. */ paldigits = digits(pal); if (digits(newhalf) == digits(half)) { /* no change in half digits: process as normal */ if (iseven(paldigits)) { newpal = mkpal(newhalf); } else { newpal = mkpalmiddigit(newhalf // 10, newhalf % 10); } } else { /* change in half digits: process as opposite */ if (iseven(paldigits)) { newpal = mkpalmiddigit(newhalf // 10, newhalf % 10); } else { newpal = mkpal(newhalf); } } /* * return the new palindrome */ return newpal; } /* * nextpal - return next palindrome from a value * * NOTE: We assume base 10 digits and place 1 is the units digit. * * given: * val a value * * returns: * next palindrome > val */ define nextpal(val) { local newval; /* val+1 */ local newvaldigits; /* digits in newval */ local half; /* upper half of newval */ local pal; /* palindrome test value */ local newpal; /* new palindrome */ /* case: negative value */ if (val < 0) { return -(prevpal(-val)); } /* * start looking from a larger value */ newval = val+1; newvaldigits = digits(newval); /* case: single digit palindrome */ if (newvaldigits < 2) { return newval; } /* * start with next upper half */ half = upperhalf(newval); /* * form palindrome from upper half * * We need to deal with even vs. odd digit counts * when forming a palindrome from a half as the * half may not or may include the middle digit. */ if (iseven(newvaldigits)) { pal = mkpal(half); } else { pal = mkpalmiddigit(half // 10, half % 10); } /* * case: we found a larger palindrome, we are done */ if (pal > val) { return pal; } /* * we need to find an even larger palindrome */ newpal = palnextpal(pal); /* * return the new palindrome */ return newpal; } /* * palprevpal - return previous palindrome from a palindrome * * NOTE: We assume base 10 digits and place 1 is the units digit. * * given: * pal a palindrome * * returns: * previous palindrome < pal */ define palprevpal(pal) { local paldigits; /* digits in pal */ local half; /* upper half of newval */ local newhalf; /* half+1 */ local newpal; /* new palindrome */ /* case: negative value */ if (pal < 0) { return -(palnextpal(-pal)); } /* case: single digit palindrome */ if (pal < 10) { newpal = pal-1; return newpal; } /* case: 10 or 11 */ if (pal < 12) { newpal = 9; return newpal; } /* * start with upper half */ half = upperhalf(pal); /* * prep to find a smaller palindrome */ newhalf = half-1; /* * form palindrome from new upper half * * We need to watch for the corner case where changing the * half changes the number of digits as this will impact * or even/odd number of digits processing. */ paldigits = digits(pal); if (digits(newhalf) == digits(half)) { /* no change in half digits: process as normal */ if (iseven(paldigits)) { newpal = mkpal(newhalf); } else { newpal = mkpalmiddigit(newhalf // 10, newhalf % 10); } } else { /* change in half digits: process as opposite */ if (iseven(paldigits)) { newpal = mkpalmiddigit(newhalf // 10, newhalf % 10); } else { newpal = mkpal(newhalf); } } /* * return the new palindrome */ return newpal; } /* * prevpal - return previous palindrome from a value * * NOTE: We assume base 10 digits and place 1 is the units digit. * * given: * val a value * * returns: * previous palindrome < val */ define prevpal(val) { local newval; /* val-1 */ local newvaldigits; /* digits in newval */ local half; /* upper half of newval */ local pal; /* palindrome test value */ local newpal; /* new palindrome */ /* case: negative value */ if (val < 0) { return -(nextpal(-val)); } /* * start looking from a smaller value */ newval = val-1; newvaldigits = digits(newval); /* case: single digit palindrome */ if (newvaldigits < 2) { return newval; } /* * start with previous upper half */ half = upperhalf(newval); /* * form palindrome from upper half * * We need to deal with even vs. odd digit counts * when forming a palindrome from a half as the * half may not or may include the middle digit. */ if (iseven(newvaldigits)) { pal = mkpal(half); } else { pal = mkpalmiddigit(half // 10, half % 10); } /* * case: we found a smaller palindrome, we are done */ if (pal < val) { return pal; } /* * we need to find an even smaller palindrome */ newpal = palprevpal(pal); /* * return the new palindrome */ return newpal; } /* * nextprimepal - return next palindrome that is a (highly probable) prime * * NOTE: We assume base 10 digits and place 1 is the units digit. * * given: * val a value * * returns: * next palindrome (highly probable) prime > val */ define nextprimepal(val) { local pal; /* palindrome test value */ local dpal; /* digits in pal */ /* * pre-start under the next palindrome */ pal = nextpal(val-1); /* * loop until we find a (highly probable) prime or 0 */ do { /* case: negative values and tiny values */ if (pal < 2) { return 2; } /* * compute the next palindrome */ pal = palnextpal(pal); dpal = digits(pal); /* case: 11 is the only prime palindrome with even digit count */ if (pal == 11) { return 11; } /* case: even number of digits and not 11 */ if (iseven(dpal)) { /* * Except for 11 (which is handled above already), there are * no prime palindrome with even digits. So we need to * increase the digit count and work with that larger palindrome. */ pal = nextpal(10^dpal); } /* case: palindrome is even or ends in 5 */ if (iseven(pal % 10) || (pal%10 == 10/2)) { /* * we need to increase the bottom and top digits * so that we have a chance to be prime */ pal += (1 + 10^(dpal-1)); } if (config("resource_debug") & 0x8) { print "DEBUG: nextprimepal:", pal; } } while (ptest(pal) == 0 && pal > 0); /* return palindrome that his (highly probable) prime or 0 */ return pal; } /* * prevprimepal - return prev palindrome that is a (highly probable) prime * * NOTE: We assume base 10 digits and place 1 is the units digit. * * given: * val a value * * returns: * prev palindrome (highly probable) prime < val or 0 */ define prevprimepal(val) { local pal; /* palindrome test value */ local dpal; /* digits in pal */ /* * pre-start over the previous palindrome */ pal = prevpal(val+1); /* * loop until we find a (highly probable) prime or 0 */ do { /* * case: single digit values are always palindromes */ if (val < 10) { /* * The prevcand() call will return 0 if there is no previous prime * such as the case when val < 2. */ return prevcand(pal); } /* * compute the previous palindrome */ pal = palprevpal(pal); dpal = digits(pal); /* case: 11 is the only prime palindrome with even digit count */ if (pal == 11) { return 11; } /* case: 2 digit palindrome and not 11 */ if (dpal == 2) { return 7; } /* case: even number of digits */ if (iseven(dpal)) { /* * Except for 11 (which is handled above already), there are * no prime palindrome with even digits. So we need to * decrease the digit count and work with that smaller palindrome. */ pal = prevpal(10^(dpal-1)); } /* case: palindrome is even or ends in 5 */ if (iseven(pal % 10) || (pal%10 == 10/2)) { /* * we need to decrease the bottom and top digits * so that we have a chance to be prime */ pal -= (1 + 10^(dpal-1)); } if (config("resource_debug") & 0x8) { print "DEBUG: prevprimepal:", pal; } } while (ptest(pal) == 0 && pal > 0); /* return palindrome that his (highly probable) prime or 0 */ return pal; }