View Issue Details

IDProjectCategoryView StatusLast Update
0035626FPCCompilerpublic2019-10-20 23:12
ReporterJuan DíazAssigned ToFlorian 
PrioritynormalSeverityminorReproducibilityhave not tried
Status resolvedResolutionfixed 
PlatformOSWindows 7OS Version
Product VersionProduct Build 
Target VersionFixed in Version3.3.1 
Summary0035626: In ISO mode, round(x) function does not round up x when the integral part of x is even and its fractional part is = 0.5
DescriptionWhen compiling in ISO mode, either from the command line (-Miso) or with the {$MODE ISO} switch in the source code file, the round(x) function does not round up x when the integral part of x is even and its fractional part is = 0.5.
For instance, round(12.5) yields 12 and round(-4-5) yields -4, while the ISO 7185:1990 standard requires the round(x) function to be equivalent to trunc(x+0.5) if x is positive or zero; otherwise it shall be equivalent to trunc(x-0.5). That is to say, for the examples given above round(12.5) should produce 13 and round(-4.5) should produce -5.

In the FPC documentation one can read FPC uses the banker's rounding algorithm, but it would be great if in cases like the ones described above FPC's round function behaved in compliance with the ISO 7185 standard. I am hopeful that this issue will be fixed soon.
Steps To ReproduceJust write a simple test program where round is called with a positive real number whose integral part is even and its fractional part is = 0.5, and then with a negative real number with the same characteristics. Example:

program TestRoundFunction(output);

begin
  writeln('round(12.5) = ', round(12.5));
  writeln('round(-4.5) = ', round(-4.5))
end.

Compile from the command line with -Miso or add the {$MODE ISO} switch before the begin keyword and compile from within the IDE.
TagsNo tags attached.
Fixed in Revision43281
FPCOldBugId
FPCTarget-
Attached Files
  • RoundFunctionTest.pas (2,594 bytes)
    program RoundFunctionTest(output);
    
    {$MODE ISO}
                                                                     {                      Expected result       }
                                                                     { FPC result     in accordance with ISO 7185 }
                                                                     { ----------     --------------------------- }
    begin
      writeln('Testing the round() function with positive numbers:');
      writeln('round(0.5)   = ', round(0.5));                        {     0                          1           }
      writeln('round(1.5)   = ', round(1.5));                        {     2                          2           }
      writeln('round(2.5)   = ', round(2.5));                        {     2                          3           }
      writeln('round(3.5)   = ', round(3.5));                        {     4                          4           }
      writeln('round(4.5)   = ', round(4.5));                        {     4                          5           }
      writeln('round(5.5)   = ', round(5.5));                        {     6                          6           }
      writeln('round(10.5)  = ', round(10.5));                       {    10                         11           }
      writeln('round(11.5)  = ', round(11.5));                       {    12                         12           }
      writeln('round(12.5)  = ', round(12.5));                       {    12                         13           }
      writeln;
      writeln('Testing the round() function with negative numbers:');
      writeln('round(-0.5)  = ', round(-0.5));                       {     0                         -1           }
      writeln('round(-1.5)  = ', round(-1.5));                       {    -2                         -2           }
      writeln('round(-2.5)  = ', round(-2.5));                       {    -2                         -3           }
      writeln('round(-3.5)  = ', round(-3.5));                       {    -4                         -4           }
      writeln('round(-4.5)  = ', round(-4.5));                       {    -4                         -5           }
      writeln('round(-5.5)  = ', round(-5.5));                       {    -6                         -6           }
      writeln('round(-10.5) = ', round(-10.5));                      {   -10                        -11           }
      writeln('round-(11.5) = ', round(-11.5));                      {   -12                        -12           }
      writeln('round(-12.5) = ', round(-12.5));                      {   -12                        -13           }
      writeln
    end.
    
    RoundFunctionTest.pas (2,594 bytes)
  • round.gif (24,207 bytes)
    round.gif (24,207 bytes)

Activities

Juan Díaz

2019-05-24 08:14

reporter  

RoundFunctionTest.pas (2,594 bytes)
program RoundFunctionTest(output);

{$MODE ISO}
                                                                 {                      Expected result       }
                                                                 { FPC result     in accordance with ISO 7185 }
                                                                 { ----------     --------------------------- }
