Project

General

Profile

Actions

Bug #8299

closed

Minor error in float parsing

Added by bobjalex (Bob Alexander) almost 11 years ago. Updated 4 months ago.

Status:
Closed
Target version:
-
ruby -v:
trunk
Backport:
[ruby-core:54473]
Tags:

Description

I encountered a float that either parses [slightly] differently (or converts to string differently) in Ruby than it does in Python or Java. This looks like a Ruby bug since the result "looks" incorrect.

It is easily reproduced by entering the magic number (-1.1505945E-5) into irb. It behaves the same in 2.0 and 1.9. I'm using Windows.

Below is an irb session that demonstrates. Also included are JRuby and Python trials that show better behavior.

This issue is not causing me any problems, but just in case someone there is interested in looking into it...

Bob

ruby -v
ruby 2.0.0p0 (2013-02-24) [i386-mingw32]

irb
irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> -1.1505945E-5
=> -1.1505945000000001e-05

ruby19 -v
ruby 1.9.3p392 (2013-02-22) [i386-mingw32]

irb19
irb(main):001:0> RUBY_VERSION
=> "1.9.3"
irb(main):002:0> -1.1505945E-5
=> -1.1505945000000001e-05
irb(main):002:0>

jirb
irb(main):001:0> -1.1505945E-5
=> -1.1505945e-05

python
Python 2.7.4rc1 (default, Mar 24 2013, 14:34:32) [MSC v.1500 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.

-1.1505945E-5
-1.1505945e-05
repr(-1.1505945E-5)
'-1.1505945e-05'


Related issues 1 (0 open1 closed)

Related to Ruby master - Bug #8358: TestSprintf#test_float test failureClosedluislavena (Luis Lavena)05/02/2013Actions

Updated by nobu (Nobuyoshi Nakada) almost 11 years ago

  • Status changed from Open to Rejected

Just -1.1505945e-5 is not accurately representable in binary format.

Updated by marcandre (Marc-Andre Lafortune) almost 11 years ago

  • Status changed from Rejected to Open
  • Priority changed from 3 to Normal

Actually, this is a bug.

  1. Float#to_s must be minimal while round-tripping. This is clearly not the case in the given examples.

  2. Float#to_s must be platform independent

On OS X, I get the correct string conversion.
$ ruby -v -e 'p(-1.1505945E-5)'
ruby 1.9.3p392 (2013-02-22 revision 39386) [x86_64-darwin10.8.0]
-1.1505945e-05
ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin10.8.0]
-1.1505945e-05

Updated by naruse (Yui NARUSE) almost 11 years ago

  • Status changed from Open to Rejected

marcandre (Marc-Andre Lafortune) wrote:

Actually, this is a bug.

  1. Float#to_s must be minimal while round-tripping. This is clearly not the case in the given examples.

If the given value cannot be represented by binary, it won't be minimal while round-tripping.

  1. Float#to_s must be platform independent

double calculation itself is platform dependent.
see also x87 and if you want ruby for x86 to behave as x64 specify -msse2 -mfpmath=sse for gcc.
http://en.wikipedia.org/wiki/X87

Updated by Student (Nathan Zook) almost 11 years ago

Double calculations are not in themselves platform dependent except in the IEEE-754 boundary cases. These boundaries are extremely narrow, such as half way from min normal to max denormal. If there is a platform dependence, then the libraries involved must be either setting the hardware control bits differently, or computing the values differently. The value in question is no where near the double precision boundary, so there must be a difference in either how the libraries are doing the conversion or in how the rounding bits are being set.

This may be worth investigation to see the source of the difference.

Updated by naruse (Yui NARUSE) almost 11 years ago

Student (Nathan Zook) wrote:

Double calculations are not in themselves platform dependent except in the IEEE-754 boundary cases. These boundaries are extremely narrow, such as half way from min normal to max denormal. If there is a platform dependence, then the libraries involved must be either setting the hardware control bits differently, or computing the values differently. The value in question is no where near the double precision boundary, so there must be a difference in either how the libraries are doing the conversion or in how the rounding bits are being set.

This may be worth investigation to see the source of the difference.

Learn 80bit precision of x87's FPU as I wrote in [ruby-core:54485].

Moreover there's non IEEE-754 fp for example VAX.

Actions #6

Updated by nagachika (Tomoyuki Chikanaga) almost 11 years ago

  • Tracker changed from Backport to Bug
  • Project changed from Backport200 to Ruby master

Updated by marcandre (Marc-Andre Lafortune) almost 11 years ago

  • Status changed from Rejected to Open
  • ruby -v set to trunk

naruse (Yui NARUSE) wrote:

  1. Float#to_s must be minimal while round-tripping. This is clearly not the case in the given examples.

If the given value cannot be represented by binary, it won't be minimal while round-tripping.

Sorry, I do not understand what you said. What I meant to say is ([ruby-core:30145]):

