【问题标题】:Return value not as expected. AVR C calculation返回值与预期不符。 AVR C 计算
【发布时间】:2019-11-12 23:30:28
【问题描述】:

我正在尝试使用 ATmega328P MCU 执行以下计算。

?????????????????????????????????? = 1000 · ????????????0 + 2000 · ????????????1 +⋯ + 8000 · ????????????7 /????????????0+????????????1+⋯+????????????7

在主程序中(如图所示):

int main(void)
{
    //variables
    uint16_t raw_values[8];
    uint16_t position = 0;
    uint16_t positions[8];
    char raw[] = " raw";
    char space[] = ", ";
    char channelString[] = "Channel#: ";
    char positionString[] = "Position: ";

    //initialize ADC (Analog)
    initADC();

    //initialize UART
    initUART(BAUD, DOUBLE_SPEED);

    //give time for ADC to perform & finish 1st conversion
    //8us x 25 = 200us
    delay_us(200);

    while(1)
    {
        //get the raw values from the ADC for each channel
        for(uint8_t channel = 0; channel < 8; channel++)
        {
            raw_values[channel] = analog(channel);

            //invert the raw value
            raw_values[channel] = DIVISOR - raw_values[channel];
        }

        for(uint8_t channel = 0; channel < 8; channel++)
        {
            //print the channel#
            transmitString(channelString);
            printDec16bit(channel);
            transmitString(space);

            //print the raw value from the ADC conversion
            printDec16bit(raw_values[channel]);
            transmitString(raw);
            transmitString(space);

            //calculate the position value at each sensor
            transmitString(positionString);
            positions[channel] = (uint16_t)((POSITION_REF/DIVISOR) * raw_values[channel]);
            printDec16bit(positions[channel]);
            printCR();
        }

        printCR();

        //calculate and display 'position'
        position = calculatePosition(positions);
        printDec16bit(position);
        printCR();
        printCR();


        //add a delay
        delay_ms(2000);
    }
}

我正在调用下面的函数,但我得到的返回值是错误的。

uint16_t calculatePosition(uint16_t* channel_positions)
{
    uint32_t intermediates[8];
    uint32_t temp_sum = 0;
    uint16_t divisor = 0;
    uint16_t value = 0;

    for(uint8_t i = 0; i < 8; i++)
    {
        intermediates[i] = channel_positions[i] * ((i + 1) * 1000);
    }

    for(uint8_t j = 0; j < 8; j++)
    {
        temp_sum = temp_sum + intermediates[j];
    }

    for(uint8_t k = 0; k < 8; k++)
    {
        divisor = divisor + channel_positions[k];
    }

    value = temp_sum/divisor;

    return value;
}

或者,我什至尝试过这段代码,得到的结果不是我所期望的。

uint16_t calculatePosition(uint16_t* channel_positions)
{
    uint16_t position;
    position = ((1000 * channel_positions[0]) + 
                (2000 * channel_positions[1]) + 
                (3000 * channel_positions[2]) + 
                (4000 * channel_positions[3]) + 
                (5000 * channel_positions[4]) + 
                (6000 * channel_positions[5]) + 
                (7000 * channel_positions[6]) + 
                (8000 * channel_positions[7])) /
                (channel_positions[0] + 
                 channel_positions[1] + 
                 channel_positions[2] + 
                 channel_positions[3] + 
                 channel_positions[4] + 
                 channel_positions[5] + 
                 channel_positions[6] + 
                 channel_positions[7]);
    return position;
}

我做错了什么?对于诸如 {15, 12, 5, 16, 11, 35, 964, 76} 之类的值数组,我希望得到 6504 的结果,但我得到的是 200 的值(或其他一些奇怪的值)。

【问题讨论】:

  • 请创建一个minimal reproducible example,并用一个最小的主函数来说明问题所在。显示实际和预期的输出。另外,我认为你应该更好地解释你的公式。
  • 与我的另一个问题中显示的程序相同,但我缩小了问题的范围。我将删除我的另一个问题,并编辑这个以显示上下文。
  • 您如何将964 * 7000 表示为uint16_t?提示:你的计算溢出了。
  • 我已经编辑了我的问题,并消除了我进行计算的替代方法。
  • A minimal reproducible example 并不表示您正在使用的主要功能。这意味着一个minimal main 函数足以演示问题,这意味着我们应该能够只复制问题中的代码并编译它并得到与你相同的问题。

标签: c microcontroller avr


【解决方案1】:

查看您的输入数组:{15, 12, 5, 16, 11, 35, 964, 76}

具体来说,查看 964 的元素。该元素乘以 7000 是 6748000,大于 uint16_t 可以处理的值。

