pdftex.web 中的 round_xn_over_d 函数如何工作?

pdftex.web 中的 round_xn_over_d 函数如何工作?

该函数round_xn_over_d以缩放后的点值为参数,返回缩放后的点值,其含义如下:

假设我们有\mydimen=10truept。众所周知,如果\magnification使用,则“真实”尺寸不会被放大(参见第 60 页电子书)。round_xn_over_d在 TeX 的内部表示中使用会得到与未使用时\mydimen相同的值。\mydimen\magnification

这是函数体的简化版本(x-参数-应该是正数)round_xn_over_d(写成C语言):

int n = mag;
int d = 1000;
unsigned int t, u, v;
t = (x % 0100000) * n;
u = (x / 0100000) * n + (t / 0100000);
v = (u % d) * 0100000 + (t % 0100000);
/* it is supposed that u/d < 0100000 */
u = 0100000 * (u/d) + (v/d);
v = v % d;
if (2*v >= d)
  incr(u);
return u;

它是如何工作的?

答案1

(冗长的回答,一边打字一边思考。稍后会尝试写一个较短的版本。)

0100000表示八进制100000,即 8 5 = 2 15 = 32768。因此,让我们看一下问题中的代码片段,用0100000替换,32768因为我们今天对八进制都不太熟悉。我们可以省略n和的具体值d:它们并不重要,除非我们假设0 <= n < 327680 <= d

unsigned int t, u, v;
t = (x % 32768) * n;
u = (x / 32768) * n + (t / 32768);
v = (u % d) * 32768 + (t % 32768);
/* it is supposed that u/d < 32768 */
u = 32768 * (u/d) + (v/d);
v = v % d;
if (2*v >= d)
  incr(u);
return u;

我们将整数写xa*32768 + b其中a = x/32768b = x % 32768。然后代码片段变成:

unsigned int t, u, v;
t = b * n;
u = a * n + (t / 32768);
v = (u % d) * 32768 + (t % 32768);
/* it is supposed that u/d < 32768 */
u = 32768 * (u/d) + (v/d);
v = v % d;
if (2*v >= d)
  incr(u);
return u;

为了进一步理解这一点,请考虑以下几点:

        32768s place    units place
x            a              b
x*n         a*n            b*n      (multiply both components by n)
x*n         a*n             t       (definition of t)
x*n     a*n + t/32768     t%32768   (normalize, i.e. "carry" to the 32768s place)
x*n          u            t%32768   (definition of u)

到目前为止,我们已经乘以了xn接下来我们要除以d。我们可以像通常做长除法那样做:

        32768s place    units place
x*n          u            t%32768            (from earlier)
→ divide the first component by d, and shift the remainder:
            u/d     (u%d)*32768 + (t%32768)
            u/d              v               (definition of v)
→ also divide the unit's component by d:
            u/d             v/d,   with remainder v%d

将此结果读为数字可得出商。为了避免因重复使用变量而产生的混淆,让我们调整代码行,将商和余数称为uuand ,vv而不是uand v

/* it is supposed that u/d < 32768 */
uu = 32768 * (u/d) + (v/d);
vv = v % d;

最后是四舍五入:uu上面是商(除法的整数部分),但是如果余数(vv)恰好大于的一半d,那么就意味着我们应该向上舍入:

if (2*vv >= d)
  incr(uu);

我们现在可以返回结果:

return uu;

我们从来不用使用x实际上恰好是缩放值的事实;即使将x视为整数,该函数也能正常工作。此代码的目的只是计算x * n / d正确舍入的值,而不会导致任何结果超过 32768 × 32768 = 2 30


换句话说:假设我们给出了三个整数xnd,它们都保证小于 2 30(并且n小于 32768),并且我们想要计算(半整数向上舍入)的值x * n / d,而不超过值2^30。这就是我们要做的。

相关内容