For any float f, the two following conditions should hold:
(1) f.to_s.to_f == f (round trips)
(2) f.to_s.chop.to_f != f (minimal)

The second condition is a simplification; if the string representation is in scientific notation, than the character to remove would be the one just before the "e". Also, if the string representation ends with ".0", then it is minimal.

  1. Float#to_s must be platform independent

double calculation itself is platform dependent.

Sorry, I should have specified "...on IEEE 754 platforms", which is the case for mgwin32, right?

Moreover there's non IEEE-754 fp for example VAX.

We don't support VAX, as you wrote yourself in [ruby-core:42077], so how is this relevant exactly?

Updated by marcandre (Marc-Andre Lafortune) almost 11 years ago

Can anyone on [i386-mingw32] report which of the two following is false:

-1.1505945e-05 != -1.1505945000000001e-05
"\xBE\xE8!5\n\x1D\x17A".unpack('G').first.to_s == "-1.1505945e-05"

If the first is false, then parsing is buggy and I presume you get:

f = "\xBE\xE8!5\n\x1D\x17A".unpack('G').first
f.to_s.to_f == f # => false, should be true for any float f (not NaN or inf).

If the second is false, then conversion to string is buggy and I imagine you will get:

f = -1.1505945e-05
g = -1.1505945000000001e-05
f == g # => false
f.to_s == g.to_s # => true (should be consistent for any floats f & g (not NaN))

Updated by naruse (Yui NARUSE) almost 11 years ago

I wrote this is because of x87.
Did you read it?

marcandre (Marc-Andre Lafortune) wrote:

naruse (Yui NARUSE) wrote:

  1. Float#to_s must be minimal while round-tripping. This is clearly not the case in the given examples.

If the given value cannot be represented by binary, it won't be minimal while round-tripping.

Sorry, I do not understand what you said. What I meant to say is ([ruby-core:30145]):

For any float f, the two following conditions should hold:
(1) f.to_s.to_f == f (round trips)

Yeah, unless you transfer the result of to_s to another environment.

(2) f.to_s.chop.to_f != f (minimal)

The second condition is a simplification; if the string representation is in scientific notation, than the character to remove would be the one just before the "e". Also, if the string representation ends with ".0", then it is minimal.

The same as above.

  1. Float#to_s must be platform independent

double calculation itself is platform dependent.

Sorry, I should have specified "...on IEEE 754 platforms", which is the case for mgwin32, right?

Moreover there's non IEEE-754 fp for example VAX.

We don't support VAX, as you wrote yourself in [ruby-core:42077], so how is this relevant exactly?

I had written VAX but the main subject of here is x87 FPU.

On 32bit IA (yeah, this is also reproducible on 32bit Linux), program usually calculates floating point numbers with x87 FPU.
x87 FPU stores and calculates floating numbers in 80bit on registers, even if it is stored in 64bit on memory.
Therefore x87's result may differ from modern FPU's result.

For example -1.1505945e-05,
On modern FPU, it is stored/expressed as -0x1.821350a1d1742p-17, and this is converted to -1.1505945E-05 in decimal.
But on x87 FPU this is expressed as -0x1.821350a1d1741800p-17, and converted to -1.1505945000000001E-05 in decimal.
Yeah, this can be avoided by storing it to memory by specifying volatile, but this costs time and make code complex.
On gcc, specifying -ffloat-store or -fexcess-precision=standard (gcc 4.5) is more simple, but this is also slows down.
On modern x86 CPU it has SSE, so specify -msse2 -mfpmath=sse is easy way.

Note that Xeon Phi has x87 and 512bit SMID, but doesn't have SSE

Updated by david_macmahon (David MacMahon) almost 11 years ago

On Apr 21, 2013, at 7:18 PM, naruse (Yui NARUSE) wrote:

marcandre (Marc-Andre Lafortune) wrote:

For any float f, the two following conditions should hold:
(1) f.to_s.to_f == f (round trips)

Yeah, unless you transfer the result of to_s to another environment.