有多种解决方案。其中之一正在更改为uint32_t。如果这不是一个选项,您可以提取 1000 的因子,如下所示:

position = 1000 *(
            ((1 * channel_positions[0]) + 
            (2 * channel_positions[1]) + 
            (3 * channel_positions[2]) + 
            (4 * channel_positions[3]) + 
            (5 * channel_positions[4]) + 
            (6 * channel_positions[5]) + 
            (7 * channel_positions[6]) + 
            (8 * channel_positions[7])) /
            (channel_positions[0] + 
             channel_positions[1] + 
             channel_positions[2] + 
             channel_positions[3] + 
             channel_positions[4] + 
             channel_positions[5] + 
             channel_positions[6] + 
             channel_positions[7]));

请注意,这不会消除问题,但可能会减少问题,以便合理输入不会出现问题。

对循环版本采用相同的想法,我们得到:

uint16_t calculatePosition(uint16_t* channel_positions)
{
    uint16_t temp_sum = 0;
    uint16_t divisor = 0;

    for(uint8_t i = 0; i < 8; i++) {
        temp_sum  += (channel_positions[i] * (i+1));
        divisor += channel_positions[i];
    }

    return 1000*(temp_sum/divisor);
}

请注意,由于整数除法四舍五入,您将在此过程中损失一些准确性。由于您在指定宽度时非常小心,我假设您不愿意更改输入数组的类型。此代码应该以最少的额外内存使用量为您提供最大的准确性。但是,如果您经常在 16 位机器上运行此功能,则会对性能产生相当大的影响。

uint16_t calculatePosition(uint16_t* channel_positions)
{
    // Use 32 bit for these
    uint32_t temp_sum = 0;
    uint32_t divisor = 0;

    for(uint8_t i = 0; i < 8; i++) {
        // Copy the value to a 32 bit number
        uint32_t temp_pos = channel_positions[i];
        temp_sum  += temp_pos * (i+1);
        divisor += temp_pos;
    }

    // Moved parenthesis for better accuracy
    return (1000*temp_sum) / divisor;
}

如果结果可以适合uint16_t,则此版本失败的可能性绝对为零,因为1000*temp_sum 的最大可能值是2,359,260,000,它可以容纳的最大值是4,294,967,295

关于 MRE 的旁注(最小的、可重现的示例)

MRE:s 在这里描述:https://stackoverflow.com/help/minimal-reproducible-example

在这个例子中,在问题中发布的一个很好的主要功能是:

#include <stdio.h>

int main() 
{ 
    uint16_t positions[] = {15, 12, 5, 16, 11, 35, 964, 76}; 
    uint16_t pos = calculatePosition(positions); 
    printf("%d\n", pos); 
}

足以证明您遇到的问题,仅此而已。

【讨论】:

    【解决方案2】:

    如前所述,问题在于整数溢出。

    在使用整数数学时,将乘数移到外面时要小心! (A * 1000) / B 不等于 (A / B) * 1000

    最简单的解决方案,将每个操作中的第一个操作数转换为更广泛的类型。其他的将被隐式转换。等式

        ...
        position = ((1000UL * channel_positions[0]) + 
                    (2000UL * channel_positions[1]) + 
                    (3000UL * channel_positions[2]) + 
                    (4000UL * channel_positions[3]) + 
                    (5000UL * channel_positions[4]) + 
                    (6000UL * channel_positions[5]) + 
                    (7000UL * channel_positions[6]) + 
                    (8000UL * channel_positions[7])) /
                    ((uint32_t)channel_positions[0] + 
                     channel_positions[1] + // no need to convert, it will be converted implicitly
                     channel_positions[2] + // since previous operand is wider
                     channel_positions[3] + 
                     channel_positions[4] + 
                     channel_positions[5] + 
                     channel_positions[6] + 
                     channel_positions[7]);
    

    【讨论】:

    • 在这个 uint16_t 类型的例子中,结果变量是“位置”吗?知道结果总是一个整数吗?
    • 是的,整数除以整数得到整数结果
    • 次要细节,但从技术上讲它不会溢出。只有有符号整数会溢出,其行为未定义。但这里是无符号整数。
    • C89 claims "computation involving unsigned operands can never overflow",这是 C 特定的语言;模算术已定义(环绕),但overflow 仍然如此,因为真正的总和不适合。它只是不能产生异常(在这种情况下仍然会溢出,除非posititon 的类型也被更改)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-27
    • 1970-01-01
    • 2012-01-25
    • 1970-01-01
    • 2013-03-22
    • 2012-02-04
    相关资源
    最近更新 更多