Skip to content

Commit e69580d

Browse files
committed
Float precision
PullRequest: truffleruby/714
2 parents a186963 + 835464f commit e69580d

File tree

4 files changed

+223
-5
lines changed

4 files changed

+223
-5
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# 1.0 RC 15
22

3+
Bug fixes:
4+
5+
* Improved compatibility with MRI's `Float#to_s` formatting (#1626).
6+
37
New features:
48

59
* `Process.clock_getres` has been implemented.

spec/ruby/core/float/to_s_spec.rb

Lines changed: 195 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@
7575
-10000000000000000.0.to_s.should == "-1.0e+16"
7676
end
7777

78-
it "uses non-e format for a positive value with whole part having 17 significant figures" do
78+
it "uses e format for a positive value with whole part having 17 significant figures" do
7979
1000000000000000.0.to_s.should == "1.0e+15"
8080
end
8181

82-
it "uses non-e format for a negative value with whole part having 17 significant figures" do
82+
it "uses e format for a negative value with whole part having 17 significant figures" do
8383
-1000000000000000.0.to_s.should == "-1.0e+15"
8484
end
8585

@@ -95,6 +95,199 @@
9595
it "outputs the minimal, unique form to represent the value" do
9696
0.56.to_s.should == "0.56"
9797
end
98+
99+
describe "matches" do
100+
it "random examples in all ranges" do
101+
# 50.times do
102+
# bytes = (0...8).map { rand(256) }
103+
# string = bytes.pack('C8')
104+
# float = string.unpack('D').first
105+
# puts " #{bytes.pack('C8').inspect}.unpack1('D').to_s.should == #{float.to_s.inspect}"
106+
# end
107+
108+
"\x97\x15\xC1| \xF5\x19\xAD".unpack1('D').to_s.should == "-1.9910613439044092e-91"
109+
"\xBF\xF0\x14\xAD\xDF\x17q\xD1".unpack1('D').to_s.should == "-2.075408637901046e+84"
110+
"\xDF\xBD\xC0\x89\xDA\x1F&$".unpack1('D').to_s.should == "1.5219626883645564e-134"
111+
"|0<?a\xFB\xBFG".unpack1('D').to_s.should == "4.251130678455814e+37"
112+
"U\xEE*\xB7\xF1\xB8\xE7\x18".unpack1('D').to_s.should == "1.0648588700899858e-188"
113+
"\x15Y\xD1J\x80/7\xD0".unpack1('D').to_s.should == "-2.6847034291392176e+78"
114+
"\x1D\x1E\xD2\x9A3)\xF5q".unpack1('D').to_s.should == "8.818842365424256e+240"
115+
"M\xD0C\xA3\x19-\xE3\xE5".unpack1('D').to_s.should == "-6.365746090981858e+182"
116+
"\xAFf\xFE\xF0$\x85\x01L".unpack1('D').to_s.should == "1.374692728674642e+58"
117+
"'N\xB7\x12\xE0\xC8t\t".unpack1('D').to_s.should == "4.1254080603298014e-263"
118+
"\xAFn\xF2x\x85\xB5\x15j".unpack1('D').to_s.should == "1.0635019031720867e+203"
119+
"nQ\x95\xFA\xD9\xE3\xC5)".unpack1('D').to_s.should == "1.8641386367625094e-107"
120+
"\xC2\x9A\xB1|/\xCAJM".unpack1('D').to_s.should == "2.204135837758401e+64"
121+
"q n\xD8\x86\xF2\xA8D".unpack1('D').to_s.should == "5.890531214599543e+22"
122+
"dmR\xC6\xB3\xF3\x95G".unpack1('D').to_s.should == "7.294790578028111e+36"
123+
"6I\x0E)?E\xB5\xE1".unpack1('D').to_s.should == "-4.7847061687992665e+162"
124+
"\xCD\xE0\xBBy\x9F\xD8\xE89".unpack1('D').to_s.should == "9.800091365433584e-30"
125+
"\xB8\x98TN\x98\xEE\xC1\xF9".unpack1('D').to_s.should == "-3.178740061599073e+278"
126+
"\x8F_\xFF\x15\x1F2\x17B".unpack1('D').to_s.should == "24906286463.84332"
127+
"\x94\x18V\xC5&\xE6\xEAi".unpack1('D').to_s.should == "1.6471900588998988e+202"
128+
"\xECq\xB1\x01\ai\xBD,".unpack1('D').to_s.should == "3.5248469410018065e-93"
129+
"\x9C\xC6\x13pG\xDAx\x9A".unpack1('D').to_s.should == "-3.743306318201459e-181"
130+
"\xEA7,gJ\xEE\x8E*".unpack1('D').to_s.should == "1.0789044330549825e-103"
131+
"1\xD3\xF5K\x8D\xEF\xA7\r".unpack1('D').to_s.should == "7.011009309284311e-243"
132+
"o\xB3\x02\xAF\x9D\xFC\r\xF6".unpack1('D').to_s.should == "-4.610585875652112e+260"
133+
"&:x\x15\xFC3P\x01".unpack1('D').to_s.should == "2.362770515774595e-302"
134+
"\xE6<C\xB8\x90\xF2\xCF\x90".unpack1('D').to_s.should == "-1.0535871178808475e-227"
135+
"\x9Al\aB6's}".unpack1('D').to_s.should == "1.957205609213647e+296"
136+
"+\v\x16\xFD\x19\x0E\x9B\x06".unpack1('D').to_s.should == "7.631200870990123e-277"
137+
"\xEC\xF8~\xDA\xE7Tf\x92".unpack1('D').to_s.should == "-4.942358450191624e-220"
138+
"\xE0\xA0\xC9\x906\xBDcI".unpack1('D').to_s.should == "3.521575588133954e+45"
139+
"\xBD\xFD\xC9\xFD\rp\x02\x0F".unpack1('D').to_s.should == "2.2651682962118346e-236"
140+
"\xE9\xA8\xAD\xC4\xF6u\xF7\x19".unpack1('D').to_s.should == "1.3803378872547194e-183"
141+
"\"f\xED9\x17\xF0\xF1!".unpack1('D').to_s.should == "3.591307506787987e-145"
142+
"\xE6\xF2\xB6\x9CFl\xB3O".unpack1('D').to_s.should == "8.785250953340842e+75"
143+
"g\xFD\xEA\r~x\xBA\x9D".unpack1('D').to_s.should == "-1.7955908504285607e-165"
144+
"\xE2\x84J\xC7\x00\n/\x06".unpack1('D').to_s.should == "6.839790344291208e-279"
145+
"s\xFB\xA58x\xF1\xA9\xD9".unpack1('D').to_s.should == "-8.574967051032431e+123"
146+
"\xE2\x9D\xBE\xE2\x10k{\xFC".unpack1('D').to_s.should == "-4.2751876153404507e+291"
147+
"!z \xB4i4\x8C5".unpack1('D').to_s.should == "9.423078517655126e-51"
148+
"!_\xEAp- 7R".unpack1('D').to_s.should == "1.1500944673871687e+88"
149+
"\x03\xAD=\\\xCB >\xBB".unpack1('D').to_s.should == "-2.4921382721208654e-23"
150+
"\x94\x01\xB1\x87\x10\x9B#\x88".unpack1('D').to_s.should == "-1.8555672851958583e-269"
151+
"\x90H\xFF\\S\x01)\x89".unpack1('D').to_s.should == "-1.5509713490195968e-264"
152+
"HW@\x13\x85&=)".unpack1('D').to_s.should == "4.848496966571536e-110"
153+
"\x14\xDB\\\x10\x93\x9C\xD66".unpack1('D').to_s.should == "1.5842813502410472e-44"
154+
"\x9D8p>\xFF\x9B[\xF3".unpack1('D').to_s.should == "-4.826061446912647e+247"
155+
"c\x9D}\t]\xF9pg".unpack1('D').to_s.should == "1.8907034486212682e+190"
156+
"\xA51\xC9WJ\xB5a^".unpack1('D').to_s.should == "4.422435231445608e+146"
157+
"\x8BL\x90\xCB\xEARf\f".unpack1('D').to_s.should == "6.235963569982745e-249"
158+
end
159+
160+
it "random examples in human ranges" do
161+
# 50.times do
162+
# formatted = ''
163+
# rand(1..3).times do
164+
# formatted << rand(10).to_s
165+
# end
166+
# formatted << '.'
167+
# rand(1..9).times do
168+
# formatted << rand(10).to_s
169+
# end
170+
# float = formatted.to_f
171+
# string = [float].pack('D')
172+
# puts " #{string.inspect}.unpack1('D').to_s.should == #{float.to_s.inspect}"
173+
# end
174+
175+
";\x01M\x84\r\xF7M@".unpack1('D').to_s.should == "59.9301"
176+
"\xAE\xD3HKe|\x8A@".unpack1('D').to_s.should == "847.54946"
177+
"/\xDD$\x06\x81u8@".unpack1('D').to_s.should == "24.459"
178+
"E\xD8\xF0\xF4JY\xF0?".unpack1('D').to_s.should == "1.0218"
179+
"[\brP\xC2\xCC\x05@".unpack1('D').to_s.should == "2.72498"
180+
"\xE6w\x9A\xCCx\xF6T@".unpack1('D').to_s.should == "83.851123"
181+
"\xB4\xD4&\xC0C\xFD.@".unpack1('D').to_s.should == "15.494657521"
182+
"\xCD\xCC\xCC\xCC\xCCLM@".unpack1('D').to_s.should == "58.6"
183+
"\xA1\x84\x99\xB6\x7F\xE5\x13@".unpack1('D').to_s.should == "4.97412"
184+
"\xD7\xA3p=\n\x9C\x80@".unpack1('D').to_s.should == "531.505"
185+
"S\x96!\x8E\xF5\x0E\x8F@".unpack1('D').to_s.should == "993.8699"
186+
"\xF1F\xE6\x91?\x18\xD7?".unpack1('D').to_s.should == "0.360855"
187+
"=\n\xD7\xA3p=\x15@".unpack1('D').to_s.should == "5.31"
188+
"\x90Ci\x147\xC74@".unpack1('D').to_s.should == "20.7781842"
189+
"A\ft\xED\v\xE8\xB9?".unpack1('D').to_s.should == "0.101197"
190+
"\x9A\x99\x99\x99\x999T@".unpack1('D').to_s.should == "80.9"
191+
"\x00\x00\x00\x00\x00\x00\x1A@".unpack1('D').to_s.should == "6.5"
192+
"\xD3J\xC6\xD6\x98\x8Es@".unpack1('D').to_s.should == "312.9123142"
193+
"SQ\xE5I\fQ\x1E@".unpack1('D').to_s.should == "7.57914844"
194+
"k]Q\xE7\xDDb\x1E@".unpack1('D').to_s.should == "7.59654962"
195+
"\x1F\x85\xEBQ\xB8\xEAz@".unpack1('D').to_s.should == "430.67"
196+
"\x00\x00\x00\x00\x00\x00\x14@".unpack1('D').to_s.should == "5.0"
197+
"{\x14\xAEG\xE1\n}@".unpack1('D').to_s.should == "464.68"
198+
"\x12\x83\xC0\xCA\xA1=V@".unpack1('D').to_s.should == "88.963"
199+
"\x9Aw\x9C\xA2#y\e@".unpack1('D').to_s.should == "6.8683"
200+
"(\x0F\v\xB5\xA6y\xFB?".unpack1('D').to_s.should == "1.7172"
201+
"\xD5x\xE9&1H!@".unpack1('D').to_s.should == "8.641"
202+
"w'Deh\x1Ab@".unpack1('D').to_s.should == "144.8252436"
203+
":X\xFF\xE70_\x04@".unpack1('D').to_s.should == "2.54648"
204+
"E4\xB2\x12\x90\xCA\x1E@".unpack1('D').to_s.should == "7.69781522"
205+
"fffff\xAA\x80@".unpack1('D').to_s.should == "533.3"
206+
"\xCD\x92\x005\xB5p:@".unpack1('D').to_s.should == "26.440265"
207+
"\xBE\x1D<nS\x7F\x19@".unpack1('D').to_s.should == "6.3743417"
208+
"R\xB8\x1E\x85\xEBYb@".unpack1('D').to_s.should == "146.81"
209+
"\x02\x87\xAB^\xD9\xC0\xF4?".unpack1('D').to_s.should == "1.2970823"
210+
"\x00\x00\x00\x00\x00\x00\"@".unpack1('D').to_s.should == "9.0"
211+
"Zd;\xDFO3\x84@".unpack1('D').to_s.should == "646.414"
212+
"\x9A\x99\x99\x99\x99\x99\t@".unpack1('D').to_s.should == "3.2"
213+
"\xCD#\x7F0\xF0\xE5i@".unpack1('D').to_s.should == "207.18557"
214+
"\xBE\x9F\x1A/\xDD$\xF2?".unpack1('D').to_s.should == "1.134"
215+
"\xEE|?5^\xBA\xF3?".unpack1('D').to_s.should == "1.233"
216+
"\xB4\xB7\xFE\xD7\x05\x03i@".unpack1('D').to_s.should == "200.094463346"
217+
"N\x95\xD6|\xE8HG@".unpack1('D').to_s.should == "46.56959496"
218+
"Y\x868\xD6\xC5-!@".unpack1('D').to_s.should == "8.5894"
219+
"myE\xED\a;\x12@".unpack1('D').to_s.should == "4.557647426"
220+
"\xA7s\xEAo\xAE\x96B@".unpack1('D').to_s.should == "37.1771984"
221+
"\x14\x7Fo.\x99\x11|@".unpack1('D').to_s.should == "449.0998978"
222+
"\xB2\x9EZ}u\x89;@".unpack1('D').to_s.should == "27.536949"
223+
"\xD7\xA3p=\nwY@".unpack1('D').to_s.should == "101.86"
224+
"\xF3\xE6p\xAD\xF6\xC3x@".unpack1('D').to_s.should == "396.247724"
225+
end
226+
227+
it "random values from divisions" do
228+
(1.0 / 7).to_s.should == "0.14285714285714285"
229+
230+
# 50.times do
231+
# a = rand(10)
232+
# b = rand(10)
233+
# c = rand(10)
234+
# d = rand(10)
235+
# expression = "#{a}.#{b} / #{c}.#{d}"
236+
# puts " (#{expression}).to_s.should == #{eval(expression).to_s.inspect}"
237+
# end
238+
239+
(1.1 / 7.1).to_s.should == "0.15492957746478875"
240+
(6.5 / 8.8).to_s.should == "0.7386363636363635"
241+
(4.8 / 4.3).to_s.should == "1.1162790697674418"
242+
(4.0 / 1.9).to_s.should == "2.1052631578947367"
243+
(9.1 / 0.8).to_s.should == "11.374999999999998"
244+
(5.3 / 7.5).to_s.should == "0.7066666666666667"
245+
(2.8 / 1.8).to_s.should == "1.5555555555555554"
246+
(2.1 / 2.5).to_s.should == "0.8400000000000001"
247+
(3.5 / 6.0).to_s.should == "0.5833333333333334"
248+
(4.6 / 0.3).to_s.should == "15.333333333333332"
249+
(0.6 / 2.4).to_s.should == "0.25"
250+
(1.3 / 9.1).to_s.should == "0.14285714285714288"
251+
(0.3 / 5.0).to_s.should == "0.06"
252+
(5.0 / 4.2).to_s.should == "1.1904761904761905"
253+
(3.0 / 2.0).to_s.should == "1.5"
254+
(6.3 / 2.0).to_s.should == "3.15"
255+
(5.4 / 6.0).to_s.should == "0.9"
256+
(9.6 / 8.1).to_s.should == "1.1851851851851851"
257+
(8.7 / 1.6).to_s.should == "5.437499999999999"
258+
(1.9 / 7.8).to_s.should == "0.24358974358974358"
259+
(0.5 / 2.1).to_s.should == "0.23809523809523808"
260+
(9.3 / 5.8).to_s.should == "1.6034482758620692"
261+
(2.7 / 8.0).to_s.should == "0.3375"
262+
(9.7 / 7.8).to_s.should == "1.2435897435897436"
263+
(8.1 / 2.4).to_s.should == "3.375"
264+
(7.7 / 2.7).to_s.should == "2.8518518518518516"
265+
(7.9 / 1.7).to_s.should == "4.647058823529412"
266+
(6.5 / 8.2).to_s.should == "0.7926829268292683"
267+
(7.8 / 9.6).to_s.should == "0.8125"
268+
(2.2 / 4.6).to_s.should == "0.47826086956521746"
269+
(0.0 / 1.0).to_s.should == "0.0"
270+
(8.3 / 2.9).to_s.should == "2.8620689655172415"
271+
(3.1 / 6.1).to_s.should == "0.5081967213114754"
272+
(2.8 / 7.8).to_s.should == "0.358974358974359"
273+
(8.0 / 0.1).to_s.should == "80.0"
274+
(1.7 / 6.4).to_s.should == "0.265625"
275+
(1.8 / 5.4).to_s.should == "0.3333333333333333"
276+
(8.0 / 5.8).to_s.should == "1.3793103448275863"
277+
(5.2 / 4.1).to_s.should == "1.2682926829268295"
278+
(9.8 / 5.8).to_s.should == "1.6896551724137934"
279+
(5.4 / 9.5).to_s.should == "0.5684210526315789"
280+
(8.4 / 4.9).to_s.should == "1.7142857142857142"
281+
(1.7 / 3.5).to_s.should == "0.4857142857142857"
282+
(1.2 / 5.1).to_s.should == "0.23529411764705882"
283+
(1.4 / 2.0).to_s.should == "0.7"
284+
(4.8 / 8.0).to_s.should == "0.6"
285+
(9.0 / 2.5).to_s.should == "3.6"
286+
(0.2 / 0.6).to_s.should == "0.33333333333333337"
287+
(7.8 / 5.2).to_s.should == "1.5"
288+
(9.5 / 5.5).to_s.should == "1.7272727272727273"
289+
end
290+
end
98291
end
99292

