tags:

views:

633

answers:

4

This code:

 SetRoundMode(rmUp);
 Memo1.Lines.Add(CurrToStr(SimpleRoundTo(10)));

Results in 10,0001.

I simply don't get it.

I thought that rmUp would do something like, round 10,0001 to 11, but never 10 to 10,0001.

Can anyone explain why this happens?

Thanks.

+11  A: 

SimpleRoundTo works like this:

  1. Divide the input value by 10-x, where x is the number of decimal places to preserve in the result.
  2. Add 0.5 to that product.
  3. Truncate the sum.
  4. Multiply by 10-x.

The result is a floating-point value. As with most floating-point values, the result will not be exact, even though in your case you start with an exact value. The number of decimal places specified for SimpleRoundTo is negative, so the divisor in step 1, for your example input, will ideally be 0.01. But that can't be represented exactly as a floating-point number, so when 10 / 0.01 is calculated in step 1, the result is not exactly 1000. The result in step 3 will be exactly 1000, though, so the inexactness of the division isn't important. The inexactness of the multiplication in step 4 is, though. That product won't be exact. It will be slightly higher than 10.

So SimpleRoundTo returns a slightly higher value, and since you've specified that rounding should go up, the conversion of the Extended result of SimpleRoundTo to the Currency input of CurrToStr results in exactly 10.0001.

Currency values are exact; they represent a fixed-point value, an integer scaled by four decimal places.

Rob Kennedy
+1 Good find. I was quite fascinated by this seemingly odd behaviour, but couldn't find the reason.
Smasher
Nice, but I guess I will need another question asking how to solve my problem then..
Fabio Gomes
+2  A: 

The return values from calculation SimpleToRound is also a Double and they can never be trusted on rounding. Truncate the value before converting it should do the work!

Memo1.Lines.Add(CurrToStr(Trunc(SimpleRoundTo(10))));

Tool
That workaround only works when the input is already trivially rounded to the desired precision. And your answer doesn't explain why such a workaround would be necessary anyway.
Rob Kennedy
+1  A: 

i'd use the Round( ) function if banker's rounding is ok. it returns an integer.

if you don't like banker's rounding you can use this:

// use this to not get "banker's rounding"
function HumanRound(X: Extended): integer;
// Rounds a number "normally": if the fractional
// part is >= 0.5 the number is rounded up (see RoundUp)
// Otherwise, if the fractional part is < 0.5, the
// number is rounded down
//   RoundN(3.5) = 4     RoundN(-3.5) = -4
//   RoundN(3.1) = 3     RoundN(-3.1) = -3
begin
  // Trunc() does nothing except conv to integer.  needed because return type of Int() is Extended
  Result := Trunc(Int(X) + Int(Frac(X) * 2));
end;

my posting here is somewhat off-topic but still informative.

i looked into this at length since i needed to not be using banker's rounding. here are my findings. so far as i can see, this still doesn't get rid of banker's rounding

Value    Meaning
rmNearest    Rounds to the closest value. // default, bankers rounding
rmDown   Rounds toward negative infinity.
rmUp     Rounds toward positive infinity.
rmTruncate   Truncates the value, rounding positive numbers down and negative numbers up.

rmNearest   // default
0.500   0
1.500   2
2.450   2
2.500   2
2.550   3
3.450   3
3.500   4
3.550   4

rmDown
0.500   0
1.500   1
2.450   2
2.500   2
2.550   2
3.450   3
3.500   3
3.550   3

rmUp
0.500   1
1.500   2
2.450   3
2.500   3
2.550   3
3.450   4
3.500   4
3.550   4

rmTrunc
0.500   0
1.500   1
2.450   2
2.500   2
2.550   2
3.450   3
3.500   3
3.550   3


uses
  math, sysutils, clipbrd;
var
  s:string;

procedure trythis(sMode:string);

  procedure tryone(d:double);
  begin
    s:=s+Format('%1.3f   %d%s',[d,Round(d),#13+#10]);
  end;

begin
  s:=s+#13#10+sMode+#13#10;

  tryone(0.50);
  tryone(1.50);
  tryone(2.45);
  tryone(2.50);
  tryone(2.55);
  tryone(3.45);
  tryone(3.50);
  tryone(3.55);
end;

begin
  s:=inttostr(integer(GetRoundMode));
  SetRoundMode(rmNearest);
  trythis('nearest');
  SetRoundMode(rmDown);
  trythis('down');
  SetRoundMode(rmUp);
  trythis('up');
  SetRoundMode(rmTruncate);
  trythis('trunc');
  clipboard.astext:=s;
end.
X-Ray
+1  A: 

The Ceil() : Integer function should give you the answer you want for values > 0. If < 0 you may need to use floor() instead, depending on desired behaviour.

Gerry