Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Bug in Delphi SimpleRoundTo function?

2,146 views
Skip to first unread message

Trevor Toms

unread,
Aug 3, 2007, 9:56:00 AM8/3/07
to
SimpleRoundTo is supposed to round midvalues up to the level of specified
precision, but some specific values fail this.

E.g. SimpleRoundTo(79.625, -2) gives the result 79.62 rather than the
expected 79.63.

We've got round it by a convoluted process of conversion from floats to
strings and back to floats again, but is there an alternative function
which works?

Trevor

Serge Dosyukov (Dragon Soft)

unread,
Aug 3, 2007, 10:06:43 AM8/3/07
to
Accordinly to this (snippet from help file)

SimpleRoundTo(1234567, 3) 1234000
SimpleRoundTo(1.234, -2) 1.23
SimpleRoundTo(1.235, -2) 1.24
SimpleRoundTo(-1.235, -2) -1.23

it is a bug

PS. I am not sure about last row thought ;)

I would think that Wiki shows it right -
http://en.wikipedia.org/wiki/Rounding - one just have to decide which method
to use

Andrew Fiddian-Green

unread,
Aug 3, 2007, 10:17:13 AM8/3/07
to
Not 100% sure about this, but I think that -- in the case of numbers that
lie *exactly* half way between two choices -- the function uses "bankers
rounding" so as to eliminate the statistical bias you would otherwise get if
"half way" numbers were always (say) rounded down...

i.e. 79.625 would round to 79.62 but 79.635 would round to 79.64

Regards,
AndrewFG


willem van deursen

unread,
Aug 3, 2007, 10:36:23 AM8/3/07
to
There was a similar discussion in
borland.public.delphi.language.delphi.win32 about two weeks back (Google
for SimpleRoundTo and that group).

John Herbster pointed out the same problem, and he suggests to replace
it with his DecimalRounding (JH1). The module is available from
CodeCentral, http://cc.codegear.com/Item.aspx?id=21909.


--
Willem van Deursen, The Netherlands
wvandeursen_nospam@nospam_carthago.nl
replace _nospam@nospam_ for @ to get a valid email address
www.carthago.nl

Nick Hodges (CodeGear)

unread,
Aug 3, 2007, 11:52:24 AM8/3/07
to
Andrew Fiddian-Green wrote:

> i.e. 79.625 would round to 79.62 but 79.635 would round to 79.64

That is correct -- that is the IEEE spec, as I understand it.

--
Nick Hodges
Delphi Product Manager - CodeGear
http://blogs.codegear.com/nickhodges

Ralf Jansen

unread,
Aug 3, 2007, 12:08:14 PM8/3/07
to
>
> That is correct -- that is the IEEE spec, as I understand it.
>

Yes, when using Bankers rounding like RoundTo does.
But Trevor is using SimpleRoundTo and SimpleRoundTo is meant to do Arithmetic
rounding. So 79.62 is wrong.


--
Ralf Jansen

deepinvent Software GmbH - Viersen, Germany - http://www.deepinvent.com
Archiving E-mails with MailStore: http://www.mailstore.com


Ralf Jansen

unread,
Aug 3, 2007, 12:41:11 PM8/3/07
to
Trevor Toms schrieb:


If you need correct rounding you should use bcd types (like currency in delphi)
and not real floatingpoint values. If you can't change that try Willem's suggestion.

Some decimal values can not be represented with absolut precision as binary
values. 79.625 shouldn't be a problem (it can be represented as binary something
like 1001111.101) but either your 79.625 value was the result of another
floatingpoint operation that evaluates to something slightly lower than 79.625
(you wouldn't see the ~real~ value in delphi's watch window while debugging if
you didn't mark the results as floatingpoints there to show the results as
floatingpoints) or one of the intermediate results inside SimpleRoundTo can't be
represented correctly as binary and therefore evaluates to something slightly
lower than 79.625

Nick Hodges (CodeGear)

unread,
Aug 3, 2007, 1:05:37 PM8/3/07
to
Ralf Jansen wrote:

> But Trevor is using SimpleRoundTo and SimpleRoundTo is meant to do
> Arithmetic rounding. So 79.62 is wrong.

Ahh. Indeed --

Nick Hodges (CodeGear)

unread,
Aug 3, 2007, 1:29:48 PM8/3/07
to
Rod wrote:

> No Borland person was interested in fixing it up to now.

Rod --

It's in our internal system -- and has been "bandied around" a bit.
I've raised its visibility and it will get looked at afresh.

Rod

unread,
Aug 3, 2007, 1:18:16 PM8/3/07
to
Nick Hodges (CodeGear) wrote:
> Ralf Jansen wrote:
>
>> But Trevor is using SimpleRoundTo and SimpleRoundTo is meant to do
>> Arithmetic rounding. So 79.62 is wrong.
>
> Ahh. Indeed --
>
>

These errors exists since years.
http://qc.codegear.com/wc/qcmain.aspx?d=8070

Wayne Niddery [TeamB]

unread,
Aug 3, 2007, 1:51:54 PM8/3/07
to
Serge Dosyukov (Dragon Soft) wrote:
> Accordinly to this (snippet from help file)
>
> SimpleRoundTo(1234567, 3) 1234000
> SimpleRoundTo(1.234, -2) 1.23
> SimpleRoundTo(1.235, -2) 1.24
> SimpleRoundTo(-1.235, -2) -1.23
>
> it is a bug

No, it is fully documented "Bankers" rounding. Rounding is to the nearest
*even* value.

--
Wayne Niddery - Winwright, Inc (www.winwright.ca)
"The purpose of morality is to teach you, not to suffer and die, but to
enjoy yourself and live." - Ayn Rand


James Miller

unread,
Aug 3, 2007, 1:51:30 PM8/3/07
to
I am pretty sure that Delphi's Currency type calculations
also use bankers rounding. Is there a compiler switch
or something to make it use simple rounding?


James Miller

unread,
Aug 3, 2007, 3:28:03 PM8/3/07
to

"John Herbster" <herb-sci1_AT_sbcglobal.net> wrote in message news:46b37e47$1...@newsgroups.borland.com...
> A good programmer should know that...

Is there or is there not a compiler switch or some other option
to make calculations performed with Delphi Currency type values
use the common method of rounding, also known as symmetric
arithmetic rounding or round-half-up (symmetric implementation)
rounding instead of bankers' rounding, also known as Round-to-even
rounding, unbiased rounding, convergent rounding, or statistician's
rounding?


John Herbster

unread,
Aug 3, 2007, 3:17:45 PM8/3/07
to
"James Miller" wrote

A good programmer should know that there is no such
thing as "simple rounding" -- by that, I mean a
rounding rule that most programmers can agree on.
At least "bankers rounding" is a rule that most
programmers can agree on.

That aside, when ordinary programmers get caught in
the trap of using floating *binary* point numbers,
to represent (fixed or floating) decimal fraction
numbers and have to do any kind of rounding, then
they stumble all over themselves.

The problem, of course, is that general decimal
fractions cannot be exactly represented as
floating binary point numbers. This leads to
problems deciding what the floating binary point
number is really supposed to represent in decimal
fraction terms. My DecimalRounding package that
Willem mentioned solves this problem.

However, as someone ready to step into retirement
who has for many years tried both to educate the
ignorant and to push for incorporating actual
fixed and floating decimal fraction into Delphi,
I am really disappointed that I am still reading
such ill informed discussion of this problem.

Rgds, JohnH

--
Support the movement to add floating and fixed
decimal fraction numbers to Delphi.
http://qc.borland.com/wc/qcmain.aspx?d=28022

DecimalRounding (JH1) -- allows you to do about
nine kinds of decimal rounding with floating
binary point numbers.
http://cc.codegear.com/Item.aspx?id=21909

IEEE Number Analyzer (project source code) -- a
program that will convert a decimal fraction
number into the nearest possible floating binary
point number (i.e. single, double, or extended)
http://cc.codegear.com/item.aspx?id=23631

ExactFloatToStr -- a function for converting a
floating binary point number to its exact
decimal fraction values in a ASCII string.
http://cc.codegear.com/Item.aspx?id=19421

Ralf Jansen

unread,
Aug 3, 2007, 4:24:59 PM8/3/07
to
James Miller schrieb:

AFAIK Delphi has no build in rounding functions for the currency type.

For floatingpoint types SimpleRoundTo() does arithmetic rounding.
By default the RoundTo() method does bankers rounding but that can be changed
via the SetRoundMode() function.

Q Correll

unread,
Aug 3, 2007, 11:15:10 PM8/3/07
to
John,

| However, as someone ready to step into retirement
| who has for many years tried both to educate the
| ignorant and to push for incorporating actual
| fixed and floating decimal fraction into Delphi,
| I am really disappointed that I am still reading
| such ill informed discussion of this problem.

I retired seven years ago. I've been seeing these same discussions
since 1955. That's fifty-two years now. I'd be willing to bet my urn
that it will still be being bandied about in another fifty years. <g>

--
Q

08/03/2007 20:12:32