100293
with_feature :encoding do

spec/tags/core/float/to_s_tags.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/main/java/org/truffleruby/core/numeric/FloatNodes.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,11 +708,17 @@ public abstract static class ToSNode extends CoreMethodArrayArgumentsNode {
708708
@TruffleBoundary
709709
@Specialization
710710
public DynamicObject toS(double value) {
711+
/*
712+
* Ruby has complex custom formatting logic for floats. Our logic meets the specs but we suspect it's
713+
* possibly still not entirely correct. JRuby seems to be correct, but their logic is tied up in their
714+
* printf implementation. Also see our FormatFloatNode, which I suspect is also deficient or under-tested.
715+
*/
716+
711717
if (Double.isInfinite(value) || Double.isNaN(value)) {
712718
return makeStringNode.executeMake(Double.toString(value), USASCIIEncoding.INSTANCE, CodeRange.CR_7BIT);
713719
}
714720

715-
String str = StringUtils.format(Locale.ENGLISH, "%.15g", value);
721+
String str = StringUtils.format(Locale.ENGLISH, "%.17g", value);
716722

717723
// If no dot, add one to show it's a floating point number
718724
if (str.indexOf('.') == -1) {
@@ -733,7 +739,23 @@ public DynamicObject toS(double value) {
733739
i--;
734740
}
735741

736-
final String formatted = str.substring(0, i + 1) + str.substring(start, str.length());
742+
String formatted = str.substring(0, i + 1) + str.substring(start);
743+
744+
int wholeDigits = 0;
745+
int n = 0;
746+
747+
if (formatted.charAt(0) == '-') {
748+
n++;
749+
}
750+
751+
while (formatted.charAt(n) != '.') {
752+
wholeDigits++;
753+
n++;
754+
}
755+
756+
if (wholeDigits >= 16) {
757+
formatted = StringUtils.format(Locale.ENGLISH, "%.1e", value);
758+
}
737759

738760
return makeStringNode.executeMake(formatted, USASCIIEncoding.INSTANCE, CodeRange.CR_7BIT);
739761
}

0 commit comments

Comments
 (0)