function Fraction_QuantizeValue($val, $idx, $frac_prec, &$first_denom, $style_arr, $rnd, $oc) { $ret = Fraction::toFractWithPrecision($val, $frac_prec); $nofrac1 = FALSE; $redux = FALSE; $prime_max = 0; if (DebugReporting::$dbg >= 4) { printf("fraction = %s/%s (@ prec=%s)\n", $ret->num, $ret->denom, $frac_prec); } $pos_l = 0; $pos_h = $oc; // span left hand AND right hand item! foreach ($style_arr as $st) { switch ($st) { default: if (!strncmp($st, "prime_max=", 10)) { $prime_max = (int) substr($st, 10); } else { if (!strncmp($st, "step=", 5)) { // calculus values may only be values, rounded to a given factor. $factor = (double) substr($st, 5); if (DebugReporting::$dbg >= 2) { printf("<p>fraction step: factor = %s, ret= %s, idx = %d, zero_ok = %d, pos_l = %d, pos_h = %d\n", $factor, $val, $idx, $zero_ok, $pos_l, $pos_h); } if ($idx >= $pos_l && $idx <= $pos_h) { $val = (int) ($val / $factor) * $factor; if (DebugReporting::$dbg >= 2) { printf("<p>fraction step: corrected ret: %s, factor = %s, idx = %d, zero_ok = %d\n", $val, $factor, $idx, $zero_ok); } $ret = Fraction::toFractWithPrecision($val, $frac_prec); } } else { if ($st[0] == 'T') { // value may only be one of a specified set // // originally only intended for [tables of] multiplication, but now usable anywhere. if ($idx >= $pos_l && $idx <= $pos_h) { // allow only particular tables of multiplication: set following the T! // since the multiplication tables may go BEYOND 10 while we only allow multipliers up to 10, // we cannot do this by 'filtering' the currently generated material. // Instead, we just pick the table of choice from the list and apply that to lvalue[0]. // // Of course, the answer must be recalculated too! $multipliers = explode(',', substr($st, 1)); $diff = 4000000000.0; $val = $ret->Val(); foreach ($multipliers as $m) { $delta = abs($val - (int) $m); if ($delta < $diff) { $val = (int) $m; $ret->num = $val * $ret->denom; $diff = $delta; } } if (DebugReporting::$dbg >= 2) { printf("<p>T table quatization: delta: %s, idx = %d, val: %s\n", $diff, $idx, $val); } } } else { if ($st[0] == 'p' && FALSE !== strpos("3456789", substr($st, 1)) && strlen($st) == 2) { // next filters apply to Nth (and subsequent) lvalues only! $pos_l = (int) substr($st, 1) - 1; $pos_h = $oc - 1; } } } } break; case "p1": // next filters apply to first lvalue only! $pos_l = 0; $pos_h = 0; break; case "p2": // next filters apply to second (and subsequent) lvalues only! $pos_l = 1; $pos_h = $oc - 1; break; case "pO": // next filters apply to answer (right hand) lvalue only! $pos_l = $oc; $pos_h = $oc; break; case "px": // next filters apply to ANY lvalues! $pos_l = 0; $pos_h = $oc - 1; break; case "nofrac1": // we do not accept a divisor of '1'! if ($idx >= $pos_l && $idx <= $pos_h) { $nofrac1 = TRUE; } break; case "redux": // the produced fraction MUST be reducible if ($idx >= $pos_l && $idx <= $pos_h) { $redux = TRUE; } break; case "1denom": // only permit one (common) denominator: take the denom of lvalue[0] and apply to all if (!$first_denom || $idx == 0) { $first_denom = $ret->denom; } else { if ($ret->denom != $first_denom) { // ensure the /value/ of the fraction, which gets its denominator patched, stays close to what it was: $rescale = $first_denom / $ret->denom; $ret->num = (int) ($rescale * $ret->num + 0.5); $ret->denom = $first_denom; } } break; case "1denom_M": // only permit one (common) denominator: take the denom of lvalue[0] and apply to all // // same as '1denom' apart from the fact that it also accepts 'non-reduced' fractions with the // same denominator, i.e. if denominator has been set to be '8', the fraction '1/2' is okay // too as it can be expressed in 8ths: '4/8'. // // It also picks the denominator at random, while '1denom' just picks the denominator of the // first term. if (!$first_denom || $idx == 0) { // pick a 'common denominator' at random and apply that one to each term as it comes along. $m_range = (int) (1.0 / ($frac_prec * $ret->denom)); $m = (int) ($d_range * $rnd->frand()) + 1; $d = $ret->denom * $m; if ($prime_max > 0 && !CheckPrimeMax($d, $prime_max, $frac_prec)) { $d = $ret->denom; } $first_denom = $d; } $factor = $first_denom / $ret->denom; // is our fraction expressible as a $first_denom fraction? if not: tweak! if (!FltEquals((int) $factor, $factor, $frac_prec)) { // ensure the /value/ of the fraction, which gets its denominator patched, stays close to what it was: $ret->num = (int) ($factor * $ret->num + 0.5); $ret->denom = $first_denom; } break; } } if ($prime_max > 0 && !CheckPrimeMax($ret->denom, $prime_max, $frac_prec)) { if (DebugReporting::$dbg >= 1) { printf("<p>boom at checkprimemax (%s/%s @ %s)\n", $ret->num, $ret->denom, $prime_max); } throw new Exception(sprintf("value not acceptable: fraction denominator %s is contains primes beyond the maximum allowed %s", $ret->denom, $prime_max)); } if ($redux) { // ensure the fraction is reducible! $gcd = CalcGCD($ret->num, $ret->denom); if ($gcd <= 1) { $m = 1; if (!$first_denom || $idx == 0) { $m_range = (int) (1.0 / ($frac_prec * $ret->denom)) - 1; $m = 2 + (int) ($m_range * $rnd->frand()); $first_denom = $m * $ret->denom; } if ($m <= 1) { if (DebugReporting::$dbg >= 1) { printf("<p>boom at GCD (%s/%s @ %s / first=%s / idx = %s)\n", $ret->num, $ret->denom, $m, $first_denom, $idx); } throw new Exception(sprintf("value not acceptable: fraction %s/%s is not expandible nor reducible", $ret->num, $ret->denom)); } } } return $ret->Val(); }