XanaNews Version 1.17.5.7 [Q's salutation mod]

John Herbster

unread,
Aug 4, 2007, 1:19:13 AM8/4/07
to

"Ralf Jansen" <ralf.jans...@deepinvent.com> wrote
> ... For floating point types SimpleRoundTo() does
> arithmetic rounding.

Ralf,

At least for D7, SimpleRoundTo was screwed up. See
open QualityCentral reports #8070 and #8143:
_RoundTo and SimpleRoundTo are very sick_
http://qc.codegear.com/wc/qcmain.aspx?d=8070
_Replace RoundTo and SimpleRoundTo_
http://qc.codegear.com/wc/qcmain.aspx?d=8143

I would not recommend RoundTo or SimpleRoundTo to
anyone.

What is "arithmetic rounding"?
The types that I am familiar with include:
(Abbr: 'HalfEven'; Dscr: 'to nearest or even (bankers)'),
(Abbr: 'HalfPos' ; Dscr: 'to nearest or toward positive'),
(Abbr: 'HalfNeg' ; Dscr: 'to nearest or toward negative'),
(Abbr: 'HalfDown'; Dscr: 'to nearest or toward zero'),
(Abbr: 'HalfUp' ; Dscr: 'to nearest or away from zero'),
(Abbr: 'RndNeg' ; Dscr: 'toward negative (floor)'),
(Abbr: 'RndPos' ; Dscr: 'toward positive (ceil )'),
(Abbr: 'RndDown' ; Dscr: 'toward zero (trunc)'),
(Abbr: 'RndUp' ; Dscr: 'away from zero') );
The above are from my module DecimalRounding_JH1.
And then there is "statistical rounding" which is not
included in the current DecimalRounding_JH1.

> By default, the RoundTo() method does bankers rounding


> but that can be changed via the SetRoundMode() function.

I would not say that RoundTo() *does* bankers rounding.
The D7 help says that RoundTo() *uses* bankers rounding,
which is technically true; but the results are not what
the innocent programmer would expect to see. Instead,
it gives this:
RoundTo(1.235,-2) ==> 1.24
RoundTo(1.245,-2) ==> 1.25

If you examine more closely the real numbers being passed
into and out of the RoundTo function, you will observe for
RoundingMode: rmNearest
RoundTo(1.235,-2) ==> 1.24
Exact Inp: + 1.23500 00000 00000 09769 96261 67013
77555 72795 86791 99218 75
Exact Out: + 1.23999 99999 99999 99111 82158 02998
74767 66109 46655 27343 75
RoundTo(1.245,-2) ==> 1.25
Exact Inp: + 1.24500 00000 00000 10658 14103 64015
02788 06686 40136 71875
Exact Out: + 1.25

Here is the code that I used for the test:
Uses Math, ExactFloatToStr_JH0;

procedure TForm1.Button1Click(Sender: TObject);
{sub}procedure TestRT(const AValue: Double; const ADigit:
TRoundToRange);
var AVStr, RVStr: string; RVal: double;
begin
RVal := RoundTo(AValue,ADigit);
Memo1.Lines.Add(Format(' RoundTo(%S,%D) ==> %S',
[FloatToStr(AValue),ADigit,FloatToStr(RVal)]));
AVStr := ExactFloatToStrEx(AValue);
Memo1.Lines.Add(Format(' ExactInp: %S',[AVStr]));
RVStr := ExactFloatToStrEx(RVal);
Memo1.Lines.Add(Format(' ExactOut: %S',[RVStr]))
end;
var ORM, RM: TFPURoundingMode;
const RoundingModeStrs: array [TFPURoundingMode] of string =
('rmNearest','rmDown','rmUp','rmTruncate');
begin
For RM := rmNearest to rmTruncate do begin
ORM := SetRoundMode(RM);
Memo1.Lines.Add('RoundingMode: '+RoundingModeStrs[RM]);
Try
TestRT(1.235, -2); // 1.24
TestRT(1.245, -2); // 1.24
Finally
SetRoundMode(ORM);
End;
end;
end;

I will post the source for the whole test if anyone is interested.


Rgds, JohnH

--
Support the movement to add floating and fixed
decimal fraction numbers to Delphi.
http://qc.borland.com/wc/qcmain.aspx?d=28022
DecimalRounding (JH1)

http://cc.codegear.com/Item.aspx?id=21909
IEEE Number Analyzer (project code)
http://cc.codegear.com/item.aspx?id=23631
ExactFloatToStr
http://cc.codegear.com/Item.aspx?id=19421

John Herbster

unread,
Aug 4, 2007, 1:39:28 AM8/4/07
to

"Wayne Niddery [TeamB]" <wnid...@chaffaci.on.ca> wrote
> > According to this (snippet from help file)

> > SimpleRoundTo(1234567, 3) 1234000
> > SimpleRoundTo(1.234, -2) 1.23
> > SimpleRoundTo(1.235, -2) 1.24
> > SimpleRoundTo(-1.235, -2) -1.23

> No, it is fully documented "Bankers" rounding. Rounding


> is to the nearest *even* value.

Wayne,

Then why does SimpleRoundTo give the following?
SimpleRoundTo(1.245,-2) ==> 1.25


Exact Inp: + 1.24500 00000 00000 10658 14103 64015 02788 06686
40136 71875
Exact Out: + 1.25

At least for D7, SimpleRoundTo was screwed up. See


open QualityCentral reports #8070 and #8143:
_RoundTo and SimpleRoundTo are very sick_
http://qc.codegear.com/wc/qcmain.aspx?d=8070
_Replace RoundTo and SimpleRoundTo_
http://qc.codegear.com/wc/qcmain.aspx?d=8143

Rgds, JohnH

John Herbster

unread,
Aug 4, 2007, 2:01:25 AM8/4/07
to

"Nick Hodges (CodeGear)" <nick....@codegear.com> wrote

> > i.e. 79.625 would round to 79.62 but 79.635 would round to 79.64

> That is correct -- that is the IEEE spec, as I understand it.

Nick,

So what about the fact that for D7,
SimpleRoundTo(79.605,-2) ==> 79.61
ExactInp: + 79.60500 00000 00003 97903 93202 56561 04087 82958
98437 5
ExactOut: + 79.60999 99999 99999 43156 58113 91919 85130 31005
85937 5

Rgds, JohnH


John Herbster

unread,
Aug 4, 2007, 2:00:14 AM8/4/07
to

"Ralf Jansen" <ralf.jans...@deepinvent.com> wrote

> But Trevor is using SimpleRoundTo and SimpleRoundTo is meant to do
Arithmetic
> rounding. So 79.62 is wrong.

Ralf,

According to
http://www.diycalculator.com/popup-m-round.shtml
"arithmetic rounding" is half-up toward positive for positive
numbers.

So how do you explain this fact:
SimpleRoundTo(79.615,-2) ==> 79.61
ExactInp: + 79.61499 99999 99994 88409 23025 27278 66172 79052
73437 5

John Herbster

unread,
Aug 4, 2007, 2:05:35 AM8/4/07
to

"Q Correll" <qcor...@pacNObell.net> wrote

> I retired seven years ago. I've been seeing these same
> discussions since 1955. That's fifty-two years now. I'd
> be willing to bet my urn that it will still be being bandied
> about in another fifty years. <g>

We are damn slow learners. We would rather argue over
some silly point that change the environment to eliminate
the problem. --JohnH

Ralf Jansen

unread,
Aug 4, 2007, 7:27:20 AM8/4/07
to
John Herbster schrieb:

> "Ralf Jansen" <ralf.jans...@deepinvent.com> wrote
>> But Trevor is using SimpleRoundTo and SimpleRoundTo is meant to do
> Arithmetic
>> rounding. So 79.62 is wrong.
>
> Ralf,
>
> According to
> http://www.diycalculator.com/popup-m-round.shtml
> "arithmetic rounding" is half-up toward positive for positive
> numbers.

If you ask me i would descibe Arithmetic rounding as 'to nearest or if
undecidable away from zero'. So correctly named SimpleRoundTo() should do
Symmetric Arithmetic Rounding. I think you called it HalfUp in your replacement
routines.

>
> So how do you explain this fact:
> SimpleRoundTo(79.615,-2) ==> 79.61
> ExactInp: + 79.61499 99999 99994 88409 23025 27278 66172 79052
> 73437 5
> ExactOut: + 79.60999 99999 99999 43156 58113 91919 85130 31005
> 85937 5
>
> Rgds, JohnH
>

I don't know what you want to hear from me, John? I'm pretty shure you know why
this is happening.

The problem isn't the Algorithm(if we had real decimal ALU's and proper
datatypes for that it would work) but the used datatype. It was a dumb idea from
Borland/Codegear to put rounding functions in math.pas without applying some
kind of delta to the input values to adjust the inherently missing precision of
binary interpreted decimal values. They should at least have mentioned the
implications in the help files.

Ralf Jansen

unread,
Aug 4, 2007, 7:41:19 AM8/4/07
to
John Herbster schrieb:

>
> I would not recommend RoundTo or SimpleRoundTo to
> anyone.
>

Neither do i.

