View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0035626 | FPC | Compiler | public | 2019-05-24 08:14 | 2019-10-20 23:12 |
Reporter | Juan Díaz | Assigned To | Florian | ||
Priority | normal | Severity | minor | Reproducibility | have not tried |
Status | resolved | Resolution | fixed | ||
OS | Windows 7 | ||||
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(-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 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/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. |
|
@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://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). |
|
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 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]. |
|
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 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. |
|
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+}. |
|
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 |
|
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! |
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 | => - |