【问题标题】:Want to scale int to int with integer math想用整数数学将 int 缩放为 int
【发布时间】:2014-01-28 17:37:38
【问题描述】:

我使用的是 SDCC 2.8.0,因此内存和代码大小非常有限。假设我有一个介于 0 和 127 之间的输入值,我想将其缩放到 20 - 100。通常我会这样做:

int scale(int input, int min, int max)
{
 // assuming max is always greater than min
 float range = (float)max - (float)min;
 int output = min + int((range / 127.f) * (float)input);
 return output;
}

通过调用 scale(64, 20, 100); 我得到 60,正好是 20 到 100 之间的一半。

如何在不使用浮点数的情况下做到这一点?有什么移位魔法吗?

【问题讨论】:

  • 你的整数有多大?您期望max - min 的最大值是多少?
  • 如果可能,请尝试使用更新的 SDCC。最新版本是 3.3.0 和 3.4.0。即将到来。自 2.8.0 以来,SDCC 有了很大改进。

标签: floating-point integer scaling sdcc


【解决方案1】:

如果(max-min)<(INT_MAX/127),那么你可以在除以/127之前天真地乘以(max-min)*input
否则,您必须分解操作以避免溢出和未定义的行为......

在后一种情况下,一种天真的可能性是将两个乘数除以 127。

A=Q1*127+R1
B=Q2*127+R2
A*B = (Q1*Q2*127 + Q1*R2 + Q2*R1) * 127 + R1*R2
(A*B)/127 = Q1*Q2*127 + Q1*R2 + Q2*R1 + (R1*R2/127)

或在 C 中:

unsigned int range=max-min;
unsigned int output = min
    + (range/127)*(input/127)*127
    + (range/127)*(input%127)
    + (range%127)*(input/127)
    + (range%127)*(input%127) / 127;

很确定有更有效的位移公式>>8,编译器可能已经做得很好,但可能不太好,我们可能会更好地帮助他:

A=Q1*128+R1
B= 0*128+R2 (because B<=127)
A*B = (Q1*R2) * (127+1) + R1*R2
(A*B)/127 = Q1*R2 + (Q1*R2 + R1*R2)/127

在 C 中:
编辑
咳咳,我的意图是除以 128,即 >>7,我错误地写了 >>8 相同的余数,应该是 &0x7F 而不是 &0xFF
最好不要那么晦涩,只写 /128 和 %128 因为我们现在可以相信编译器会将这些操作转换为简单的位操作...

unsigned int range=max-min;
unsigned int high=(range / 128)*input;
unsigned int low =(range % 128)*input;
unsigned int output = min + high + (high+low)/127;

EDIT2
为了更好地平衡分布,我们可能会像这样应用某种舍入而不是截断:

unsigned int output = min + high + (high+low+63)/127;

【讨论】:

  • 好吧,这行得通!我采取了不同的路线,但这产生的代码比我的少。谢谢!
  • 直接映射到指定的端点(如(x-OldLow)*(NewHigh-NewLow)/(OldHigh-OldLow)+NewLow)是否是正确的做法并不总是很清楚。通常,这些值表示某种分类,在这种情况下,实际跨度不是High-Low,而是High-Low+BinWidth。为此调整地图可能会提供更好的质量转换。
  • @EricPostpischil 是的,这是正确的,例如当 range = 63 时,将有一个输入导致最大值,三个导致最小值。这符合原始功能,但我们可以做得更好......哦,但你说的是当 range > 127 时两个 bin 之间的分隔......
【解决方案2】:

我知道这是一个旧线程,但我只是想分享一些技巧,您可以使用它来更有效地使用浮点数进行缩放,前提是缩放常数是固定的并且事先已知。编译器通常在使用整数文字进行除法时使用这些技巧,以避免通常昂贵的 div 指令(在许多架构上可能需要数十个周期)。

显然,除非您真的需要从每个缩放操作中减少这几个周期,否则这就是过早优化的定义。

无论如何,我们的想法是将浮点因子更改为分母为 2 次方的近似值,这样您就可以用乘法(通常为 1 个周期)和右移运算(再次通常为与架构字长匹配的整数运算需要 1 个周期)。

在您的情况下,您的目标是用右移替换 1/127 部分,即具有 2 次幂的除法。由于您需要使用80/127(大约为0.62992)进行缩放并且输入适合7位,您可以选择161/256之类的东西(我假设您有一个16位控制器,所以我只是乘以@ 987654327@ 和 256 因为您的输入值都适合单词的低字节)。

所以函数就变成了:

// scale 0..127 into 20..100
uint8_t scale(uint8_t input)
{
    uint16_t low = input * 161;   // <- this will move the result into the high 8 bits
    low += 1 << 7;                // <- adding a "half bit" before shifting (like +0.5)
    low >>= 8;                    // <- cheap division by 256
    low += 20;                    // <- and finally, add offset

    return (uint8_t)(low);
}

在 32 位微控制器上,您可以选择更大的因子以获得更好的近似值。 cpu/编译器使用本机字大小通常更快,因为它不需要截断或扩展寄存器值来获得更小的整数大小。

由于127 需要 7 位,因此您可以选择 24 位分母,并且仍然确保相乘后的值适合 32 位字,即:

// 0.62992 == 10568325 / ‭16777216‬ == 10568325 / (1<<24)
uint8_t scale_32(uint8_t input)
{
    uint32_t low = input * 10568325;
    low += 1 << 23;
    low >>= 24;
    low += 20;
    return (uint8_t)(low);
}

您可以使用godbolt online compiler 来比较这些函数在不同编译器/架构中的程序集。

【讨论】:

    猜你喜欢
    • 2020-10-10
    • 2011-03-16
    • 2017-08-21
    • 2016-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多