> What is "arithmetic rounding"?
> The types that I am familiar with include:
> (Abbr: 'HalfEven'; Dscr: 'to nearest or even (bankers)'),
> (Abbr: 'HalfPos' ; Dscr: 'to nearest or toward positive'),
> (Abbr: 'HalfNeg' ; Dscr: 'to nearest or toward negative'),
> (Abbr: 'HalfDown'; Dscr: 'to nearest or toward zero'),
> (Abbr: 'HalfUp' ; Dscr: 'to nearest or away from zero'),
> (Abbr: 'RndNeg' ; Dscr: 'toward negative (floor)'),
> (Abbr: 'RndPos' ; Dscr: 'toward positive (ceil )'),
> (Abbr: 'RndDown' ; Dscr: 'toward zero (trunc)'),
> (Abbr: 'RndUp' ; Dscr: 'away from zero') );
> The above are from my module DecimalRounding_JH1.
> And then there is "statistical rounding" which is not
> included in the current DecimalRounding_JH1.
>

HalfUp

>> By default, the RoundTo() method does bankers rounding
>> but that can be changed via the SetRoundMode() function.
>
> I would not say that RoundTo() *does* bankers rounding.
> The D7 help says that RoundTo() *uses* bankers rounding,
> which is technically true; but the results are not what
> the innocent programmer would expect to see. Instead,
> it gives this:
> RoundTo(1.235,-2) ==> 1.24
> RoundTo(1.245,-2) ==> 1.25
>

Ok. RoundTo() *tries* to use bankers rounding.
But because of the missing precision in the given float value
it looks different (to the innocent).

John Herbster

unread,
Aug 4, 2007, 8:56:52 AM8/4/07
to

"Ralf Jansen" <ralf.jans...@deepinvent.com> wrote
> ... I don't know what you want to hear from me, ...
> The problem isn't the Algorithm (if we had real decimal
> ALU's and proper datatypes for that it would work) ...

Ralf, et. al, Thanks for suffering my outrage. I
think that to properly fix the problem would require a
lot of real work and I guess that the "bean counters",
as we used to call them, were not convinced by the
proponents of the fix of the worthwhileness of the
expenditure. Regards, JohnH

--
Support the movement to add floating and fixed
decimal fraction numbers to Delphi.
http://qc.borland.com/wc/qcmain.aspx?d=28022
DecimalRounding (JH1)
http://cc.codegear.com/Item.aspx?id=21909

IEEE Number Analyzer (project source code)

Q Correll

unread,
Aug 5, 2007, 10:56:30 AM8/5/07
to
John,

| I will post the source for the whole test if anyone is interested.

I am interested. I'm leaving in a couple of hours for a mini-vacation
so won't be able to see it for a week. If no one else is interested
you can e-mail me.

--
Q

08/05/2007 07:55:11

John Herbster

unread,
Aug 5, 2007, 2:31:35 PM8/5/07
to

"Q Correll" <qcor...@pacNObell.net> wrote

> | I will post the source for the whole test if anyone is
interested.

> I am interested.

I just posted it in b.p.attachments group under subject:
T_RoundTo_3 -- Test for bug in RoundTo functions

--JohnH

d r

unread,
Aug 5, 2007, 10:40:43 PM8/5/07
to
I followed the discussion in this thread and I must admit I'm blissfully
unaware/ignorant of the challenge posed by floating number
representation and rounding.


I was curious to look at the source code of SimpleRoundTo in math.pas
(D2007).


Here it is - original code:


function SimpleRoundTo(const AValue: Double; const ADigit: TRoundToRange
= -2): Double;
var
LFactor: Double;
begin
LFactor := IntPower(10, ADigit);
if AValue < 0 then
Result := Trunc((AValue / LFactor) - 0.5) * LFactor
else
Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
end;

I wanted to find where the problem occurs so copied it and modified it
to following code:


function SimpleRoundTo(const AValue: Double; const ADigit: TRoundToRange
= -2): Double;
var
LFactor: Double;
begin
LFactor := IntPower(10, ADigit);
if AValue < 0 then
Result := Trunc((AValue / LFactor) - 0.5) * LFactor
else
Begin
//Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
//******

Result := (AValue / LFactor) + 0.5;

Result := Trunc(Result);
Result := Result * LFactor;

//******
End;

end;

I just split single line expression into three lines, and surprisingly
error disappeared?! WTF???

I looked into CPU window there are differences but I'm not very familiar
with opcodes - analyzing what exactly happened would probably take me a
day or so nad I still might get it wrong. Anybody care to
comment/explain what and why happened here?


Dragan

Jolyon Smith

unread,
Aug 5, 2007, 11:03:49 PM8/5/07
to
In article <46b68a28$1...@newsgroups.borland.com>, d r says...

> I just split single line expression into three lines, and surprisingly
> error disappeared?! WTF???

It is almost certainly a change in underlying type resulting from the
breaking up of the expression that has resulted in the different result,
especially if (as seems to be the case) the original "problem" stems
from an internal representation issue in some floating point type.

i.e. one value that cannot be exactly represented in a double variable
may be representable in an extended variable or vice versa.

The point at which a piece of code incurs a change from one internal
representation to another will determine where/when/if representation
errors creep in. Changing the point(s) at which representation changes
can change the result of an otherwise equivalent calculation.


In this case, in the original code, the expression (AValue / LFactor) +
0.5 yields an Extended result (this is defined by the language - the /
operator yields Extended).

In your re-written version that Extended result is assigned to a Double,
invoking a representation change.

That Double value is then converted BACK to an Extended value for
passing to the Trunc function.


In the original version this flip-flopping between Extended and Double
does not take place. The expression is evaluated and passed to Trunc()
in Extended representation and the result only converted to Double once
fully evaluated.

That probably explains the difference.

--
JS
TWorld.Create.Free;

d r

unread,
Aug 5, 2007, 11:25:38 PM8/5/07
to
Hi Jolyon,


seems that you're right introducing tmp variable of Extended type realy
shows that problem is with TRUNC function, modified test code looks
like this :


//test call: SimpleRoundTo(79.625, -2);

function SimpleRoundTo(const AValue: Double; const ADigit: TRoundToRange
= -2): Double;
var

TmpExt : Extended;


LFactor: Double;
begin
LFactor := IntPower(10, ADigit);
if AValue < 0 then
Result := Trunc((AValue / LFactor) - 0.5) * LFactor
else
Begin
//Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
//******

TmpExt := (AValue / LFactor) + 0.5;

TmpExt := Trunc(TmpExt); //returns 7962 instead of 7963


Result := TmpExt * LFactor;

//******
End;


To Nick Hodges: context sensitive for Trunc function (amongs others) is
still missing, broken help is my biggest gripe with D2007. I still keep
D6 installed as a help reference when D2007 help fails.


Anders Isaksson

unread,
Aug 6, 2007, 3:02:42 AM8/6/07
to
Wayne Niddery [TeamB] wrote:

> No, it is fully documented "Bankers" rounding. Rounding is to the

> nearest even value.

But that's not what the D2006 Help says about SimpleRoundTo:

"SimpleRoundTo uses asymmetric arithmetic rounding to determine how to
round values that are exactly midway between the two values that have
the desired number of significant digits. This method always rounds to
the larger value."

But then the examples in the Help are meaningless and don't support
neither the above claim nor Banker's Rounding.

Looking at the source, it definitely is trying to do something else
than Banker's Rounding. It also seems the part "This method always
rounds to the larger value." actually should read "This method always
rounds to the larger *absolute* value."

All that aside from the fact that it doesn't work...

--
Anders Isaksson, Sweden
BlockCAD: http://web.telia.com/~u16122508/proglego.htm
Gallery: http://web.telia.com/~u16122508/gallery/index.htm

Anders Isaksson

unread,
Aug 6, 2007, 3:26:18 AM8/6/07
to
Jolyon Smith wrote:

> i.e. one value that cannot be exactly represented in a double
> variable may be representable in an extended variable or vice versa.

Or more probable: The value, as represented in a double might be
*slightly* larger than the original value, while represented as an
extended it might be *slightly* smaller (or vice versa).

John Herbster

unread,
Aug 6, 2007, 8:30:54 AM8/6/07
to
> > No, it is fully documented "Bankers" rounding.
> > Rounding is to the nearest even value.

"Anders Isaksson" <i.rather@not> wrote


> But that's not what the D2006 Help says about
> SimpleRoundTo:
> "SimpleRoundTo uses asymmetric arithmetic rounding
> to determine how to round values that are exactly
> midway between the two values that have the desired
> number of significant digits. This method always
> rounds to the larger value."

"To the larger value" to me would imply that
-1.5 would go to -2, and +1.5 would go to +2.
To me, that would be symmetric, not asymmetric.
But I have some doubt. Am I wrong? The Help
writers could have made written this more clearly.

Given, our present IQs there is no such thing as
"simple rounding".

Regards, JohnH

Trevor Toms

unread,
Aug 6, 2007, 10:47:00 AM8/6/07
to
In article <46b3660c$1...@newsgroups.borland.com>, nick....@codegear.com
(Nick Hodges (CodeGear)) wrote:

> Rod --
>
> It's in our internal system -- and has been "bandied around" a bit.
> I've raised its visibility and it will get looked at afresh.
>
> --

