From c26460b255f3dfd2293de00526763ebdab66b3f2 Mon Sep 17 00:00:00 2001 From: Landon Curt Noll Date: Tue, 25 Jul 2023 22:58:58 -0700 Subject: [PATCH] add cal/fnv_tool.cal Added cal/fnv_tool.cal, a calc resource file defining: find_fnv_prime(bits) deprecated_fnv0(bits,fnv_prime,string) fnv_offset_basis(bits,fnv_prime) fnv1a_style_hash(bits,fnv_prime,prev_hash,string) Fixed sorted order of cal/README. --- CHANGES | 14 +- cal/Makefile | 6 +- cal/README | 66 ++++++- cal/fnv_tool.cal | 474 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 553 insertions(+), 7 deletions(-) create mode 100644 cal/fnv_tool.cal diff --git a/CHANGES b/CHANGES index 01d25b7..0eff2ba 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,16 @@ -The following are the changes from calc version 2.14.2.0 to date: +The following are the changes from calc version 2.14.2.1 to date: + + Added cal/fnv_tool.cal, a calc resource file defining: + + find_fnv_prime(bits) + deprecated_fnv0(bits,fnv_prime,string) + fnv_offset_basis(bits,fnv_prime) + fnv1a_style_hash(bits,fnv_prime,prev_hash,string) + + Fixed sorted order of cal/README. + + +The following are the changes from calc version 2.14.2.0 to 2.14.2.0: Ported calc to the s390x IBM Mainframe running RHEL9.1. diff --git a/cal/Makefile b/cal/Makefile index 7105115..10c2211 100644 --- a/cal/Makefile +++ b/cal/Makefile @@ -267,9 +267,9 @@ TRUE= true # to keep this list in nice sorted order. # CALC_FILES= README alg_config.cal beer.cal bernoulli.cal \ - bernpoly.cal bigprime.cal bindings brentsolve.cal chi.cal \ - chrem.cal comma.cal constants.cal deg.cal dms.cal dotest.cal \ - ellip.cal factorial.cal factorial2.cal gvec.cal hello.cal hms.cal \ + bernpoly.cal bigprime.cal bindings brentsolve.cal chi.cal chrem.cal \ + comma.cal constants.cal deg.cal dms.cal dotest.cal ellip.cal \ + factorial.cal factorial2.cal fnv_tool.cal gvec.cal hello.cal hms.cal \ infinities.cal intfile.cal intnum.cal lambertw.cal linear.cal \ lnseries.cal lucas.cal lucas_chk.cal mersenne.cal mfactor.cal mod.cal \ natnumset.cal palindrome.cal pell.cal pi.cal pix.cal pollard.cal \ diff --git a/cal/README b/cal/README index 1278614..7c1bfca 100644 --- a/cal/README +++ b/cal/README @@ -388,6 +388,13 @@ dotest.cal dotest("set8700.line"); +ellip.cal + + efactor(iN, ia, B, force) + + Attempt to factor using the elliptic functions: y^2 = x^3 + a*x + b. + + factorial.cal factorial(n) @@ -548,12 +555,65 @@ factorial2.cal for information on falling factorials. -ellip.cal +fnv_util.cal - efactor(iN, ia, B, force) + Utility tools for FNV hash and "FNV-style" hash operations. - Attempt to factor using the elliptic functions: y^2 = x^3 + a*x + b. + These functions, if given non-standard values, will produce bogus results. + In some cases, such as specifying the number of bits in the hash, + using a non-power of 2 bit will produce a result that may work, + but the hash will be only an "FNV-style" hash and not a true FNV hash. + find_fnv_prime(bits) + + If bits == null(), this function will attempt to prompt stdin + for a value and provide commends on the value of bits. + + given: + bits number of bits in the hash, null() ==> prompt for value + + returns: + 0 ==> no FNV prime found + >0 ==> FNV prime + + deprecated_fnv0(bits, fnv_prime, string) + + If fnv_prime == null(), this function will try to compute the FNV prime + for a hash of size bits. + + given: + bits number of bits in FNV hash + fnv_prime FNV prime, null() ==> generate suitable FNV prime if possible + string string to hash + + returns: + FNV-0 hash, for size bytes, of string + + NOTE: This function does NOT attempt to determine that fnv_prime is prime. + + fnv_offset_basis(bits, fnv_prime) + + given: + bits number of bits in FNV hash + fnv_prime FNV prime, null() ==> generate suitable FNV prime if possible + + returns: + FNV offset basis for a hash size of bits and an FNV prime of fnv_prime + + NOTE: This function does NOT attempt to determine that fnv_prime is prime. + + fnv1a_style_hash(bits, fnv_prime, prev_hash, string) + + given: + bits number of bits in FNV hash + fnv_prime FNV prime, null() ==> generate suitable FNV prime if possible + prev_hash previous hash value, null() ==> generate FNV offset basis + string string to hash + + returns: + "FNV-style" hash of bits + + NOTE: This function does NOT attempt to determine that fnv_prime is prime. gvec.cal diff --git a/cal/fnv_tool.cal b/cal/fnv_tool.cal new file mode 100644 index 0000000..1769cdf --- /dev/null +++ b/cal/fnv_tool.cal @@ -0,0 +1,474 @@ +/* + * fnv_util - utility tools for FNV hash and "FNV-style" hash operations + * + * This file provides the following functions: + * + * find_fnv_prime(bits) + * deprecated_fnv0(bits, fnv_prime, string) + * fnv_offset_basis(bits, fnv_prime) + * fnv1a_style_hash(bits, fnv_prime, prev_hash, string) + * + * See the individual function for details on args and return value. + * + * If no args are given to find_fnv_prime() and stdin is associated + * with a tty (i.e., an interactive terminal), then bits will be + * prompted for and commentary will be printed to stdout as well. + * + * If fnv_prime == null(), then an attempt to compute the FNV prime + * for a hash if size bits is attempted.. + * + * If prev_hash == null(), then the FNV offset basis for + * for a hash if size bits is computed. + * + * For more information on the FNV hash see: + * + * https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function + * http://www.isthe.com/chongo/tech/comp/fnv/index.html + * + * IMPORTANT NOTE: + * + * These functions, if given non-standard values, will produce bogus results. + * In some cases, such as specifying the number of bits in the hash, + * using a non-power of 2 bit will produce a result that may work, + * but the hash will be only an "FNV-style" hash and not a true FNV hash. + * + * We say "FNV-style" because the result hash is not a "true FNV-like" hash. + * + * Let integer n > 0 be the number if bits in the FNV hash. Then: + * + * t = floor((5+n)/12) + * + * The FNV prime, for the given n bits is the smallest prime of the form: + * + * p = 256^t + 2^8 + b + * + * such that: + * + * 0 < b < 2^8 + * The number of one-bits in b must be 4 or 5 + * p mod (2^40 - 2^24 - 1) > (2^24 + 2^8 + 2^7) + * + * If you force n to not be a power of 2, for example: + * + * n = 44 + * + * you will find that the FNV prime for 44 bits is: + * + * p44 = 4294967597 + * = 0x10000012d + * = 0b100000000000000000000000100101101 + * = 2^32 + 301 = 2^32 + 2^8 + 2^5 + 2^3 + 2^2 + 2^0 + * + * However a hash size of 44 bits is not a true FNV hash, it is only a "FNV-style" hash. + * + * NOTE: We disallow n <= 31 because there are no FNV primes that small. + * + * NOTE: For n that is a power of 2 and n > 1024, you will find that + * that FNV primes become so rare that that one may not find a suitable + * FNV prime. For n = 2048, 4096, 8192, 16384, 32768, 65536, 131072 + * and 262144, there is NO suitable FNV prime. + * + * As for as hashing goes, large values of n, even if an + * FNV hash may be found, are unlikely to be truly useful. :-) + */ +/* + * Copyright (c) 2023 by Landon Curt Noll. All Rights Reserved. + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby granted, + * provided that the above copyright, this permission notice and text + * this comment, and the disclaimer below appear in all of the following: + * + * supporting documentation + * source copies + * source works derived from this source + * binaries derived from this source or from derived source + * + * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO + * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + * chongo (Landon Curt Noll, http://www.isthe.com/chongo/index.html) /\oo/\ + * + * Share and enjoy! :-) + */ + + +/* + * find_fnv_prime - try to find a FNV prime given the number of bits + * + * If bits == null(), this function will attempt to prompt stdin + * for a value and provide commends on the value of bits. + * + * given: + * bits number of bits in the hash, null() ==> prompt for value + * + * returns: + * 0 ==> no FNV prime found + * >0 ==> FNV prime + */ +define find_fnv_prime(bits) +{ + local b; /* lower octet of the potential FNV prime: [1,255] */ + local p; /* value to test as an FNV prime */ + local t; /* power of 256 part of the FNV prime */ + local one_bits; /* number of 1 bits in b */ + local p_minus_b; /* potential FNV prime less b */ + local interactive; /* true ==> interactive mode and print commentary */ + + /* + * case: no arg, prompt for bits and print commentary + */ + interactive = 0; /* assume non-interactive mode */ + if (isnull(bits)) { + + /* + * must be attached to an interactive terminal + */ + if (!isatty(files(0))) { + print "# FATAL: stdin is not a tty: not attached to an interactive terminal"; + return 0; + } + interactive = 1; /* set interactive mode */ + + /* + * prompt for the number of bits + */ + do { + local strscanf_ret; /* return from strscanf_ret */ + local input; /* value read after prompt */ + + /* + * prompt and obtain the input + */ + input = prompt("Enter hash size in bits: "); + strscanf_ret = strscanf(input, "%i", bits); + print "input =", input; + print "bits =", bits; + if (!isint(bits) || bits <= 0) { + print; + print "# NOTE: must enter a integer > 0, try again"; + print; + } + } while (!isint(bits) || bits <= 0); + } + + /* + * firewall - bits must be non-negative integer + */ + if (!isint(bits) || bits < 0) { + if (interactive) { + print "# FATAL: bits must be non-negative integer"; + } + return 0; + } + + /* + * provide commentary on the choice of bits if we are interactive + */ + if (interactive) { + if (popcnt(bits) == 1) { + if (bits > 1024) { + print "# WARNING: FNV primes for powers of 2 > 1024 are extremely rare."; + print "# WARNING: There are no FNV primes for 2048, 4096, 8192, 16384, 327678, 65536, 131072, nor 262144."; + } + print "# NOTE: bits a power of 2 and bits >= 32: bits is suitable for a true FNV hash"; + print "n =", bits; + } else { + if (bits < 32) { + print "# WARNING: bits < 32 is not recommended because there isn't enough bits to be worth hashing"; + } + print "# WARNING: because bits is not a power of 2, we can only form an \"FNV-style hash\": not a true FNV hash."; + print "# WARNING: A \"FNV-style hash\" may not have the desired hash properties of a true FNV hash."; + print "n =", bits; + } + } + + /* + * search setup + */ + t = floor((5+bits)/12); + p_minus_b = 256^t + 2^8; + + /* + * search for a b that forms a suitable FNV prime + */ + for (b=1; b < 256; ++b) { + + /* + * reject b unless the of one-bits in bottom octet of p is 4 or 5 + */ + one_bits = popcnt(b); + if (one_bits != 4 && one_bits != 5) { + continue; + } + + /* + * reject potential p value that is not prime + */ + p = p_minus_b + b; + if (ptest(p) == 0) { + continue; + } + + /* + * accept p if p mod (2^40 - 2^24 - 1) > (2^24 + 2^8 + 2^7) + */ + if ((p % (2^40 - 2^24 - 1)) > (2^24 + 2^8 + 2^7)) { + return p; + } + } + + /* + * case: did not find an FNV prime + */ + if (b >= 256) { + + /* + * examine results if interactive + */ + if (interactive) { + print "# FATAL: There is no a suitable FNV prime for bits =", bits; + quit "find_fnv_prime: FATAL: FNV prime search failed"; + } + + /* + * return 0 to indicate no FNV prime found + */ + return 0; + } + + /* + * provide FNV commentary if interactive + */ + if (interactive) { + print "t =", t; + print "b =", b; + print "# NOTE: p = 256^":t, "+ 2^8 +", b; + print; + print "p =", p; + } + + /* + * return FNV prime + */ + return p; +} + + +/* + * deprecated_fnv0 - FNV-0 hash that should only be used to generate an FNV offset basis + * + * If fnv_prime == null(), this function will try to compute the FNV prime + * for a hash of size bits. + * + * given: + * bits number of bits in FNV hash + * fnv_prime FNV prime, null() ==> generate suitable FNV prime if possible + * string string to hash + * + * returns: + * FNV-0 hash, for size bytes, of string + * + * NOTE: This function does NOT attempt to determine that fnv_prime is prime. + */ +define deprecated_fnv0(bits, fnv_prime, string) +{ + local hash; /* FNV hash value */ + local len; /* length of string */ + local base; /* base of FNV hash: 2^bits */ + local i; + + /* + * firewall + */ + if (!isint(bits) || bits <= 0) { + quit "deprecated_fnv0: FATAL: bits arg must be an integer > 0"; + } + if (!isstr(string)) { + quit "deprecated_fnv0: FATAL: string arg must be a string"; + } + + /* + * fnv_prime == null() means to try and generate the FNV prime + */ + if (isnull(fnv_prime)) { + /* try to generate an FNV prime */ + fnv_prime = find_fnv_prime(bits); + if (fnv_prime == 0) { + quit "deprecated_fnv0: FATAL: no FNV prime exists for the given hash size in bits"; + } + } + if (!isint(fnv_prime) || fnv_prime <= 0) { + quit "deprecated_fnv0: FATAL: fnv_prime arg must be an integer > 0 and should be prime"; + } + + /* + * FNV-0 hash each character + */ + len = strlen(string); + base = 2^bits; + hash = 0; + for (i=0; i < len; ++i) { + hash = xor((hash * fnv_prime) % base, ord(string[i])); + } + return hash; +} + + +/* + * fnv_offset_basis - generate and FNV offset basis + * + * given: + * bits number of bits in FNV hash + * fnv_prime FNV prime, null() ==> generate suitable FNV prime if possible + * + * returns: + * FNV offset basis for a hash size of bits and an FNV prime of fnv_prime + * + * NOTE: This function does NOT attempt to determine that fnv_prime is prime. + */ +define +fnv_offset_basis(bits, fnv_prime) +{ + local fnv0_hash = 0; /* FNV-0 hash value */ + + /* string to generate a FNV offset basis - do not change this value */ + static chongo_was_here = "chongo /\\../\\"; + + /* + * firewall + */ + if (!isint(bits) || bits <= 0) { + quit "fnv_offset_basis: FATAL: bits arg must be an integer > 0"; + } + + /* + * fnv_prime == null() means to try and generate the FNV prime + */ + if (isnull(fnv_prime)) { + /* try to generate an FNV prime */ + fnv_prime = find_fnv_prime(bits); + if (fnv_prime == 0) { + quit "fnv_offset_basis: FATAL: no FNV prime exists for the given hash size in bits"; + } + } + if (!isint(fnv_prime) || fnv_prime <= 0) { + quit "fnv_offset_basis: FATAL: fnv_prime arg must be an integer > 0 and should be prime"; + } + + /* + * return the FNV-0 hash of fnv_offset_basis as the FNV offset basis + */ + fnv0_hash = deprecated_fnv0(bits, fnv_prime, chongo_was_here); + return fnv0_hash; +} + + +/* + * fnv_style_hash - compute an "FNV-1a-style" hash + * + * These functions, if given non-standard values, will produce bogus results. + * To produce a true FNV-1a hash: + * + * bits must be a power of 2 + * 32 <= bits + * fnv_prime == find_fnv_prime(bits) OR fnv_prime == null() + * prev_hash == previous FNV hash OR prev_hash == null() + * + * If fnv_prime == null(), this function will try to compute the FNV prime + * for a hash of size bits. + * + * If prev_hash == null(), this function will try to compute the FNV offset basis + * for a hash of size bits. + * + * One may chain "FNV-style" hashes by replacing the offset_basis with + * the hash state of the previous hash. For the first hash: + * + * fnv_prime = find_fnv_prime(bits) + * hash_val = fnv_style_hash(bits, fnv_prime, null(), string_a); + * + * then: + * + * hash_val = fnv_style_hash(bits, fnv_prime, hash_val, string_b); + * + * This will produce the same as the string_a concatenated with string_b: + * + * hash_val = fnv_style_hash(bits, null(), null(), string_a + string_b); + * + * NOTE: Because string_a and string_b are strings, the expression: + * + * string_a + string_b + * + * is string_a concatenated with string_b. + * + * given: + * bits number of bits in FNV hash + * fnv_prime FNV prime, null() ==> generate suitable FNV prime if possible + * prev_hash previous hash value, null() ==> generate FNV offset basis + * string string to hash + * + * returns: + * "FNV-style" hash of bits + * + * NOTE: This function does NOT attempt to determine that fnv_prime is prime. + */ +define +fnv1a_style_hash(bits, fnv_prime, prev_hash, string) +{ + local hash = 0; /* FNV hash value */ + local len; /* length of string */ + local base; /* base of FNV hash: 2^bits */ + local i; + + /* + * firewall + */ + if (!isint(bits) || bits <= 0) { + quit "fnv1a_style_hash: FATAL: bits arg must be an integer > 0"; + } + if (!isstr(string)) { + quit "fnv1a_style_hash: FATAL: string arg must be a string"; + } + + /* + * fnv_prime == null() means to try and generate the FNV prime + */ + if (isnull(fnv_prime)) { + /* try to generate an FNV prime */ + fnv_prime = find_fnv_prime(bits); + if (fnv_prime == 0) { + quit "fnv1a_style_hash: FATAL: no FNV prime exists for the given hash size in bits"; + } + } + if (!isint(fnv_prime) || fnv_prime <= 0) { + quit "fnv1a_style_hash: FATAL: fnv_prime arg must be an integer > 0 and should be prime"; + } + + /* + * prev_hash == null() means to generate the FNV offset basis + */ + if (isnull(prev_hash)) { + + /* generate the FNV offset basis for a hash of size bits */ + prev_hash = fnv_offset_basis(bits, fnv_prime); + } + if (!isint(prev_hash) || prev_hash < 0) { + quit "fnv1a_style_hash: FATAL: prev_hash arg must be an integer => 0"; + } + + /* + * FNV-1a hash each character + */ + len = strlen(string); + base = 2^bits; + hash = prev_hash; + for (i=0; i < len; ++i) { + hash = xor((hash * fnv_prime) % base, ord(string[i])); + } + return hash; +}