begin
  writeln('Testing the round() function with positive numbers:');
  writeln('round(0.5)   = ', round(0.5));                        {     0                          1           }
  writeln('round(1.5)   = ', round(1.5));                        {     2                          2           }
  writeln('round(2.5)   = ', round(2.5));                        {     2                          3           }
  writeln('round(3.5)   = ', round(3.5));                        {     4                          4           }
  writeln('round(4.5)   = ', round(4.5));                        {     4                          5           }
  writeln('round(5.5)   = ', round(5.5));                        {     6                          6           }
  writeln('round(10.5)  = ', round(10.5));                       {    10                         11           }
  writeln('round(11.5)  = ', round(11.5));                       {    12                         12           }
  writeln('round(12.5)  = ', round(12.5));                       {    12                         13           }
  writeln;
  writeln('Testing the round() function with negative numbers:');
  writeln('round(-0.5)  = ', round(-0.5));                       {     0                         -1           }
  writeln('round(-1.5)  = ', round(-1.5));                       {    -2                         -2           }
  writeln('round(-2.5)  = ', round(-2.5));                       {    -2                         -3           }
  writeln('round(-3.5)  = ', round(-3.5));                       {    -4                         -4           }
  writeln('round(-4.5)  = ', round(-4.5));                       {    -4                         -5           }
  writeln('round(-5.5)  = ', round(-5.5));                       {    -6                         -6           }
  writeln('round(-10.5) = ', round(-10.5));                      {   -10                        -11           }
  writeln('round-(11.5) = ', round(-11.5));                      {   -12                        -12           }
  writeln('round(-12.5) = ', round(-12.5));                      {   -12                        -13           }
  writeln
end.
RoundFunctionTest.pas (2,594 bytes)

Juan Díaz

2019-05-24 08:19

reporter   ~0116387

The FPC version I am using is 3.0.4

Marco van de Voort

2019-05-24 12:38

manager   ~0116390

Last edited: 2019-05-24 15:09

View 2 revisions

I tried with math.setroundmode, but it doesn't seem to have an effect, in mode ISO or without.

Marco van de Voort

2019-05-24 22:06

manager   ~0116398

Last edited: 2019-05-25 16:21

View 3 revisions

I found out that is because the example program rounds literals which are rounded compiletime. Note that I used 3.3.1

If I calculate using e.g.

for i:=-10 to 10 do
  writeln(round(i+0.5));

setroundingmode does work.

So there are two aspects,
(1) rounding mode for ISO
(2) inline round which doesn't respect roundingmode (and probably there should be a way to turn that off)

Marcin Wiazowski

2019-05-25 17:49

reporter   ~0116412

About rounding modes:



As can be found in https://en.wikipedia.org/wiki/Floating-point_arithmetic#Rounding_modes, IEEE 754 specifies the following rounding modes:

1) round to nearest, where ties round to the nearest even digit in the required position (the default and by far the most common mode)
2) round to nearest, where ties round away from zero
3) round up (toward +INF)
4) round down (toward −INF)
5) round toward zero (truncation)



Intel x87 FPU supports 1), 3), 4) and 5) - while the lacking 2) is what is required by ISO 7185.



A potential solution I can see is to add (implement) mode 2) in softfpu.pp, and use softfpu.pp when {$MODE ISO} is used.

Florian

2019-05-26 12:09

administrator   ~0116417

@Marcin: Do you have any reference in the iso standard that also arithmetic operations need to use "type 2)" rounding? Else we could just fix round in ISO mode.

Serge Anvarov

2019-05-26 15:04

reporter   ~0116419

I think this (ISO7185:1990): [http://pascal-central.com/docs/iso7185.pdf] Page 44 (6.6.6.3) "From the expression x that shall be of real-type, this function shall return a result of integer-type. If x is positive or zero, round(x) shall be equivalent to trunc(x+0.5); otherwise, round(x) shall be equivalent to trunc(x-0.5).

Florian

2019-05-26 15:08

administrator   ~0116420

Serge: Yes, but this is only about the round function. Not about floating operations in general.

Serge Anvarov

2019-05-26 20:20

reporter   ~0116422

Because it depends on the specific implementation. Ibid
6.7.2.2 Arithmetic operators...The results of integer-to-real conversion, of the real arithmetic operators and of the required real functions shall be approximations to the corresponding mathematical results. The accuracy of this approximation shall be [b]implementation-defined[/b].
Annex E. E9. The accuracy of the approximation of the result of the real operations and functions to the mathematical result is [b]implementation-defined[/b].

Juan Díaz

2019-05-26 20:45

reporter   ~0116423

I think one thing is the specification for the transfer functions and another is the accuracy, the former being a rule that must be followed and the latter being dependent on the implementation.
By the way, I'm pretty sure Turbo Pascal would also round numbers that were exactly halfway between two whole numbers to the whole number with the greatest absolute magnitude.
Thus, when fixing this issue not only FPC's compliance with ISO 7185:1990 will increase but also its compatibility with Turbo Pascal.

Florian

2019-05-26 20:56

administrator   ~0116424

@Juan: In TP it depends on the real type/instruction set used:

program TestRoundFunction(output);

var
  d : double;

begin
  writeln('round(12.5) = ', round(12.5));
  writeln('round(-4.5) = ', round(-4.5));
  d:=12.5;
  writeln('round(d) = ', round(d))
end.

compiled with $N+:

round(12.5) = 13
round(-4.5) = -5
round(r) = 12


compiled without $N+:

round(12.5) = 13
round(-4.5) = -5
round(r) = 13

So the software implementation does it like the ISO standard, if x87 instructions are used, TP uses just the FPU.

Marcin Wiazowski

2019-05-26 21:08

reporter   ~0116425

Same observations here.

As Serge wrote, Round() is strictly defined by ISO7185, while the other calculations are implementation-defined.

When using Turbo Pascal:



{$N-} <==== use software for calculations
var
  I: Integer;
begin
  for I := -5 to 4 do
    Writeln(I+0.5:1:1,' -> ',Round(I+0.5));
end.

Output:
  -4.5 -> -5
  -3.5 -> -4
  -2.5 -> -3
  -1.5 -> -2
  -0.5 -> -1
  0.5 -> 1
  1.5 -> 2
  2.5 -> 3
  3.5 -> 4
  4.5 -> 5



{$N+} <==== use hardware for calculations
... same code here...

Output:
  -4.5 -> -4
  -3.5 -> -4
  -2.5 -> -2
  -1.5 -> -2
  -0.5 -> 0
  0.5 -> 0
  1.5 -> 2
  2.5 -> 2
  3.5 -> 4
  4.5 -> 4



So, in ISO mode, it's enough to:
- redefine the Round() function,
- force the compiler to use this redefined Round(), when calculating constant values at compilation time.

Steven Pietrobon

2019-08-30 10:55

reporter   ~0117874

Last edited: 2019-08-30 11:40

View 3 revisions

I also raised this issue in this thread:

https://forum.lazarus.freepascal.org/index.php/topic,46572.0.html

It would be good if the developers could allow {$MODE TP} and {$MODE ISO} to implement the Turbo Pascal and ISO 7185 round function by default. My reason for this is below.

With regard to the $N switch, that is not used in ISO 7185. Thus, if {$MODE ISO} is selected round must conform to the standard and not use the processor round (whatever that may be). Note that an integer is returned.

http://pascal-central.com/docs/iso7185.pdf

round(x)
From the expression x that shall be of real-type, this function shall return a result of integer type. If x is positive or zero, round(x) shall be equivalent to trunc(x+0 .5) ; otherwise, round(x) shall be equivalent to trunc(x-0.5) . It shall be an error if such a value does not exist.

For Turbo Pascal, the default is {$N-}. That is, the math coprocessor is not used unless the {$N+} switch is used. Thus round must by default use the Turbo Pascal round definition if {$MODE TP} is selected.

http://turbopascal.org/files/Turbo_Pascal_Version_7.0_Programmers_Reference_1992.pdf

Numeric coprocessor: Switch
Syntax: {$N+} or {$N-}
Default: { $N - }
Type: Global
Remarks: The $N directive switches between the two different models of floating-point code generation supported by Turbo Pascal. In the {$N-} state, code is generated to perform all real-type calculations in software by calling run-time library routines. In the {$N+} state, code is generated to perform all real-type calculations using the 80x87 numeric coprocessor.

Here is how round is defined for Turbo Pascal 4.0 and above. Note that the return is a longint. For Turbo Pascal 3.0 and below the return is an integer, the same as in the ISO standard.

Round function: System
Purpose: Rounds a real-type value to an integer-type value.
Declaration function: Round (X: Real): Longint;
Remarks: X is a real-type expression. Round returns a Longint value that is the value of X rounded to the nearest whole number. If X is exactly halfway between two whole numbers, the result is the number with the greatest absolute magnitude. A run-time error occurs if the rounded value of X is not within the Longint range.

The question is what is the behaviour of round when {$N+} is used with {$MODE TP}? The documentation I have does not say that the round function is affected by the coprocessor. However, the $N documentation says that all real-type calculations are performed using the coprocessor. I'm not really clear what should happen, since we are going from a real to a longint.

In TP 4.0 the coprocessor is assumed to be an 8087, 80287 or 80387, a chip separate to the main processor. In TP 7.0 they specify an 80x87 coprocessor. I think the only way to answer this correctly is for someone to fire up a very old PC with a coprocessor and see what happens when running Turbo Pascal and a program with {$N+}.

Steven Pietrobon

2019-08-30 12:31

reporter   ~0117875

Last edited: 2019-08-30 12:34

View 2 revisions

For reference the 8087, 80287 and 80387 have a two bit input that is used to control rounding. None of them conform the ISO or TP standard, with the default being banker's rounding.

http://bitsavers.trailing-edge.com/components/intel/80386/231917-001_80387_Programmers_Reference_Manual_1987.pdf



round.gif (24,207 bytes)
round.gif (24,207 bytes)

Steven Pietrobon

2019-08-31 06:38

reporter   ~0117889

Last edited: 2019-08-31 06:44

View 2 revisions

Thinking about this some more, I think the problem stems from the limited number of round options that Intel originally implemented and that has passed on down through the ages. If we ignore nonsensical functions which round or truncate integer values to other values, then I come up with six different round functions and four truncate functions.

With round, the normal operation is to round the real to the nearest integer. The different versions come about in how mid way (0.5) values are handled. The options are

round up if positive or negative
round down if positive of negative
round up if positive and round down if negative (the ISO 7185 and Turbo Pascal method)
round down if positive and round up if negative
round to nearest even number (the only option available for Intel devices)
round to nearest odd number

For truncate, this just removes the non-integer part of the real. Here the options in what direction you round (up or down) if the non-integer part is non-zero.

round up if positive or negative (supported by Intel)
round down if positive of negative (supported by Intel)
round up if positive and round down if negative (means that only 0 will round to 0).
round down if positive and round up if negative (supported by Intel)

If we eliminate the last round method and third trunc method in the above, that would leave us eight schemes, which could have been supported with a three bit rounding code instead of the two bits that Intel used.

In any case, software developers should have known the limitations of the rounding function used by Intel, with only one option provided. The TP and ISO trunc function is supported by Intel and is selected using RC Field = 11 (the so called Chop toward 0). It would have been very easy to define round the same way that ISO defines round:

function round(x:real):longint;
begin{round}
  if x >= 0
     then round := trunc(x+0.5)
     else round := trunc(x-0.5);
end;{round}

This would have avoided any differences between the standard and what ever math coprocessor was used, regardless of the real type used. Nah! The past developers said sod this! We'll just use whatever round function is available, even if its different to the standard!

Issue History

Date Modified Username Field Change
2019-05-24 08:14 Juan Díaz New Issue
2019-05-24 08:14 Juan Díaz File Added: RoundFunctionTest.pas
2019-05-24 08:19 Juan Díaz Note Added: 0116387
2019-05-24 12:38 Marco van de Voort Note Added: 0116390
2019-05-24 15:09 Marco van de Voort Note Edited: 0116390 View Revisions
2019-05-24 22:06 Marco van de Voort Note Added: 0116398
2019-05-25 16:19 Marco van de Voort Note Edited: 0116398 View Revisions
2019-05-25 16:21 Marco van de Voort Note Edited: 0116398 View Revisions
2019-05-25 17:49 Marcin Wiazowski Note Added: 0116412
2019-05-26 12:09 Florian Note Added: 0116417
2019-05-26 15:04 Serge Anvarov Note Added: 0116419
2019-05-26 15:08 Florian Note Added: 0116420
2019-05-26 20:20 Serge Anvarov Note Added: 0116422
2019-05-26 20:45 Juan Díaz Note Added: 0116423
2019-05-26 20:56 Florian Note Added: 0116424
2019-05-26 21:08 Marcin Wiazowski Note Added: 0116425
2019-08-30 10:55 Steven Pietrobon Note Added: 0117874
2019-08-30 10:56 Steven Pietrobon Note Edited: 0117874 View Revisions
2019-08-30 11:40 Steven Pietrobon Note Edited: 0117874 View Revisions
2019-08-30 12:31 Steven Pietrobon File Added: round.gif
2019-08-30 12:31 Steven Pietrobon Note Added: 0117875
2019-08-30 12:34 Steven Pietrobon Note Edited: 0117875 View Revisions
2019-08-31 06:38 Steven Pietrobon Note Added: 0117889
2019-08-31 06:44 Steven Pietrobon Note Edited: 0117889 View Revisions
2019-10-20 23:12 Florian Assigned To => Florian
2019-10-20 23:12 Florian Status new => resolved
2019-10-20 23:12 Florian Resolution open => fixed
2019-10-20 23:12 Florian Fixed in Version => 3.3.1
2019-10-20 23:12 Florian Fixed in Revision => 43281
2019-10-20 23:12 Florian FPCTarget => -