Whilst I wouldn't recommend my solution as a CodeGear fix, you may
consider how I solved the problem for the time being. I've written a new
function RoundMoney, which does what I wanted SimpleRoundTo to do:

function RoundMoney(money : double) : double ;
var
precision : string ;
tempStr : string ;
begin
precision := '%.' + IntToStr(CurrencyDecimals) + 'f' ;
tempStr := Format(precision, [Abs(money + (Sign(money) * 0.00000001))])
;
result := Sign(money) * StrToFloatDef(tempStr, 0) ;
end ;

You might also note that this function ALWAYS rounds away from zero, so
-1.625 rounds to -1.63, not -1.62.

This is done so that (for instance) credit notes generate an equal and
opposite financial value from the original invoice. Otherwise you'd end
up with a penny/cent/yourcurrency100th discrepancy.

Trevor


Trevor Toms

unread,
Aug 6, 2007, 11:49:00 AM8/6/07
to
In article <46b37e47$1...@newsgroups.borland.com>,
herb-sci1_AT_sbcglobal.net (John Herbster) wrote:

> A good programmer should know that there is no such
> thing as "simple rounding"

I disagree.

Ask any mathematician and you'll get the same answer: 0.5 rounds up.
Check it on your calculator too.

Mathematically, this is:
0 <= x < 0.5 => 0 (or [0, 0.5[ => 0)
0.5 <= x < 1.0 => 1 (or [0.5, 1.0[ => 1)

The only area of debate is whether negative values round towards or away
from zero.

Banker's Rounding is a convenience to avoid either party (buyer or seller)
gaining a financial advantage from fractional values.

> That aside, when ordinary programmers get caught in
> the trap of using floating *binary* point numbers,
> to represent (fixed or floating) decimal fraction
> numbers and have to do any kind of rounding, then
> they stumble all over themselves.

This isn't a trap that can always be avoided. You may be given a database
populated with floating point values that are not 100% accurate. Any
reasonable programmer should be able to work around this without recourse
to scaled integer values or BCD representation, armed only with the
knowledge of the required precision and values that conform to that
precision.

Of course, using scaled integers or BCD does make it easier, but don't
yourself fall into the trap of believing that this is the only solution.

> I am really disappointed that I am still reading
> such ill informed discussion of this problem

Not ill-informed at all - and rather superior of you to suggest so. My
original post (if you refer back) was noting a failure of SimpleRoundTo()
to conform to its specification. The fact that it is being used to handle
currency is neither a mistake nor necessarily bad practice. Your library
routines may well make life easier for some, and indeed maybe for me too.
However, there are times and circumstances which do make and will
continue to make situations such as mine unavoidable. Read Kurt Godel's
hypothesis for a better understanding of why this is so.

Trevor

John Herbster

unread,
Aug 6, 2007, 12:12:08 PM8/6/07
to

"Trevor Toms" <sm...@cix.compulink.co.uk> wrote

> Whilst I wouldn't recommend my solution as a CodeGear fix,
> you may consider how I solved the problem for the time being.

> function RoundMoney(money : double) : double ;


> var precision : string ; tempStr : string ;
> begin
> precision := '%.' + IntToStr(CurrencyDecimals) + 'f' ;
> tempStr := Format(precision,
> [Abs(money + (Sign(money) * 0.00000001))]) ;
> result := Sign(money) * StrToFloatDef(tempStr, 0) ;
> end ;

Trevor, You may wish to note how I modified your code to
avoid the conversion to string and back. You may also
wish to note how the modification stretches the range
of correct operation. You can find the source for a project
with both the modification and the tests in the attachments
group (borland.public.attachments) with the subject
T_RoundTo_3. Regards, JohnH

John Herbster

unread,
Aug 6, 2007, 12:28:08 PM8/6/07
to

> Ask any mathematician and you'll get the same answer:
> 0.5 rounds up.

Hogwash! Furthermore, that statement is not even
clear as to whether "rounds up" means away from
zero or toward plus infinity. --JohnH

Rudy Velthuis [TeamB]

unread,
Aug 6, 2007, 12:59:30 PM8/6/07
to
Bob Dawson wrote:

> "Rudy Velthuis [TeamB]" wrote
> >
> > For most people it is pretty clear: for positive numbers, rounding
> > up means towards plus infinity AND away from zero.
>
> John was pointing out that for negative numbers there's no such
> universal agreement about which way is 'up'. Surely a round function
> should work with negatives.

I am aware of that. <g>
--
Rudy Velthuis [TeamB] http://rvelthuis.de

"If it wasn't for muscle spasms, I wouldn't get any exercise at
all."

Rudy Velthuis [TeamB]

unread,
Aug 6, 2007, 12:50:14 PM8/6/07
to
John Herbster wrote:

For most people it is pretty clear: for positive numbers, rounding up


means towards plus infinity AND away from zero.

--

Rudy Velthuis [TeamB] http://rvelthuis.de

"A child of five could understand this. Fetch me a child of five."
-- Groucho Marx

Bob Dawson

unread,
Aug 6, 2007, 12:57:10 PM8/6/07
to
"Rudy Velthuis [TeamB]" wrote

>
> For most people it is pretty clear: for positive numbers, rounding
> up means towards plus infinity AND away from zero.

John was pointing out that for negative numbers there's no such universal


agreement about which way is 'up'. Surely a round function should work with
negatives.

re : "Check it on your calculator too."
I'm still trying to find the 'Round' button on my calculator ... <g>

bobD


Ralf Jansen

unread,
Aug 6, 2007, 1:42:07 PM8/6/07
to
John Herbster schrieb:

>
> "To the larger value" to me would imply that
> -1.5 would go to -2, and +1.5 would go to +2.
> To me, that would be symmetric, not asymmetric.
> But I have some doubt. Am I wrong? The Help
> writers couassld have made written this more clearly.
>

I would say they simply forget to update the help after D7.
In D7 it was asymmetric and described correctly as such in the help. (For
example SimpleRoundTo(-1.235, -2) = -1.23) . But they changed the implementation
to symmetric later on.

I would love to sneak into their internal bugtracking system. I bet there is a
report from someone who had problems to distinguish between symmetric/asymmetric
rounding and found someone *fixing* it.

David Erbas-White

unread,
Aug 6, 2007, 2:36:48 PM8/6/07
to
Bob Dawson wrote:
>
> re : "Check it on your calculator too."
> I'm still trying to find the 'Round' button on my calculator ... <g>
>
> bobD
>
>


It's next to the 'any' key...

David Erbas-White

Trevor Toms

unread,
Aug 7, 2007, 4:17:00 AM8/7/07
to
In article <46b752e5$1...@newsgroups.borland.com>, bda...@idtdna.com (Bob
Dawson) wrote:

> I'm still trying to find the 'Round' button on my calculator ... <g>
>

<g> Well, I must admit that all mine are square - I should have checked
first ;-)

Trevor

Trevor Toms

unread,
Aug 7, 2007, 4:17:00 AM8/7/07
to
In article <46b7...@newsgroups.borland.com>, herb-sci1_AT_sbcglobal.net
(John Herbster) wrote:


> Trevor, You may wish to note how I modified your code to
> avoid the conversion to string and back. You may also
> wish to note how the modification stretches the range
> of correct operation. You can find the source for a project
> with both the modification and the tests in the attachments
> group (borland.public.attachments) with the subject
> T_RoundTo_3. Regards, JohnH
>

Thanks John - I'll grab it now!

Trevor

Trevor Toms

unread,
Aug 7, 2007, 4:42:00 AM8/7/07
to
In article <46b74b06$1...@newsgroups.borland.com>,
herb-sci1_AT_sbcglobal.net (John Herbster) wrote:

> Hogwash! Furthermore, that statement is not even
> clear as to whether "rounds up" means away from
> zero or toward plus infinity. --JohnH
>

Hey, I was using everyday language! The example I gave was the positive
value 0.5, and I did say "round up", i.e. "towards plus infinity" (and I
presume you mean "countable infinity" :-) )

I *also* said that the only dispute, therefore, was what happens to
negative values - do they round towards or away from zero. Was that not
clear?

So we're actually saying the same thing; in which case hopefully not
"hogwash".

Trevor

Trevor Toms

unread,
Aug 7, 2007, 4:52:00 AM8/7/07
to
In article <46b7...@newsgroups.borland.com>, herb-sci1_AT_sbcglobal.net
(John Herbster) wrote:

> Trevor, You may wish to note how I modified your code to
> avoid the conversion to string and back. You may also
> wish to note how the modification stretches the range
> of correct operation. You can find the source for a project
> with both the modification and the tests in the attachments
> group (borland.public.attachments) with the subject
> T_RoundTo_3. Regards, JohnH

Your code is perfect, thanks, and has now replaced my clunky quick hack
fix!

Trevor

Sven Pran

unread,
Aug 7, 2007, 5:08:31 AM8/7/07
to

"Ralf Jansen" <ralf.jans...@deepinvent.com> wrote in message
news:46b75d65$1...@newsgroups.borland.com...

> John Herbster schrieb:
>>
>> "To the larger value" to me would imply that -1.5 would go to -2, and
>> +1.5 would go to +2.
>> To me, that would be symmetric, not asymmetric.
>> But I have some doubt. Am I wrong? The Help writers couassld have made
>> written this more clearly.
>
> I would say they simply forget to update the help after D7.
> In D7 it was asymmetric and described correctly as such in the help. (For
> example SimpleRoundTo(-1.235, -2) = -1.23) . But they changed the
> implementation to symmetric later on.
>
> I would love to sneak into their internal bugtracking system. I bet there
> is a report from someone who had problems to distinguish between
> symmetric/asymmetric rounding and found someone *fixing* it.
>
>
> --
> Ralf Jansen

I have a problem understanding what is meant by "symmetric"
versus "asymetric" rounding? Intuitively I would expect
"asymetric" rounding to be a synonym for "banker's" rounding
(which is well defined) but that is not what the documentation
seems to imply.

However, there must be an error in the documentation on
"SimpleRoundTo" in my BDS2006 help files:

Does SimpleRoundTo(1234567, 3) really return 1234000 such
as the help example says? I certainly hope not!

Sven

John Herbster

unread,
Aug 7, 2007, 8:55:33 AM8/7/07
to

"Sven Pran" <no.d...@mail.please> wrote

> I have a problem understanding what is meant by
> "symmetric" versus "asymetric" rounding?

Me too! I guess that symmetric means that the
rounding rules act symmetrically about zero. In
other words, if 1.1 rounds to 2 then -1.1 rounds to -2.

I think that about half of our rounding problems are
sloppy terminology. The following site seems to be
pretty good at explaining some most of the common
rounding rules:
http://www.diycalculator.com/popup-m-round.shtml

> Does SimpleRoundTo(1234567, 3) really return 1234000 such
> as the help example says? I certainly hope not!

With D7:
SimpleRoundTo(1234567,3) ==> 1235000

Rgds, JohnH

John Herbster

unread,
Aug 7, 2007, 9:02:38 AM8/7/07
to

> > You may wish to note how I modified your code to
> > avoid the conversion to string and back.

"Trevor Toms" <sm...@cix.compulink.co.uk> wrote
> Your code is perfect, thanks, and has now replaced ...

Trevor, Please check my code with more complete
examination and testing, particularly for actions on
negative numbers. My tests were mainly to quickly
find suspected problems, not to show that there are
no problems. Regards, JohnH

Anders Isaksson

unread,
Aug 7, 2007, 10:17:08 AM8/7/07
to
Trevor Toms wrote:

> Ask any mathematician and you'll get the same answer: 0.5 rounds up.

That I doubt. This produces a biased rounding - round a lot of positive
values and add them, and the result will consistently be larger than
the sum of the original values.

Bankers rounding is statistically unbiased - round a lot of positive
values and add them, and the result will be very much the same as the
sum of the original values.

Trevor Toms

unread,
Aug 9, 2007, 4:05:00 AM8/9/07
to
In article <46b87ee3$1...@newsgroups.borland.com>, i.rather@not (Anders
Isaksson) wrote:

> That I doubt.

Please refer to the rest of my post. Banker's Rounding is a very specific
method of rounding that (as you note) avoids financial bias since money
comes in discrete whole values.

What we're dealing with in my original context though is slightly
different, since the values being managed are not necessarily discrete.
Also, I'm not concerned about bias - I want consistency and accuracy. I
don't want "some" to be rounded up and "others" to be rounded down.

SimpleRoundTo doesn't conform to its specification - and that's all I'm
trying to achieve! If the specification said "Uses Banker's Rounding" I
wouldn't have used it. JohnH's solution appears to be my best option for
now.

Trevor

Trevor Toms

unread,
Aug 9, 2007, 4:05:00 AM8/9/07
to
In article <46b86c56$1...@newsgroups.borland.com>,
herb-sci1_AT_sbcglobal.net (John Herbster) wrote:

> *From:* "John Herbster" <herb-sci1_AT_sbcglobal.net>
> *Date:* Tue, 7 Aug 2007 08:02:38 -0500

> Trevor, Please check my code with more complete
> examination and testing, particularly for actions on
> negative numbers. My tests were mainly to quickly
> find suspected problems, not to show that there are
> no problems. Regards, JohnH
>

Will do - thanks.

Trevor

John Herbster

unread,
Aug 9, 2007, 7:19:26 AM8/9/07
to
"Trevor Toms" <sm...@cix.compulink.co.uk> wrote
> JohnH's solution appears to be my best option for now.

Trevor,

By that, I am afraid that you are referring to my
modification of the code

> precision := '%.' + IntToStr(CurrencyDecimals) + 'f' ;
> tempStr := Format(precision,
> [Abs(money + (Sign(money) * 0.00000001))]) ;
> result := Sign(money) * StrToFloatDef(tempStr, 0) ;

I believe that a better solution would be to use one
of the rounding functions from my decimal rounding
module available from CodeCentral, using rounding
control word drHalfUp, while also paying attention
to the expected precision of your variables. The
routines in this unit do more intelligent selection
of your "0.00000001" fudge factor.

Regards, JohnH

--
Support the movement to add floating and fixed
decimal fraction numbers to Delphi.
http://qc.borland.com/wc/qcmain.aspx?d=28022
DecimalRounding (JH1)
http://cc.codegear.com/Item.aspx?id=21909
IEEE Number Analyzer (project code)
http://cc.codegear.com/item.aspx?id=23631
ExactFloatToStr
http://cc.codegear.com/Item.aspx?id=19421

Trevor Toms

unread,
Aug 10, 2007, 1:16:00 PM8/10/07
to
In article <46baf725$1...@newsgroups.borland.com>,
herb-sci1_AT_sbcglobal.net (John Herbster) wrote:

> *From:* "John Herbster" <herb-sci1_AT_sbcglobal.net>

> *Date:* Thu, 9 Aug 2007 06:19:26 -0500


>
> I believe that a better solution would be to use one
> of the rounding functions from my decimal rounding
> module available from CodeCentral, using rounding
> control word drHalfUp, while also paying attention
> to the expected precision of your variables. The
> routines in this unit do more intelligent selection
> of your "0.00000001" fudge factor.
>
> Regards, JohnH
>

Thanks John. The 0.0000001 was indeed a kludge! I'll look into your other
routines over the weekend.

Trevor

David Marcus

unread,
Aug 10, 2007, 5:09:28 PM8/10/07
to
Trevor Toms wrote:
> SimpleRoundTo is supposed to round midvalues up to the level of specified
> precision, but some specific values fail this.
>
> E.g. SimpleRoundTo(79.625, -2) gives the result 79.62 rather than the
> expected 79.63.
>
> We've got round it by a convoluted process of conversion from floats to
> strings and back to floats again, but is there an alternative function
> which works?

Changing the declaration of LFactor in the Delphi code to extended seems
to help. I'd suggest changing all the doubles in the routine to
extendeds. Of course, this won't help if the string doesn't have an
exact binary representation and the binary representation is on the
other side of the 5 threshold than the string, but I wonder if it will
make the routine work properly when the string does have such a
representation.

--
David Marcus

Q Correll

unread,
Aug 10, 2007, 6:44:03 PM8/10/07
to
John,

| I just posted it in b.p.attachments group under subject:
| T_RoundTo_3 -- Test for bug in RoundTo functions

Snagged it.

Thanks!!!

--
Q

08/10/2007 15:43:52

XanaNews Version 1.17.5.7 [Q's salutation mod]

John Herbster

unread,
Aug 10, 2007, 8:36:48 PM8/10/07
to
> > We've got round it by a convoluted process
> > of conversion from floats to strings and back
> > to floats again, but is there an alternative
> > function which works?


"David Marcus" <David...@AlumDotMIT.edu> wrote


> Changing the declaration of LFactor in the Delphi
> code to extended seems to help. I'd suggest
> changing all the doubles in the routine to
> extendeds. Of course, this won't help if the
> string doesn't have an exact binary representation

David, Most do not!

> and the binary representation is on the other side
> of the 5 threshold than the string, but I wonder
> if it will make the routine work properly when the
> string does have such a representation.

You can save yourself a lot of wondering by properly
using the rounding functions in DecimalRounding_JH1.

Rgds, JohnH

David Marcus

unread,
Aug 11, 2007, 5:16:49 PM8/11/07
to
"John Herbster" <herb-sci1_AT_sbcglobal.net> wrote:
> "David Marcus" <David...@AlumDotMIT.edu> wrote
> > Changing the declaration of LFactor in the Delphi
> > code to extended seems to help. I'd suggest
> > changing all the doubles in the routine to
> > extendeds. Of course, this won't help if the
> > string doesn't have an exact binary representation
>
> David, Most do not!
>
> > and the binary representation is on the other side
> > of the 5 threshold than the string, but I wonder
> > if it will make the routine work properly when the
> > string does have such a representation.
>
> You can save yourself a lot of wondering by properly
> using the rounding functions in DecimalRounding_JH1.

I'm sure the routines are useful, but my point was that the Delphi
routine seems to have a bug. There are two separate issues: conversion
from decimal strings to binary floating point and rounding of binary
floating point numbers.

IEEE arithmetic works pretty well, in general. But, trying to round a
floating point number by using doubles is not a good idea.

If you really want to round a decimal string, then converting it to a
binary floating point number is not a good idea. But, if you want to
round a binary floating point number, then a simple fix will make the
Delphi routine do it much more reliably.

--
David Marcus

David Marcus

unread,
Aug 11, 2007, 5:30:46 PM8/11/07
to
David Marcus wrote:
> I'm sure the routines are useful, but my point was that the Delphi
> routine seems to have a bug. There are two separate issues: conversion
> from decimal strings to binary floating point and rounding of binary
> floating point numbers.
>
> IEEE arithmetic works pretty well, in general. But, trying to round a
> floating point number by using doubles is not a good idea.
>
> If you really want to round a decimal string, then converting it to a
> binary floating point number is not a good idea. But, if you want to
> round a binary floating point number, then a simple fix will make the
> Delphi routine do it much more reliably.

Note that Delphi's writeln function does rounding and doesn't have the
problem that SimpleRoundTo has. E.g.,

var
X: double;
begin
X := 79.625;
writeln( 79.625:5:2 );
writeln( X:5:2 );
end.

produces

79.63
79.63

So, if writeln can do it, then SimpleRoundTo should be able to do it,
too.

--
David Marcus

John Herbster

unread,
Aug 11, 2007, 8:54:09 PM8/11/07
to

> > > Changing the declaration of LFactor in the
> > > Delphi code to extended seems to help.

"David Marcus" <David...@AlumDotMIT.edu> wrote


> I'm sure the routines are useful, but my point
> was that the Delphi routine seems to have a bug.

There is no doubt about that.

> There are two separate issues:

David,

> conversion from decimal strings to binary floating
> point

What do you see as the problem above? Maybe the
fact that most decimal fractions have no exact
representation in floating binary point
variables or something else?

> and rounding of binary floating point numbers [to
> fixed decimal fraction]



> IEEE arithmetic works pretty well, in general. But,
> trying to round a floating point number by using
> doubles is not a good idea.

Then why do we do so often? Is it perhaps because
there is not a good alternative?

> If you really want to round a decimal string, then
> converting it to a binary floating point number
> is not a good idea.

> But, if you want to round a binary floating point
> number, then a simple fix will make the Delphi
> routine do it much more reliably.

A proof would be welcome. Please include a statement
of the rounding rule along with the code and test
results.

Regards, JohnH

David Marcus

unread,
Aug 12, 2007, 12:50:44 PM8/12/07
to
"John Herbster" <herb-sci1_AT_sbcglobal.net> wrote:
>
> > > > Changing the declaration of LFactor in the
> > > > Delphi code to extended seems to help.
>
> "David Marcus" <David...@AlumDotMIT.edu> wrote
> > I'm sure the routines are useful, but my point
> > was that the Delphi routine seems to have a bug.
>
> There is no doubt about that.
>
> > There are two separate issues:
>
> David,
>
> > conversion from decimal strings to binary floating
> > point
>
> What do you see as the problem above? Maybe the
> fact that most decimal fractions have no exact
> representation in floating binary point
> variables or something else?

Yes. However, whether it is a problem, depends on the application.

> > and rounding of binary floating point numbers [to
> > fixed decimal fraction]
>
> > IEEE arithmetic works pretty well, in general. But,
> > trying to round a floating point number by using
> > doubles is not a good idea.
>
> Then why do we do so often? Is it perhaps because
> there is not a good alternative?

Sorry, but I don't know what you mean. I don't do rounding by doing
arithmetic with doubles. The best way to round numbers for output is to
use write or writeln. If you need to round a number to store it into a
variable, then Delphi doesn't make this easy.

> > If you really want to round a decimal string, then
> > converting it to a binary floating point number
> > is not a good idea.
>
> > But, if you want to round a binary floating point
> > number, then a simple fix will make the Delphi
> > routine do it much more reliably.
>
> A proof would be welcome. Please include a statement
> of the rounding rule along with the code and test
> results.

{$Optimization on}
{$apptype console}

program Test;

uses
Math;

function F(const AValue: extended; const ADigit: TRoundToRange = -2):
extended;
var
LFactor: extended;
begin
LFactor := IntPower(10, ADigit);

if AValue < 0 then
Result := Trunc((AValue / LFactor) - 0.5) * LFactor
else
Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
end;

var
J, K, L: integer;
X, Y: extended;

begin

for J := 0 to 9 do
for K := 0 to 9 do
for L := 0 to 9 do begin
X := ( J / 10 + K / 100 );
Y := X + L / 1000;

if L >= 5 then
X := X + 1 / 100;

if abs( F( Y, -2 ) - X ) > 0.005 then
writeln( '0.', J, K, L, ' ', Y, F( Y, -2 ), Y:5:2 );
end;

writeln( 'SimpleRoundTo' );

for J := 0 to 9 do
for K := 0 to 9 do
for L := 0 to 9 do begin
X := ( J / 10 + K / 100 );
Y := X + L / 1000;

if L >= 5 then
X := X + 1 / 100;

if abs( SimpleRoundTo( Y, -2 ) - X ) > 0.005 then
writeln( '0.', J, K, L, ' ', Y, F( Y, -2 ), Y:5:2 );
end;

end. // Test.

0.135 1.35000000000000E-0001 1.30000000000000E-0001 0.14
0.535 5.35000000000000E-0001 5.30000000000000E-0001 0.54
SimpleRoundTo
0.015 1.50000000000000E-0002 2.00000000000000E-0002 0.02
0.045 4.50000000000000E-0002 5.00000000000000E-0002 0.05
0.055 5.50000000000000E-0002 6.00000000000000E-0002 0.06
0.075 7.50000000000000E-0002 8.00000000000000E-0002 0.08
0.095 9.50000000000000E-0002 1.00000000000000E-0001 0.10
0.105 1.05000000000000E-0001 1.10000000000000E-0001 0.11
0.125 1.25000000000000E-0001 1.30000000000000E-0001 0.13
0.145 1.45000000000000E-0001 1.50000000000000E-0001 0.15
0.155 1.55000000000000E-0001 1.60000000000000E-0001 0.16
0.175 1.75000000000000E-0001 1.80000000000000E-0001 0.18
0.185 1.85000000000000E-0001 1.90000000000000E-0001 0.19
0.205 2.05000000000000E-0001 2.10000000000000E-0001 0.21
0.215 2.15000000000000E-0001 2.20000000000000E-0001 0.22
0.235 2.35000000000000E-0001 2.40000000000000E-0001 0.24
0.245 2.45000000000000E-0001 2.50000000000000E-0001 0.25
0.255 2.55000000000000E-0001 2.60000000000000E-0001 0.26
0.285 2.85000000000000E-0001 2.90000000000000E-0001 0.29
0.295 2.95000000000000E-0001 3.00000000000000E-0001 0.30
0.305 3.05000000000000E-0001 3.10000000000000E-0001 0.31
0.315 3.15000000000000E-0001 3.20000000000000E-0001 0.32
0.345 3.45000000000000E-0001 3.50000000000000E-0001 0.35
0.355 3.55000000000000E-0001 3.60000000000000E-0001 0.36
0.365 3.65000000000000E-0001 3.70000000000000E-0001 0.37
0.375 3.75000000000000E-0001 3.80000000000000E-0001 0.38
0.415 4.15000000000000E-0001 4.20000000000000E-0001 0.42
0.425 4.25000000000000E-0001 4.30000000000000E-0001 0.43
0.435 4.35000000000000E-0001 4.40000000000000E-0001 0.44
0.445 4.45000000000000E-0001 4.50000000000000E-0001 0.45
0.475 4.75000000000000E-0001 4.80000000000000E-0001 0.48
0.485 4.85000000000000E-0001 4.90000000000000E-0001 0.49
0.495 4.95000000000000E-0001 5.00000000000000E-0001 0.50
0.505 5.05000000000000E-0001 5.10000000000000E-0001 0.51
0.565 5.65000000000000E-0001 5.70000000000000E-0001 0.57
0.575 5.75000000000000E-0001 5.80000000000000E-0001 0.58
0.585 5.85000000000000E-0001 5.90000000000000E-0001 0.59
0.595 5.95000000000000E-0001 6.00000000000000E-0001 0.60
0.605 6.05000000000000E-0001 6.10000000000000E-0001 0.61
0.615 6.15000000000000E-0001 6.20000000000000E-0001 0.62
0.625 6.25000000000000E-0001 6.30000000000000E-0001 0.63
0.635 6.35000000000000E-0001 6.40000000000000E-0001 0.64
0.695 6.95000000000000E-0001 7.00000000000000E-0001 0.70
0.705 7.05000000000000E-0001 7.10000000000000E-0001 0.71
0.715 7.15000000000000E-0001 7.20000000000000E-0001 0.72
0.725 7.25000000000000E-0001 7.30000000000000E-0001 0.73
0.735 7.35000000000000E-0001 7.40000000000000E-0001 0.74
0.745 7.45000000000000E-0001 7.50000000000000E-0001 0.75
0.755 7.55000000000000E-0001 7.60000000000000E-0001 0.76
0.765 7.65000000000000E-0001 7.70000000000000E-0001 0.77
0.815 8.15000000000000E-0001 8.20000000000000E-0001 0.82
0.825 8.25000000000000E-0001 8.30000000000000E-0001 0.83
0.835 8.35000000000000E-0001 8.40000000000000E-0001 0.84
0.845 8.45000000000000E-0001 8.50000000000000E-0001 0.85
0.855 8.55000000000000E-0001 8.60000000000000E-0001 0.86
0.865 8.65000000000000E-0001 8.70000000000000E-0001 0.87
0.875 8.75000000000000E-0001 8.80000000000000E-0001 0.88
0.885 8.85000000000000E-0001 8.90000000000000E-0001 0.89
0.895 8.95000000000000E-0001 9.00000000000000E-0001 0.90
0.945 9.45000000000000E-0001 9.50000000000000E-0001 0.95
0.955 9.55000000000000E-0001 9.60000000000000E-0001 0.96
0.965 9.65000000000000E-0001 9.70000000000000E-0001 0.97
0.975 9.75000000000000E-0001 9.80000000000000E-0001 0.98
0.985 9.85000000000000E-0001 9.90000000000000E-0001 0.99
0.995 9.95000000000000E-0001 1.00000000000000E+0000 1.00

This is just a crude test, but it provides evidence for two conclusions:

1. SimpleRoundTo's use of "double" instead of "extended" is a bug.

2. The rounding routines that writeln uses are superior.

Two other facts that are relevant:

3. Rounding by rescaling (as SimpleRoundTo or my fixed version does)
makes you susceptible to loss of precision.

4. Conversion of decimal strings to binary floating point is rarely
exact.

--
David Marcus

John Herbster

unread,
Aug 12, 2007, 2:29:26 PM8/12/07
to
"David Marcus" <David...@AlumDotMIT.edu> wrote
> ...
> The best way to round numbers for output is to
> use write or writeln.

Do you mean that you would use Write() or WriteLn()
to round a number stored in a floating binary point
variable to a string format? If so how do you spec
what kind of rounding to do?

> If you need to round a number to store it into a
> variable, then Delphi doesn't make this easy.

In my opinion, Delphi makes it too easy. Programmers
are frequently using StrToFloat() and assignments like
<FP var> := <currency var>; or
<FP var> := <numerator>/<denominator>; or
<double var> := <extended var>
without understand what rounding or loss of precision
or data is happening.



> > > But, if you want to round a binary floating point
> > > number, then a simple fix will make the Delphi
> > > routine do it much more reliably.

> > A proof would be welcome. Please include a statement
> > of the rounding rule along with the code and test
> > results.

> program Test;
> ...

David,

Thanks I will check out your proof and respond later.

Regards, JohnH

David Marcus

unread,
Aug 12, 2007, 4:57:28 PM8/12/07
to
David Marcus wrote:
> 2. The rounding routines that writeln uses are superior.

I think this statement I made is premature. All my little test shows is
that writeln is consistent: when you ask it round the number, the result
is consistent with what you get when you ask writeln for all digits.
We'd have to examine the binary value to see if writeln is rounding
correctly.

--
David Marcus

David Marcus

unread,
Aug 12, 2007, 5:08:49 PM8/12/07
to
"John Herbster" <herb-sci1_AT_sbcglobal.net> wrote:
> "David Marcus" <David...@AlumDotMIT.edu> wrote
> > ...
> > The best way to round numbers for output is to
> > use write or writeln.
>
> Do you mean that you would use Write() or WriteLn()
> to round a number stored in a floating binary point
> variable to a string format?

Sure.

> If so how do you spec what kind of rounding to do?

Are you asking whether writeln allows you to configure the type of
rounding? It doesn't. It rounds to nearest and rounds numbers that are
exactly in the middle away from zero.

> > If you need to round a number to store it into a
> > variable, then Delphi doesn't make this easy.
>
> In my opinion, Delphi makes it too easy. Programmers
> are frequently using StrToFloat() and assignments like
> <FP var> := <currency var>; or
> <FP var> := <numerator>/<denominator>; or
> <double var> := <extended var>
> without understand what rounding or loss of precision
> or data is happening.

None of these does what I was referring to. I was referring to rounding
an extended value to a specified decimal precision and storing the
result in an extended variable.

--
David Marcus

John Herbster

unread,
Aug 12, 2007, 5:13:30 PM8/12/07
to

"David Marcus" <David...@AlumDotMIT.edu> wrote

> function F (


> const AValue: extended;
> const ADigit: TRoundToRange = -2): extended;
> var LFactor: extended;
> begin
> LFactor := IntPower(10, ADigit);
> if AValue < 0 then
> Result := Trunc((AValue / LFactor) - 0.5) * LFactor
> else
> Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
> end;

> 1. SimpleRoundTo's use of "double" instead of
> "extended" is a bug.

David, Yes, it should have been an extended type.
I would call this a case of thoughtlessness.

Looking again at the D7 Help for SimpleRoundTo, it
claims that it "Rounds a floating-point value to a
specified digit or power of ten using asymmetric
arithmetic rounding." From my Googling, I believe
that asymmetric arithmetic rounding normal means
that rounding should be to the nearest and points
midway between are suppose to round toward plus inf.

> 2. The rounding routines that writeln uses are
> superior.

Superior -- in what regard? I have never seen a
statement of what rounding rule that they are
supposed to follow.

> Two other facts that are relevant:

> 3. Rounding by rescaling (as SimpleRoundTo or my
> fixed version does) makes you susceptible to loss
> of precision.

All rounding makes you susceptible to loss of
precision.

> 4. Conversion of decimal strings to binary floating
> point is rarely exact.

I note that in your test you are using WriteLn() to
give you strings with numbers like
1.35000000000000E-0001
Are you aware that these are not exact? The exact
number would be something like
0.13499 99999 99999 99999 13263 82620 11596
45279 40377 59304 04663 08593 75

If you really want to know the exact value, you should
be using something like my ExactFloatToStr() routines
that are available from CodeCentral.

You might want to check some of the small differences
between you F function and my DecimalRoundDbl function
(available from CodeCentral as part of the
DecimalRounding_JH1 module.)

When rounding the nominally "0.045" value
Inp=+ 0.04499 99999 99999 99999 82381 71469 71105
52947 37889 19858 63447 18933 10546 875
Your code gives
DsF=+ 0.04999 99999 99999 99999 72894 94568
78623 89149 81367 99782 51457 21435 54687 5
And DecimalRoundDbl gives
DRD=+ 0.05000 00000 00000 00000 06776 26357
80344 02712 54658 00054 37135 69641 11328 125
Yours is close, but not as close as it could be, given
that it is returning an extended result.

David Marcus

unread,
Aug 12, 2007, 9:33:34 PM8/12/07
to
"John Herbster" <herb-sci1_AT_sbcglobal.net> wrote:
> "David Marcus" <David...@AlumDotMIT.edu> wrote
>
> > function F (
> > const AValue: extended;
> > const ADigit: TRoundToRange = -2): extended;
> > var LFactor: extended;
> > begin
> > LFactor := IntPower(10, ADigit);
> > if AValue < 0 then
> > Result := Trunc((AValue / LFactor) - 0.5) * LFactor
> > else
> > Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
> > end;
>
> > 1. SimpleRoundTo's use of "double" instead of
> > "extended" is a bug.
>
> David, Yes, it should have been an extended type.
> I would call this a case of thoughtlessness.

Many bugs are due to thoughtlessness.

> > 2. The rounding routines that writeln uses are
> > superior.
>
> Superior -- in what regard?

I meant superior to the fixed SimpleRoundTo, but I'll retract this
statement, since I jumped to a conclusion. It is certainly superior to
the bugged SimpleRoundTo.

> I have never seen a
> statement of what rounding rule that they are
> supposed to follow.

It appears to be doing round to nearest with numbers exactly in the
middle rounded away from zero. All the Fortran compilers I've used do
the same. It is too long since I've used a C compiler for me to
remember, but I suspect they do the same.

> > 3. Rounding by rescaling (as SimpleRoundTo or my
> > fixed version does) makes you susceptible to loss
> > of precision.
>
> All rounding makes you susceptible to loss of
> precision.

There is a well-defined mathematical function from real numbers to real
numbers that rounds numbers to a given number of digits. There is no
reason why a computer can't implement the restriction of this function
to the set of binary floating point numbers. My point was that
implementing such a function by rescaling is unlikely to give the
correct answer for all values in the domain.

> I note that in your test you are using WriteLn() to
> give you strings with numbers like
> 1.35000000000000E-0001
> Are you aware that these are not exact?

I knew they weren't exact, but I didn't remember until after my first
post that I don't know if writeln is rounding correctly.

--
David Marcus

David Marcus

unread,
Aug 12, 2007, 10:15:14 PM8/12/07
to
David Marcus wrote:
> There is a well-defined mathematical function from real numbers to real
> numbers that rounds numbers to a given number of digits. There is no
> reason why a computer can't implement the restriction of this function
> to the set of binary floating point numbers. My point was that
> implementing such a function by rescaling is unlikely to give the
> correct answer for all values in the domain.

Come to think of it, it might be better if a routine like writeln first
converts the value to decimal, then rounds it. This way, if you assign a
decimal value to a real variable and the binary value isn't identical to
the decimal value, you would still get what you expect when you write
the variable.

--
David Marcus

David Marcus

unread,
Aug 13, 2007, 2:50:44 PM8/13/07
to

program Test;

var
X: extended;
J: integer;

begin

X := 0.49999999999999999;
writeln( X );

for J := 18 downto 1 do
writeln( X:J+3:J );

writeln( X:2:0 );

end.

Output:

5.00000000000000E-0001
0.499999999999999990
0.49999999999999999
0.5000000000000000
0.500000000000000
0.50000000000000
0.5000000000000
0.500000000000
0.50000000000
0.5000000000
0.500000000
0.50000000
0.5000000
0.500000
0.50000
0.5000
0.500
0.50
0.5
0

--
David Marcus

John Herbster

unread,
Aug 13, 2007, 5:05:58 PM8/13/07
to

"David Marcus" <David...@AlumDotMIT.edu> wrote

> X := 0.49999999999999999;
> writeln( X );

David, I am glad that you are interested in this
important stuff.

However, you need to give up on that WriteLn(X).
It is lying to you.

If your variable X above is of type extended, then
after the assignment, it will contain exactly
+0.49999 99999 99999 98999 82349 58821 22159
62812 47911 97478 77120 97167 96875
not 5.00000000000000E-0001

You can get the exact values with functions
in my ExactFloatToStr module which is available
from CodeCentral or you can write your own.

John Herbster

unread,
Aug 13, 2007, 5:06:40 PM8/13/07
to

David wrote
> function DsF (

> const AValue: extended;
> const ADigit: TRoundToRange = -2): extended;
> var LFactor: extended;
> begin
> LFactor := IntPower(10, ADigit);
> if AValue < 0 then
> Result := Trunc((AValue / LFactor) - 0.5) * LFactor
> else
> Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
> end;

David, Please note that if you change your fucntion to

function DF2
(const AValue: extended;


const ADigit: TRoundToRange = -2)
: extended;
var LFactor: extended;
begin

LFactor := IntPower(10, -ADigit);
if AValue < 0
then Result := Trunc((AValue*LFactor) - 0.5)/LFactor
else Result := Trunc((AValue*LFactor) + 0.5)/LFactor;
end;

then the accuracy will be improved in a lot of cases.
Here are the results for an input of "0.045":
Inp=+ 0.04500 00000 00000 00000 16263 03258 72825
66510 11179 20130 49125 67138 67187 5


DsF=+ 0.04999 99999 99999 99999 72894 94568 78623
89149 81367 99782 51457 21435 54687 5

DF2=+ 0.05000 00000 00000 00000 06776 26357 80344

02712 54658 00054 37135 69641 11328 125
DRD=+ 0.05000 00000 00000 00000 06776 26357 80344
02712 54658 00054 37135 69641 11328 125

where DRD is from my DecimalRoundDouble function.

However the problem that most programmers run into
is still not fixed. Consider what will happen if the
input value is from a double precision variable, such
as might be retreived from a data table, instead of
an extended variable. Then you if you are rounding
"100000.015", what you will get is this:
Inp=+ 1 00000.01499 99999 99417 92339 08653 25927
73437 5
DF2=+ 1 00000.00999 99999 99998 01048 03398 71719
47956 08520 50781 25
DRD=+ 1 00000.02000 00000 00003 12638 80373 44440
81783 29467 77343 75
and you can see that again DRD is closer to the right
result. If you step through the DF2 calculation you
can see why. That is why my decimal rounding routines
use carefully chosen fudge factors.

John Herbster

unread,
Aug 13, 2007, 6:21:40 PM8/13/07
to
David,

Please note that even with your function F changed
to the following


> function DF2
> (const AValue: extended;
> const ADigit: TRoundToRange = -2)
> : extended;
> var LFactor: extended;
> begin
> LFactor := IntPower(10, -ADigit);
> if AValue < 0
> then Result := Trunc((AValue*LFactor) - 0.5)/LFactor
> else Result := Trunc((AValue*LFactor) + 0.5)/LFactor;
> end;

and even with the caller's input variable being of type
extended. It will still start giving bad outputs when
rounding test inputs around 9e12 to pennies as you
can see in the following:
Inp=+ 900 00000 00000.02499 96185 30273 4375
Dv2=+ 900 00000 00000.02000 04577 63671 875
DRD=+ 900 00000 00000.02999 97329 71191 40625

Regards, JohnH

David Marcus

unread,
Aug 14, 2007, 6:09:09 PM8/14/07
to
"John Herbster" <herb-sci1_AT_sbcglobal.net> wrote:
> "David Marcus" <David...@AlumDotMIT.edu> wrote
> > X := 0.49999999999999999;
> > writeln( X );
>
> David, I am glad that you are interested in this
> important stuff.
>
> However, you need to give up on that WriteLn(X).
> It is lying to you.

Never said it was giving the exact value. Was just showing an example of
how writeln performs.

> If your variable X above is of type extended, then
> after the assignment, it will contain exactly
> +0.49999 99999 99999 98999 82349 58821 22159
> 62812 47911 97478 77120 97167 96875
> not 5.00000000000000E-0001

Which is the correctly rounded value, so writeln seems to be doing fine,
although one might ask why writeln doesn't give more digits by default.

--
David Marcus

David Marcus

unread,
Aug 14, 2007, 6:25:29 PM8/14/07
to
"John Herbster" <herb-sci1_AT_sbcglobal.net> wrote:
> However the problem that most programmers run into
> is still not fixed. Consider what will happen if the
> input value is from a double precision variable, such
> as might be retreived from a data table, instead of
> an extended variable. Then you if you are rounding
> "100000.015", what you will get is this:
> Inp=+ 1 00000.01499 99999 99417 92339 08653 25927
> 73437 5
> DF2=+ 1 00000.00999 99999 99998 01048 03398 71719
> 47956 08520 50781 25
> DRD=+ 1 00000.02000 00000 00003 12638 80373 44440
> 81783 29467 77343 75
> and you can see that again DRD is closer to the right
> result. If you step through the DF2 calculation you
> can see why. That is why my decimal rounding routines
> use carefully chosen fudge factors.

Sure. If you assign to a double, you will lose precision. Binary
floating point is not decimal floating point. People who think it is,
will be surprised.

Whether 100000.02 is the "right" result depends on how you define the
problem. 100000.015 rounded to two digits is 100000.02, but Inp rounded
to two digits is 100000.01.

--
David Marcus

John Herbster

unread,
Aug 14, 2007, 7:39:33 PM8/14/07
to

> > "100000.015", what you will get is this:
> > Inp=+ 1 00000.01499 99999 99417 92339 08653 25927
> > 73437 5

"David Marcus" <David...@AlumDotMIT.edu> wrote


> Whether 100000.02 is the "right" result depends
> on how you define the problem. 100000.015 rounded
> to two digits is 100000.02, but Inp rounded > to
> two digits is 100000.01.

Dear David,

The goal in writing my DecimalRounding package was to
help programmers having to use floating binary point
numbers for applications requiring decimal fraction
output. Copied here (from the beginning of that package)
is the problem as I see it and how that package solved
it.

"ROUTINES FOR ROUNDING IEEE-754 FLOATS TO SPECIFIED
NUMBER OF DECIMAL FRACTION DIGITS"

"Because, in general, numbers with decimal fractions
cannot be exactly represented in IEEE-754 floating
binary point variables, error limits are used to
determine if the input numbers are intended to
represent an exact decimal fraction rather than a
nearby value. Thus an error limit will be taken
into account when deciding that a number input such
as 1.295, which internally is represented as
1.29499 99999 …, really should be considered exactly
1.295 and that 0.29999 99999 ... really should be
interpreted as 0.3 when applying the rounding rules."

"These routines round input values to fit as closely
as possible to an output number with desired number
of decimal fraction digits. "

Good luck, JohnH

David Marcus

unread,
Aug 14, 2007, 8:17:57 PM8/14/07
to
"John Herbster" <herb-sci1_AT_sbcglobal.net> wrote:
> The goal in writing my DecimalRounding package was to
> help programmers having to use floating binary point
> numbers for applications requiring decimal fraction
> output. Copied here (from the beginning of that package)
> is the problem as I see it and how that package solved
> it.
>
> "ROUTINES FOR ROUNDING IEEE-754 FLOATS TO SPECIFIED
> NUMBER OF DECIMAL FRACTION DIGITS"
>
> "Because, in general, numbers with decimal fractions
> cannot be exactly represented in IEEE-754 floating
> binary point variables, error limits are used to
> determine if the input numbers are intended to
> represent an exact decimal fraction rather than a
> nearby value. Thus an error limit will be taken
> into account when deciding that a number input such
> as 1.295, which internally is represented as
> 1.29499 99999 …, really should be considered exactly
> 1.295 and that 0.29999 99999 ... really should be
> interpreted as 0.3 when applying the rounding rules."
>
> "These routines round input values to fit as closely
> as possible to an output number with desired number
> of decimal fraction digits. "

That's fine. But, not all people wanting to round a floating point
number have this goal. And, many people who get tangled up with this
problem are simply misusing floating point.

--
David Marcus

0 new messages