Are you saying that round tripping is only valid on "the native architecture's double-precision floating point representation" (quote from Float's RDoc)? That would make sense to me.

x87 FPU stores and calculates floating numbers in 80bit on registers, even if it is stored in 64bit on memory.

Does Float on x87 systems use 80-bits to store its value? IOW, is "the native architecture's double-precision floating point representation" 80 bits on x87?Asking (almost) the same thing a third way, what is "sizeof(double)" on a system with an x87 FPU?

Therefore x87's result may differ from modern FPU's result.

I agree that the results of floating point operations on x87 may differ from the same operations on modern FPU's given the same input operands due to 80-bit intermediate values on x87, but does this affect the parsing of Float literals?

Do Float constants like EPSILON, MIN, and MAX differ between x87 systems and modern FPUs?

Does "x87" == "non-IEEE-754" and "modern FPU" == "IEEE-754"?

I find this topic fascinating and am just trying to understand the subtle semantics involved here!

Thanks,
Dave

Updated by naruse (Yui NARUSE) almost 11 years ago

2013年4月22日月曜日 David MacMahon :

On Apr 21, 2013, at 7:18 PM, naruse (Yui NARUSE) wrote:

marcandre (Marc-Andre Lafortune) wrote:

For any float f, the two following conditions should hold:
(1) f.to_s.to_f == f (round trips)

Yeah, unless you transfer the result of to_s to another environment.

Are you saying that round tripping is only valid on "the native
architecture's double-precision floating point representation" (quote from
Float's RDoc)? That would make sense to me.

Yes.

x87 FPU stores and calculates floating numbers in 80bit on registers,
even if it is stored in 64bit on memory.

Does Float on x87 systems use 80-bits to store its value? IOW, is "the
native architecture's double-precision floating point representation" 80
bits on x87?Asking (almost) the same thing a third way, what is
"sizeof(double)" on a system with an x87 FPU?

Therefore x87's result may differ from modern FPU's result.

I agree that the results of floating point operations on x87 may differ
from the same operations on modern FPU's given the same input operands due
to 80-bit intermediate values on x87, but does this affect the parsing of
Float literals?

Do Float constants like EPSILON, MIN, and MAX differ between x87 systems
and modern FPUs?

Does "x87" == "non-IEEE-754" and "modern FPU" == "IEEE-754"?

I find this topic fascinating and am just trying to understand the subtle
semantics involved here!

Thanks,
Dave

--
NARUSE, Yui

Updated by naruse (Yui NARUSE) almost 11 years ago

I wrongly sent previous mail.

2013年4月22日月曜日 David MacMahon :

On Apr 21, 2013, at 7:18 PM, naruse (Yui NARUSE) wrote:

marcandre (Marc-Andre Lafortune) wrote:

For any float f, the two following conditions should hold:
(1) f.to_s.to_f == f (round trips)

Yeah, unless you transfer the result of to_s to another environment.

Are you saying that round tripping is only valid on "the native
architecture's double-precision floating point representation" (quote from
Float's RDoc)? That would make sense to me.

Yes, only portable on the same architecture.

x87 FPU stores and calculates floating numbers in 80bit on registers,
even if it is stored in 64bit on memory.

Does Float on x87 systems use 80-bits to store its value? IOW, is "the
native architecture's double-precision floating point representation" 80
bits on x87?Asking (almost) the same thing a third way, what is
"sizeof(double)" on a system with an x87 FPU?

x87 stores value 80bit on registers and arithmetics, 64bit on memory.
So sizeof(double)=8.

Therefore x87's result may differ from modern FPU's result.

I agree that the results of floating point operations on x87 may differ
from the same operations on modern FPU's given the same input operands due
to 80-bit intermediate values on x87, but does this affect the parsing of
Float literals?

Parsing itself is of course doesn't affect.
Decimal-binary conversion affect it

Do Float constants like EPSILON, MIN, and MAX differ between x87 systems
and modern FPUs?

I'll show those constants on my 32bit Linux.

Does "x87" == "non-IEEE-754" and "modern FPU" == "IEEE-754"?

I think what you think is correct.
Why I didn't write so is because as far as I understand, x87 complies IEEE
754 standard; the standard allows to store and calculate float64 in 80bit.

--
NARUSE, Yui

Updated by Student (Nathan Zook) almost 11 years ago

The story on x87 is not precisely as you say. Yes, everything is stored in 80 bits. However, you can set precision as well as rounding mode. If the precision is set to DP, the mantissa is rounded to 53 bits, just like double precision. Again, things get a little weird in the denormal range, but this does NOT affect the value in question.

What I don't know is if the standard specifies that floats are stored DP or max platform. Max platform is subject to some oddness when you cross platforms but this does not affect round-tripping, which is the reported issue, which is happing all in a single process.

Updated by naruse (Yui NARUSE) almost 11 years ago

(2013/04/22 12:43), David MacMahon wrote:

Do Float constants like EPSILON, MIN, and MAX differ between x87 systems and modern FPUs?

On i486-linux,
DBL_EPSILON: 2.22045e-16
DBL_MAX: 1.79769e+308
DBL_MIN: 2.22507e-308
DBL_MANT_DIG : 53
DBL_EXP_MAX: 1024,
DBL_EXP_MIN: -1021

They are same as x64.

--
NARUSE, Yui

Updated by phasis68 (Heesob Park) almost 11 years ago

I guess this is a compiler specific issue.

I tested some cases with different compilers with Ruby 1.9.3-p392 on the same machine.
gcc 4.5.2 built version(rubyinstaller version) shows not disired result.
But gcc 4.7.2 and MSVC v16.0 built version shows a correct result.

C:\work\ruby-1.9.3-p392-mg5>.\miniruby -ve 'a=-1.1505945E-5;puts(a,"%0.20g"%a)'
ruby 1.9.3p392 (2013-02-22 revision 39386) [i386-mingw32]
-1.1505945000000001e-05
-1.1505945000000000847e-05

C:\work\ruby-1.9.3-p392-mg7>.\miniruby -ve 'a=-1.1505945E-5;puts(a,"%0.20g"%a)'
ruby 1.9.3p392 (2013-02-22 revision 39386) [i386-mingw32]
-1.1505945e-05
-1.1505944999999999153e-05

C:\work\ruby-1.9.3-p392-ms>.\miniruby -ve 'a=-1.1505945E-5;puts(a,"%0.20g"%a)'
ruby 1.9.3p392 (2013-02-22 revision 39386) [i386-mswin32_100]
-1.1505945e-05
-1.1505944999999999153e-05

I found the main cause is the different result of rounded quotient operation.
Here is the test case.

C:\work>type foo.c
#include <stdio.h>
void main() {
        double a = 11505945.0;
        double b = 1000000000000.0;
        a /= b;
        printf("a = %0.20g\n",a);
}

C:\work>gcc --version
gcc (tdm-1) 4.5.2
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   
C:\work>gcc foo.c -o foo    
C:\work>foo
a = 1.1505945000000001e-005


C:\work>gcc --version
gcc (rubenvb-4.7.2-release) 4.7.2
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
C:\work>gcc foo.c -o foo
C:\work>foo
a = 1.1505944999999999e-005

C:\work>cl foo.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.
foo.c
Microsoft (R) Incremental Linker Version 10.00.40219.01
Copyright (C) Microsoft Corporation.  All rights reserved.
/out:foo.exe
foo.obj
C:\work>foo
a = 1.1505944999999999e-005

Updated by phasis68 (Heesob Park) almost 11 years ago

I found the issue is due to the difference of the floating point precision.

Here is a patch for this issue:

diff --git a/numeric.c b/numeric.c.new
index 97ba104..c548c0f 100644
--- a/numeric.c
+++ b/numeric.c.new
@@ -3767,6 +3767,9 @@ Init_Numeric(void)
     _control87(MCW_EM, MCW_EM);
     _control87(_control87(0,0),0x1FFF);
 #endif
+#if defined(__MINGW32__)
+    _control87(_PC_53, _MCW_PC);
+#endif
     id_coerce = rb_intern("coerce");
     id_to_i = rb_intern("to_i");
     id_eq = rb_intern("=="); 

Updated by david_macmahon (David MacMahon) almost 11 years ago

Nice work tracking this down!

I don't understand how it fixes the compiler specific aspect of the problem you found where gcc 4.5.2 on MinGW had the problem but gcc 4.7.2 on MinGW did not. Does gcc 4.7.2 on MinGW already automatically do whatever "_control87(_PC_53, _MCW_PC)" does, but gcc 4,5,2 on MinGW does not automatically do it so it needs to be done explicitly?

Is the MINGW32 macro enough of a check? I wonder if it could enable the _control87 call on system where it is not present.

Dave

On Apr 24, 2013, at 12:05 AM, phasis68 (Heesob Park) wrote:

Issue #8299 has been updated by phasis68 (Heesob Park).

I found the issue is due to the difference of the floating point precision.

Here is a patch for this issue:

diff --git a/numeric.c b/numeric.c.new
index 97ba104..c548c0f 100644
--- a/numeric.c
+++ b/numeric.c.new
@@ -3767,6 +3767,9 @@ Init_Numeric(void)
_control87(MCW_EM, MCW_EM);
_control87(_control87(0,0),0x1FFF);
#endif
+#if defined(MINGW32)

  • _control87(_PC_53, _MCW_PC);
    +#endif
    id_coerce = rb_intern("coerce");
    id_to_i = rb_intern("to_i");
    id_eq = rb_intern("==");

Bug #8299: Minor error in float parsing
https://bugs.ruby-lang.org/issues/8299#change-38861

Author: bobjalex (Bob Alexander)
Status: Open
Priority: Normal
Assignee:
Category:
Target version:
ruby -v: trunk
Backport:

I encountered a float that either parses [slightly] differently (or converts to string differently) in Ruby than it does in Python or Java. This looks like a Ruby bug since the result "looks" incorrect.

It is easily reproduced by entering the magic number (-1.1505945E-5) into irb. It behaves the same in 2.0 and 1.9. I'm using Windows.

Below is an irb session that demonstrates. Also included are JRuby and Python trials that show better behavior.

This issue is not causing me any problems, but just in case someone there is interested in looking into it...

Bob

ruby -v
ruby 2.0.0p0 (2013-02-24) [i386-mingw32]

irb
irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> -1.1505945E-5
=> -1.1505945000000001e-05

ruby19 -v
ruby 1.9.3p392 (2013-02-22) [i386-mingw32]

irb19
irb(main):001:0> RUBY_VERSION
=> "1.9.3"
irb(main):002:0> -1.1505945E-5
=> -1.1505945000000001e-05
irb(main):002:0>

jirb
irb(main):001:0> -1.1505945E-5
=> -1.1505945e-05

python
Python 2.7.4rc1 (default, Mar 24 2013, 14:34:32) [MSC v.1500 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.

-1.1505945E-5
-1.1505945e-05
repr(-1.1505945E-5)
'-1.1505945e-05'

--
http://bugs.ruby-lang.org/

Updated by naruse (Yui NARUSE) almost 11 years ago

phasis68 (Heesob Park) wrote:

I found the issue is due to the difference of the floating point precision.

Here is a patch for this issue:

diff --git a/numeric.c b/numeric.c.new
index 97ba104..c548c0f 100644
--- a/numeric.c
+++ b/numeric.c.new
@@ -3767,6 +3767,9 @@ Init_Numeric(void)
     _control87(MCW_EM, MCW_EM);
     _control87(_control87(0,0),0x1FFF);
 #endif
+#if defined(__MINGW32__)
+    _control87(_PC_53, _MCW_PC);
+#endif
     id_coerce = rb_intern("coerce");
     id_to_i = rb_intern("to_i");
     id_eq = rb_intern("==");

It specify fraction as 53 bit (other IEEE 754 FPU's 52 bit with economized form), but its internal exponent is still 15 bit.
For example following result should be still different (I don't tested on mingw32).

[0x0008008000000000,0x3ff0000000000001].pack("Q2").unpack("d2").inject(&:*)

Therefore use SSE2 rather than such workaround.

Updated by phasis68 (Heesob Park) almost 11 years ago

2013/4/25 David MacMahon

Nice work tracking this down!

I don't understand how it fixes the compiler specific aspect of the
problem you found where gcc 4.5.2 on MinGW had the problem but gcc 4.7.2 on
MinGW did not. Does gcc 4.7.2 on MinGW already automatically do whatever
"_control87(_PC_53, _MCW_PC)" does, but gcc 4,5,2 on MinGW does not
automatically do it so it needs to be done explicitly?

The console application built with mingw32 compiler calls _fpreset
funciton at startup.
The _fpreset function is defined in MSVCRT.dll which set FP default
precision to 53 bit mantissa.

But in mingwrt-3.18 library which bundled in mingw32 4.7.2 redefined
_fpreset function to change FP default precision from 53 to 64-bit
mantissa.
Actually, the "_control87(_PC_53, _MCW_PC)" means lowering precision
from 64 to 53-bit mantissa.

BTW, why we should consider FP precision?
When converting string value to double value, ruby calls ruby_strtod
function instead of native strtod function.
The ruby_strtod function is based on David M. Gay's netlib library
which requires double-precision (53-bit) rounding precision.

Is the MINGW32 macro enough of a check? I wonder if it could enable
the _control87 call on system where it is not present.

Ruby 1.9.x requires Windows 2000 or later OS which requires Pentium CPU.
And most x86 processors since the Intel 80486 have had x87
instructions implemented in the main CPU.
I cannot imagine a system with Windows 2000 and not present x87 instructions.

Regards,
Park Heesob

Updated by phasis68 (Heesob Park) almost 11 years ago

2013/4/25 naruse (Yui NARUSE) :

Issue #8299 has been updated by naruse (Yui NARUSE).

phasis68 (Heesob Park) wrote:

I found the issue is due to the difference of the floating point precision.

Here is a patch for this issue:

diff --git a/numeric.c b/numeric.c.new
index 97ba104..c548c0f 100644
--- a/numeric.c
+++ b/numeric.c.new
@@ -3767,6 +3767,9 @@ Init_Numeric(void)
     _control87(MCW_EM, MCW_EM);
     _control87(_control87(0,0),0x1FFF);
 #endif
+#if defined(__MINGW32__)
+    _control87(_PC_53, _MCW_PC);
+#endif
     id_coerce = rb_intern("coerce");
     id_to_i = rb_intern("to_i");
     id_eq = rb_intern("==");

It specify fraction as 53 bit (other IEEE 754 FPU's 52 bit with economized form), but its internal exponent is still 15 bit.
For example following result should be still different (I don't tested on mingw32).

[0x0008008000000000,0x3ff0000000000001].pack("Q2").unpack("d2").inject(&:*)

Therefore use SSE2 rather than such workaround.

I'm not sure why you think SSE2 can fix this issue.

The ruby_strtod function used in converting string value to double
value requires double-precision (53-bit) rounding precision but
mingw32 gcc 4.5.2 have default 64-bit precision which higher than
other compilers.

So the patch lowers precision from 64 bit to 53 bit.

Regards,
Park Heesob

Updated by naruse (Yui NARUSE) almost 11 years ago

phasis68 (Heesob Park) wrote:

2013/4/25 naruse (Yui NARUSE) :

Therefore use SSE2 rather than such workaround.

I'm not sure why you think SSE2 can fix this issue.

The ruby_strtod function used in converting string value to double
value requires double-precision (53-bit) rounding precision but
mingw32 gcc 4.5.2 have default 64-bit precision which higher than
other compilers.

So the patch lowers precision from 64 bit to 53 bit.

double arithmetics with SSE2 is double-precision.
see also gcc's -mfpmath=sse option
http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/i386-and-x86_002d64-Options.html#index-march-959

Updated by david_macmahon (David MacMahon) almost 11 years ago

On Apr 24, 2013, at 7:03 PM, Heesob Park wrote:

2013/4/25 David MacMahon

Nice work tracking this down!

I don't understand how it fixes the compiler specific aspect of the
problem you found where gcc 4.5.2 on MinGW had the problem but gcc 4.7.2 on
MinGW did not. Does gcc 4.7.2 on MinGW already automatically do whatever
"_control87(_PC_53, _MCW_PC)" does, but gcc 4,5,2 on MinGW does not
automatically do it so it needs to be done explicitly?

The console application built with mingw32 compiler calls _fpreset
funciton at startup.
The _fpreset function is defined in MSVCRT.dll which set FP default
precision to 53 bit mantissa.

But in mingwrt-3.18 library which bundled in mingw32 4.7.2 redefined
_fpreset function to change FP default precision from 53 to 64-bit
mantissa.
Actually, the "_control87(_PC_53, _MCW_PC)" means lowering precision
from 64 to 53-bit mantissa.

Thanks for the information, but I'm still confused. The above sounds like gcc 4.7.2 uses a higher precision (64 bit mantissa) than strtod expects (53 bit mantissa), yet in an earlier message you wrote:

gcc 4.5.2 built version(rubyinstaller version) shows not disired result.
But gcc 4.7.2 and MSVC v16.0 built version shows a correct result.

Not a big problem, I'm just trying to understand things. Am I misreading/misinterpreting things?

BTW, why we should consider FP precision?
When converting string value to double value, ruby calls ruby_strtod
function instead of native strtod function.
The ruby_strtod function is based on David M. Gay's netlib library
which requires double-precision (53-bit) rounding precision.

This raises the question of whether Ruby really supports "the native architecture's double-precision floating point representation" as described on Float's RDoc. Does strtod work properly on systems that use something other than IEEE 754 binary64 (e.g. VAX)?

Is the MINGW32 macro enough of a check? I wonder if it could enable
the _control87 call on system where it is not present.

Ruby 1.9.x requires Windows 2000 or later OS which requires Pentium CPU.
And most x86 processors since the Intel 80486 have had x87
instructions implemented in the main CPU.
I cannot imagine a system with Windows 2000 and not present x87 instructions.

There is Windows RT...

http://en.wikipedia.org/wiki/Windows_RT

...but I don't know whether Ruby runs on it. :-)

Dave

Updated by phasis68 (Heesob Park) almost 11 years ago

2013/4/25 David MacMahon :

On Apr 24, 2013, at 7:03 PM, Heesob Park wrote:

2013/4/25 David MacMahon

Nice work tracking this down!

I don't understand how it fixes the compiler specific aspect of the
problem you found where gcc 4.5.2 on MinGW had the problem but gcc 4.7.2 on
MinGW did not. Does gcc 4.7.2 on MinGW already automatically do whatever
"_control87(_PC_53, _MCW_PC)" does, but gcc 4,5,2 on MinGW does not
automatically do it so it needs to be done explicitly?

The console application built with mingw32 compiler calls _fpreset
funciton at startup.
The _fpreset function is defined in MSVCRT.dll which set FP default
precision to 53 bit mantissa.

But in mingwrt-3.18 library which bundled in mingw32 4.7.2 redefined
_fpreset function to change FP default precision from 53 to 64-bit
mantissa.
Actually, the "_control87(_PC_53, _MCW_PC)" means lowering precision
from 64 to 53-bit mantissa.

Thanks for the information, but I'm still confused. The above sounds like gcc 4.7.2 uses a higher precision (64 bit mantissa) than strtod expects (53 bit mantissa), yet in an earlier message you wrote:

The higher precision produce the undesired result in the magic number
(-1.1505945E-5).
-1.1505945000000001e-05 instead of -1.1505945e-05.

gcc 4.5.2 built version(rubyinstaller version) shows not disired result.
But gcc 4.7.2 and MSVC v16.0 built version shows a correct result.

Not a big problem, I'm just trying to understand things. Am I misreading/misinterpreting things?

I just mentioned the issue is occurred on mingw32 gcc 4.5.2 compiler
due to the excess precision.

BTW, why we should consider FP precision?
When converting string value to double value, ruby calls ruby_strtod
function instead of native strtod function.
The ruby_strtod function is based on David M. Gay's netlib library
which requires double-precision (53-bit) rounding precision.

This raises the question of whether Ruby really supports "the native architecture's double-precision floating point representation" as described on Float's RDoc. Does strtod work properly on systems that use something other than IEEE 754 binary64 (e.g. VAX)?

I have no idea why ruby call David M. Gay's strtod instead of native
strtod funciton.

Is the MINGW32 macro enough of a check? I wonder if it could enable
the _control87 call on system where it is not present.

Ruby 1.9.x requires Windows 2000 or later OS which requires Pentium CPU.
And most x86 processors since the Intel 80486 have had x87
instructions implemented in the main CPU.
I cannot imagine a system with Windows 2000 and not present x87 instructions.

There is Windows RT...

http://en.wikipedia.org/wiki/Windows_RT

...but I don't know whether Ruby runs on it. :-)

The patch can be more strict like this:
#if defined(MINGW32) && defined(_M_IX86)
_control87(_PC_53, _MCW_PC);
#endif

Regards,
Park Heesob

Updated by phasis68 (Heesob Park) almost 11 years ago

2013/4/25 naruse (Yui NARUSE) :

Issue #8299 has been updated by naruse (Yui NARUSE).

phasis68 (Heesob Park) wrote:

2013/4/25 naruse (Yui NARUSE) :

Therefore use SSE2 rather than such workaround.

I'm not sure why you think SSE2 can fix this issue.

The ruby_strtod function used in converting string value to double
value requires double-precision (53-bit) rounding precision but
mingw32 gcc 4.5.2 have default 64-bit precision which higher than
other compilers.

So the patch lowers precision from 64 bit to 53 bit.

double arithmetics with SSE2 is double-precision.
see also gcc's -mfpmath=sse option
http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/i386-and-x86_002d64-Options.html#index-march-959

I agree that SSE2 is the better solution for modern OS and modern compiler.

But SSE2 has more restrictions than x87.
SSE2 is not supported on the following environment.
Microsoft Visual C++ Compiler prior to Visual Studio .NET 2003.
AMD CPUs prior to Athlon 64, including all Socket A-based CPUs
Intel CPUs prior to Pentium 4

Updated by naruse (Yui NARUSE) almost 11 years ago

phasis68 (Heesob Park) wrote:

2013/4/25 naruse (Yui NARUSE) :

Issue #8299 has been updated by naruse (Yui NARUSE).

phasis68 (Heesob Park) wrote:

2013/4/25 naruse (Yui NARUSE) :

Therefore use SSE2 rather than such workaround.

I'm not sure why you think SSE2 can fix this issue.

The ruby_strtod function used in converting string value to double
value requires double-precision (53-bit) rounding precision but
mingw32 gcc 4.5.2 have default 64-bit precision which higher than
other compilers.

So the patch lowers precision from 64 bit to 53 bit.

double arithmetics with SSE2 is double-precision.
see also gcc's -mfpmath=sse option
http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/i386-and-x86_002d64-Options.html#index-march-959

I agree that SSE2 is the better solution for modern OS and modern compiler.

But SSE2 has more restrictions than x87.
SSE2 is not supported on the following environment.
Microsoft Visual C++ Compiler prior to Visual Studio .NET 2003.

Use newer compiler.

AMD CPUs prior to Athlon 64, including all Socket A-based CPUs
Intel CPUs prior to Pentium 4

Use -mfpmath=sse,387.
As I wrote before, if you want to get the same result with x87 FPU _control87(_PC_53, _MCW_PC) is not sufficient.
It needs to handle 15 bit exponent.
If you want to do that, it is as hard as implementing strictfp of Java on x87.
see also http://math.nist.gov/javanumerics/reports/jgfnwg-01.html
http://www.shudo.net/java-grandprix99/strictfp/#JGNWG98-2 (Japanese)

Updated by phasis68 (Heesob Park) almost 11 years ago

2013/4/25 naruse (Yui NARUSE) :

Issue #8299 has been updated by naruse (Yui NARUSE).

phasis68 (Heesob Park) wrote:

2013/4/25 naruse (Yui NARUSE) :

Issue #8299 has been updated by naruse (Yui NARUSE).

phasis68 (Heesob Park) wrote:

2013/4/25 naruse (Yui NARUSE) :

Therefore use SSE2 rather than such workaround.

I'm not sure why you think SSE2 can fix this issue.

The ruby_strtod function used in converting string value to double
value requires double-precision (53-bit) rounding precision but
mingw32 gcc 4.5.2 have default 64-bit precision which higher than
other compilers.

So the patch lowers precision from 64 bit to 53 bit.

double arithmetics with SSE2 is double-precision.
see also gcc's -mfpmath=sse option
http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/i386-and-x86_002d64-Options.html#index-march-959

I agree that SSE2 is the better solution for modern OS and modern compiler.

But SSE2 has more restrictions than x87.
SSE2 is not supported on the following environment.
Microsoft Visual C++ Compiler prior to Visual Studio .NET 2003.

Use newer compiler.
The requirement in win32/README.win32 need to be modified.

AMD CPUs prior to Athlon 64, including all Socket A-based CPUs
Intel CPUs prior to Pentium 4

Use -mfpmath=sse,387.
As I wrote before, if you want to get the same result with x87 FPU _control87(_PC_53, _MCW_PC) is not sufficient.
It needs to handle 15 bit exponent.

I don't want the same result with SSE2 and x87 FPU.
The 15 bit exponent is not a matter of this issue.
The point is that ruby_strtod function requires 53-bit precision and
mingw32 4.5.2 compiler is 64-bit precision unlike other windows
compiler which is 53-bit precision.
I confirmed that _control87(_PC_53, _MCW_PC) patch works fine with
ruby 1.9.3 mingw32 gcc 4.5.2 version.

If you want to do that, it is as hard as implementing strictfp of Java on x87.
see also http://math.nist.gov/javanumerics/reports/jgfnwg-01.html
http://www.shudo.net/java-grandprix99/strictfp/#JGNWG98-2 (Japanese)

You've gone too far from this issue.
The issuer wants the correct value of strtod function on the ruby
1.9.3 mingw32 version.
My patch is for mingw32 gcc compiler only.

Updated by naruse (Yui NARUSE) almost 11 years ago

  • Category set to platform/mingw
  • Status changed from Open to Assigned
  • Assignee set to nobu (Nobuyoshi Nakada)

phasis68 (Heesob Park) wrote:

2013/4/25 naruse (Yui NARUSE) :

Issue #8299 has been updated by naruse (Yui NARUSE).

phasis68 (Heesob Park) wrote:

2013/4/25 naruse (Yui NARUSE) :

Issue #8299 has been updated by naruse (Yui NARUSE).

phasis68 (Heesob Park) wrote:

2013/4/25 naruse (Yui NARUSE) :

Therefore use SSE2 rather than such workaround.

I'm not sure why you think SSE2 can fix this issue.

The ruby_strtod function used in converting string value to double
value requires double-precision (53-bit) rounding precision but
mingw32 gcc 4.5.2 have default 64-bit precision which higher than
other compilers.

So the patch lowers precision from 64 bit to 53 bit.

double arithmetics with SSE2 is double-precision.
see also gcc's -mfpmath=sse option
http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/i386-and-x86_002d64-Options.html#index-march-959

I agree that SSE2 is the better solution for modern OS and modern compiler.

But SSE2 has more restrictions than x87.
SSE2 is not supported on the following environment.
Microsoft Visual C++ Compiler prior to Visual Studio .NET 2003.

Use newer compiler.
The requirement in win32/README.win32 need to be modified.

There's already "strongly recommended VC++ 10 or later".
Note that VC++2003 or later has /fp:precise and it is default

AMD CPUs prior to Athlon 64, including all Socket A-based CPUs
Intel CPUs prior to Pentium 4

Use -mfpmath=sse,387.
As I wrote before, if you want to get the same result with x87 FPU _control87(_PC_53, _MCW_PC) is not sufficient.
It needs to handle 15 bit exponent.

I don't want the same result with SSE2 and x87 FPU.
The 15 bit exponent is not a matter of this issue.
The point is that ruby_strtod function requires 53-bit precision and
mingw32 4.5.2 compiler is 64-bit precision unlike other windows
compiler which is 53-bit precision.
I confirmed that _control87(_PC_53, _MCW_PC) patch works fine with
ruby 1.9.3 mingw32 gcc 4.5.2 version.

Your patch changes global state.
Change global or apply it to ruby_strtod is up to nobu, the mingw port maintainer.

If you want to do that, it is as hard as implementing strictfp of Java on x87.
see also http://math.nist.gov/javanumerics/reports/jgfnwg-01.html
http://www.shudo.net/java-grandprix99/strictfp/#JGNWG98-2 (Japanese)

You've gone too far from this issue.
The issuer wants the correct value of strtod function on the ruby
1.9.3 mingw32 version.
My patch is for mingw32 gcc compiler only.

Hmm, up to nobu.

Updated by JesseJohnson (Jesse Johnson) 4 months ago

I'm not able to replicate this in Ruby 2.6 or Ruby 3.2. Has this been fixed in the relevant compilers? If so can this bug be closed?

irb(main):001:0> RUBY_VERSION
=> "2.6.10"
irb(main):002:0> -1.1505945E-5
=> -1.1505945e-05
irb(main):001> RUBY_VERSION
=> "3.2.2"
irb(main):002> -1.1505945E-5
=> -1.1505945e-05

Updated by naruse (Yui NARUSE) 4 months ago

  • Status changed from Assigned to Closed

hdtoa has the rounding logic, but it doesn't work well on x87, but no one use x87 now. This issue is resolved.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0