简体   繁体   中英

Algorithm for integer rounding of result after division of one integer by another whose values may be negative

I am working with C source code that has a function RptRound() used in various reports which takes two values, an amount and a second value such as a count, divides them and then rounds the result. The amount value being divided is usually a currency amount such as US dollars times 100 for an amount in cents.

My question is whether the current function being used will provide correct rounding if the value of both lAmount and sDivisor are negative resulting in a positive result of their division.

Looking into the subject of rounding it appears that it is not nearly so cut and dried as I originally thought. However it appears the intent here was to do a Round-Half-Up type of rounding. Or rather Round-Half-Away-From-Zero. However this function only does so if the value of the divisor, sDivisor is positive.

The current function is as follows:

DCURRENCY RptRound( DCURRENCY lAmount, SHORT sDivisor )
{
    if (sDivisor) {                      /* Not Divide 0 Case */
        D13DIGITS   Quot, Quot1;

        if ( lAmount < 0 ) {
            Quot1 = -5;
        } else {
            Quot1 = 5;
        }
        Quot = lAmount * 10;
        Quot /= (LONG)sDivisor;
        Quot += Quot1;
        Quot /= 10;
        return Quot;
    }
    return (0);
}

It looks like so long as sDivisor is always positive then this function will give the correct, rounded up result with a positive value of x.5 rounded up to (x + 1) as well as a negative value of -x.5 rounded down (more negative) to (x - 1).

However it appears to me that if both the lAmount and the sDivisor are both negative resulting in a positive result then the rounding would be incorrect in that the bias value, Quot1 would be negative when it should be positive. The result would be that x.5 would be rounded to x and not x + 1 if both are negative. Or even worse that x.2 would be rounded to x - 1 if both are negative.

So I thought to replace the above with the following.

DCURRENCY RptRound( DCURRENCY lAmount, SHORT sDivisor )
{
    D13DIGITS   Quot = 0;

    if (sDivisor) {                      /* Not Divide 0 Case */
        Quot = lAmount;
        Quot *= 10;                      // multiply by 10 with larger number of digits
        Quot /= sDivisor;
        // add the bias and divide now in case both lAmount and sDivisor are negative.
        // this will get us to the closest whole number value of the result.
        if ( Quot < 0 ) {
            Quot += -5;      // add half of -10 to make more negative
        } else {
            Quot += 5;       // add half of +10 to make more positive
        }
        Quot /= 10;          // divide by 10 to get to nearest whole number of result.
    }

    return Quot;
}

With this version if sDivisor is negative and lAmount is positive, the result is rounded away from zero (result is negative and rounded more negative) as would the result if sDivisor is negative and lAmount is negative (result is positive and rounded more positive). If sDivisor is positive and lAmount is positive the result is rounded away from zero (result is positive and rounded more positive) and if sDivisor is positive and lAmount is negative the result is rounded away from zero (result is negative and rounded more negative).

However my degree of certainty as to this change is considerably reduced after a bit of reading so I am looking for additional feedback.

Note: since DCURRENCY is currently a long this function generates a compiler warning while doing the conversion for the returned value. This will disappear once DCURRENCY becomes a long long which is same as D13DIGITS .

warning C4244: 'return' : conversion from 'D13DIGITS' to 'DCURRENCY', possible loss of data

A negative divisor here looks a bit odd so consider an signed one and unsigned one. The signed function simply negates both operands.

Instead of scaling by 10 and adding +/-5 and dividing by 10, add half the divisor.

DCURRENCY RptRoundU(DCURRENCY lAmount, unsigned Divisor) {
  if (Divisor > 0) {
    if  lAmount < 0) {
      lAmount -= Divisor/2;
    } else {
      lAmount += Divisor/2;
    }  
    lAmount /= (LONG) sDivisor;
    return lAmount;
  }
  return 0;
}

DCURRENCY RptRoundS(DCURRENCY lAmount, signed Divisor) {
  return Divisor < 0 ? RptRoundU(-lAmount, 0u-Divisor) : RptRoundU(lAmount, Divisor);
}

The idea is simply to add or subtract half the divisor value before dividing, as this will round away from zero.

To make the @chux's answer slightly shorter, you can subtract the value only if one of the values is negative, ie

DCURRENCY RptRound( DCURRENCY lAmount, SHORT sDivisor )
{
    if (!sDivisor)
        return 0; // or some invalid value

    if ((sDivisor < 0) ^ (lAmount < 0)) {
        lAmount -= (sDivisor / 2);
    } else {
        lAmount += (sDivisor / 2);
    }

    return lAmount / sDivisor;
}

Additionally, my preference would be to return a special invalid value for division by zero, something like (long)0x80000000 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM