View Issue Details
ID  Project  Category  View Status  Date Submitted  Last Update 

0035626  FPC  Compiler  public  20190524 08:14  20191020 23:12 
Reporter  Juan Díaz  Assigned To  Florian  
Priority  normal  Severity  minor  Reproducibility  have not tried 
Status  resolved  Resolution  fixed  
Platform  OS  Windows 7  OS Version  
Product Version  Product Build  
Target Version  Fixed in Version  3.3.1  
Summary  0035626: 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  
Description  When 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(45) 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(x0.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 Reproduce  Just 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.  
Tags  No tags attached.  
Fixed in Revision  43281  
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. 

The FPC version I am using is 3.0.4 

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

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) 

About rounding modes: As can be found in https://en.wikipedia.org/wiki/Floatingpoint_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. 

@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. 

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

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

Because it depends on the specific implementation. Ibid 6.7.2.2 Arithmetic operators...The results of integertoreal 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]implementationdefined[/b]. Annex E. E9. The accuracy of the approximation of the result of the real operations and functions to the mathematical result is [b]implementationdefined[/b]. 

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. 

@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. 

Same observations here. As Serge wrote, Round() is strictly defined by ISO7185, while the other calculations are implementationdefined. 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. 

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://pascalcentral.com/docs/iso7185.pdf round(x) From the expression x that shall be of realtype, 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(x0.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 floatingpoint code generation supported by Turbo Pascal. In the {$N} state, code is generated to perform all realtype calculations in software by calling runtime library routines. In the {$N+} state, code is generated to perform all realtype 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 realtype value to an integertype value. Declaration function: Round (X: Real): Longint; Remarks: X is a realtype 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 runtime 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 realtype 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+}. 

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.trailingedge.com/components/intel/80386/231917001_80387_Programmers_Reference_Manual_1987.pdf 

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 noninteger part of the real. Here the options in what direction you round (up or down) if the noninteger part is nonzero. 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(x0.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! 
Date Modified  Username  Field  Change 

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