authorgravatar for marc@tiehu.isMarc Tiehuis <marc@tiehu.is> 2019-02-14 00:06:32+13:00
committergravatar for marc@tiehu.isMarc Tiehuis <marc@tiehu.is> 2019-02-14 00:07:51+13:00
logc34ce6878e5834a123be862d45bd26fd9a843eef
treea89a2f3769931e23e443e37eb1c02260e51bf22b
parentcf007e37b95db9faebf34c4c8f9b73aa20eb0672

Add parseFloat to std.fmt

This is not intended to be the long-term implementation as it doesn't provide various properties that we eventually will want (e.g. round-tripping, denormal support). It also uses f64 internally so the wider f128 will be inaccurate.

3 files changed, 433 insertions(+), 1 deletions(-)

CMakeLists.txt+1
...@@ -482,6 +482,7 @@ set(ZIG_STD_FILES...@@ -482,6 +482,7 @@ set(ZIG_STD_FILES
482 "fmt/errol/index.zig"482 "fmt/errol/index.zig"
483 "fmt/errol/lookup.zig"483 "fmt/errol/lookup.zig"
484 "fmt/index.zig"484 "fmt/index.zig"
485 "fmt/parse_float.zig"
485 "hash/adler.zig"486 "hash/adler.zig"
486 "hash/crc.zig"487 "hash/crc.zig"
487 "hash/fnv.zig"488 "hash/fnv.zig"
std/fmt/index.zig+7-1
...@@ -828,7 +828,7 @@ pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) ParseUnsigned...@@ -828,7 +828,7 @@ pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) ParseUnsigned
828 return x;828 return x;
829}829}
830830
831test "parseUnsigned" {831test "fmt.parseUnsigned" {
832 testing.expect((try parseUnsigned(u16, "050124", 10)) == 50124);832 testing.expect((try parseUnsigned(u16, "050124", 10)) == 50124);
833 testing.expect((try parseUnsigned(u16, "65535", 10)) == 65535);833 testing.expect((try parseUnsigned(u16, "65535", 10)) == 65535);
834 testing.expectError(error.Overflow, parseUnsigned(u16, "65536", 10));834 testing.expectError(error.Overflow, parseUnsigned(u16, "65536", 10));
...@@ -855,6 +855,12 @@ test "parseUnsigned" {...@@ -855,6 +855,12 @@ test "parseUnsigned" {
855 testing.expectError(error.Overflow, parseUnsigned(u2, "4", 16));855 testing.expectError(error.Overflow, parseUnsigned(u2, "4", 16));
856}856}
857857
858pub const parseFloat = @import("parse_float.zig").parseFloat;
859
860test "fmt.parseFloat" {
861 _ = @import("parse_float.zig");
862}
863
858pub fn charToDigit(c: u8, radix: u8) (error{InvalidCharacter}!u8) {864pub fn charToDigit(c: u8, radix: u8) (error{InvalidCharacter}!u8) {
859 const value = switch (c) {865 const value = switch (c) {
860 '0'...'9' => c - '0',866 '0'...'9' => c - '0',
std/fmt/parse_float.zig created+425
...@@ -0,0 +1,425 @@
1// Adapted from https://github.com/grzegorz-kraszewski/stringtofloat.
2
3// MIT License
4//
5// Copyright (c) 2016 Grzegorz Kraszewski
6//
7// Permission is hereby granted, free of charge, to any person obtaining a copy
8// of this software and associated documentation files (the "Software"), to deal
9// in the Software without restriction, including without limitation the rights
10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11// copies of the Software, and to permit persons to whom the Software is
12// furnished to do so, subject to the following conditions:
13//
14// The above copyright notice and this permission notice shall be included in all
15// copies or substantial portions of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23// SOFTWARE.
24//
25
26// Be aware that this implementation has the following limitations:
27//
28// - Is not round-trip accurate for all values
29// - Only supports round-to-zero
30// - Does not handle denormals
31
32const std = @import("../index.zig");
33
34const max_digits = 25;
35
36const f64_plus_zero: u64 = 0x0000000000000000;
37const f64_minus_zero: u64 = 0x8000000000000000;
38const f64_plus_infinity: u64 = 0x7FF0000000000000;
39const f64_minus_infinity: u64 = 0xFFF0000000000000;
40
41const Z96 = struct {
42 d0: u32,
43 d1: u32,
44 d2: u32,
45
46 // d = s >> 1
47 inline fn shiftRight1(d: *Z96, s: Z96) void {
48 d.d0 = (s.d0 >> 1) | ((s.d1 & 1) << 31);
49 d.d1 = (s.d1 >> 1) | ((s.d2 & 1) << 31);
50 d.d2 = s.d2 >> 1;
51 }
52
53 // d = s << 1
54 inline fn shiftLeft1(d: *Z96, s: Z96) void {
55 d.d2 = (s.d2 << 1) | ((s.d1 & (1 << 31)) >> 31);
56 d.d1 = (s.d1 << 1) | ((s.d0 & (1 << 31)) >> 31);
57 d.d0 = s.d0 << 1;
58 }
59
60 // d += s
61 inline fn add(d: *Z96, s: Z96) void {
62 var w = u64(d.d0) + u64(s.d0);
63 d.d0 = @truncate(u32, w);
64
65 w >>= 32;
66 w += u64(d.d1) + u64(s.d1);
67 d.d1 = @truncate(u32, w);
68
69 w >>= 32;
70 w += u64(d.d2) + u64(s.d2);
71 d.d2 = @truncate(u32, w);
72 }
73
74 // d -= s
75 inline fn sub(d: *Z96, s: Z96) void {
76 var w = u64(d.d0) -% u64(s.d0);
77 d.d0 = @truncate(u32, w);
78
79 w >>= 32;
80 w += u64(d.d1) -% u64(s.d1);
81 d.d1 = @truncate(u32, w);
82
83 w >>= 32;
84 w += u64(d.d2) -% u64(s.d2);
85 d.d2 = @truncate(u32, w);
86 }
87
88 fn dump(d: Z96) void {
89 std.debug.warn("{} {} {}\n", d.d0, d.d1, d.d2);
90 }
91};
92
93const FloatRepr = struct {
94 negative: bool,
95 exponent: i32,
96 mantissa: u64,
97};
98
99fn convertRepr(comptime T: type, n: FloatRepr) T {
100 const mask28: u32 = 0xf << 28;
101
102 var s: Z96 = undefined;
103 var q: Z96 = undefined;
104 var r: Z96 = undefined;
105
106 s.d0 = @truncate(u32, n.mantissa);
107 s.d1 = @truncate(u32, n.mantissa >> 32);
108 s.d2 = 0;
109
110 var binary_exponent: u64 = 92;
111 var exp = n.exponent;
112
113 while (exp > 0) : (exp -= 1) {
114 q.shiftLeft1(s); // q = p << 1
115 r.shiftLeft1(q); // r = p << 2
116 s.shiftLeft1(r); // p = p << 3
117 q.add(s); // p = (p << 3) + (p << 1)
118
119 exp -= 1;
120
121 while (s.d2 & mask28 != 0) {
122 q.shiftRight1(s);
123 binary_exponent += 1;
124 s = q;
125 }
126 }
127
128 while (exp < 0) {
129 while (s.d2 & (1 << 31) == 0) {
130 q.shiftLeft1(s);
131 binary_exponent -= 1;
132 s = q;
133 }
134
135 q.d2 = s.d2 / 10;
136 r.d1 = s.d2 % 10;
137 r.d2 = (s.d1 >> 8) | (r.d1 << 24);
138 q.d1 = r.d2 / 10;
139 r.d1 = r.d2 % 10;
140 r.d2 = ((s.d1 & 0xff) << 16) | (s.d0 >> 16) | (r.d1 << 24);
141 r.d0 = r.d2 / 10;
142 r.d1 = r.d2 % 10;
143 q.d1 = (q.d1 << 8) | ((r.d0 & 0x00ff0000) >> 16);
144 q.d0 = r.d0 << 16;
145 r.d2 = (s.d0 *% 0xffff) | (r.d1 << 16);
146 q.d0 |= r.d2 / 10;
147 s = q;
148
149 exp += 1;
150 }
151
152 if (s.d0 != 0 or s.d1 != 0 or s.d2 != 0) {
153 while (s.d2 & mask28 == 0) {
154 q.shiftLeft1(s);
155 binary_exponent -= 1;
156 s = q;
157 }
158 }
159
160 binary_exponent += 1023;
161
162 const repr: u64 = blk: {
163 if (binary_exponent > 2046) {
164 break :blk if (n.negative) f64_minus_infinity else f64_plus_infinity;
165 } else if (binary_exponent < 1) {
166 break :blk if (n.negative) f64_minus_zero else f64_plus_zero;
167 } else if (s.d2 != 0) {
168 const binexs2 = u64(binary_exponent) << 52;
169 const rr = (u64(s.d2 & ~mask28) << 24) | ((u64(s.d1) + 128) >> 8) | binexs2;
170 break :blk if (n.negative) rr | (1 << 63) else rr;
171 } else {
172 break :blk 0;
173 }
174 };
175
176 const f = @bitCast(f64, repr);
177 return @floatCast(T, f);
178}
179
180const State = enum {
181 SkipLeadingWhitespace,
182 MaybeSign,
183 LeadingMantissaZeros,
184 LeadingFractionalZeros,
185 MantissaIntegral,
186 MantissaFractional,
187 ExponentSign,
188 LeadingExponentZeros,
189 Exponent,
190 Stop,
191};
192
193const ParseResult = enum {
194 Ok,
195 PlusZero,
196 MinusZero,
197 PlusInf,
198 MinusInf,
199};
200
201inline fn isDigit(c: u8) bool {
202 return c >= '0' and c <= '9';
203}
204
205inline fn isSpace(c: u8) bool {
206 return (c >= 0x09 and c <= 0x13) or c == 0x20;
207}
208
209fn parseRepr(s: []const u8, n: *FloatRepr) ParseResult {
210 var digit_index: usize = 0;
211 var negative = false;
212 var negative_exp = false;
213 var exponent: i32 = 0;
214
215 var state = State.SkipLeadingWhitespace;
216
217 var i: usize = 0;
218 loop: while (state != State.Stop and i < s.len) {
219 const c = s[i];
220
221 switch (state) {
222 State.SkipLeadingWhitespace => {
223 if (isSpace(c)) {
224 i += 1;
225 } else {
226 state = State.MaybeSign;
227 }
228 },
229
230 State.MaybeSign => {
231 state = State.LeadingMantissaZeros;
232
233 if (c == '+') {
234 i += 1;
235 } else if (c == '-') {
236 n.negative = true;
237 i += 1;
238 } else if (isDigit(c) or c == '.') {
239 // continue
240 } else {
241 state = State.Stop;
242 }
243 },
244
245 State.LeadingMantissaZeros => {
246 if (c == '0') {
247 i += 1;
248 } else if (c == '.') {
249 i += 1;
250 state = State.LeadingFractionalZeros;
251 } else {
252 state = State.MantissaIntegral;
253 }
254 },
255
256 State.LeadingFractionalZeros => {
257 if (c == '0') {
258 i += 1;
259 if (n.exponent > std.math.minInt(i32)) {
260 n.exponent -= 1;
261 }
262 } else {
263 state = State.MantissaFractional;
264 }
265 },
266
267 State.MantissaIntegral => {
268 if (isDigit(c)) {
269 if (digit_index < max_digits) {
270 n.mantissa *%= 10;
271 n.mantissa += s[i] - '0';
272 digit_index += 1;
273 } else if (n.exponent < std.math.maxInt(i32)) {
274 n.exponent += 1;
275 }
276
277 i += 1;
278 } else if (c == '.') {
279 i += 1;
280 state = State.MantissaFractional;
281 } else {
282 state = State.MantissaFractional;
283 }
284 },
285
286 State.MantissaFractional => {
287 if (isDigit(c)) {
288 if (digit_index < max_digits) {
289 n.mantissa *%= 10;
290 n.mantissa += c - '0';
291 n.exponent -%= 1;
292 digit_index += 1;
293 }
294
295 i += 1;
296 } else if (c == 'e' or c == 'E') {
297 i += 1;
298 state = State.ExponentSign;
299 } else {
300 state = State.ExponentSign;
301 }
302 },
303
304 State.ExponentSign => {
305 if (c == '+') {
306 i += 1;
307 } else if (c == '-') {
308 negative_exp = true;
309 i += 1;
310 }
311
312 state = State.LeadingExponentZeros;
313 },
314
315 State.LeadingExponentZeros => {
316 if (c == '0') {
317 i += 1;
318 } else {
319 state = State.Exponent;
320 }
321 },
322
323 State.Exponent => {
324 if (isDigit(c)) {
325 if (exponent < std.math.maxInt(i32)) {
326 exponent *= 10;
327 exponent += @intCast(i32, c - '0');
328 }
329
330 i += 1;
331 } else {
332 state = State.Stop;
333 }
334 },
335
336 State.Stop => break :loop,
337 }
338 }
339
340 if (negative_exp) exponent = -exponent;
341 n.exponent += exponent;
342
343 if (n.mantissa == 0) {
344 return if (n.negative) ParseResult.MinusZero else ParseResult.PlusZero;
345 } else if (n.exponent > 309) {
346 return if (n.negative) ParseResult.MinusInf else ParseResult.PlusInf;
347 } else if (n.exponent < -328) {
348 return if (n.negative) ParseResult.MinusZero else ParseResult.PlusZero;
349 }
350
351 return ParseResult.Ok;
352}
353
354inline fn isLower(c: u8) bool {
355 return c -% 'a' < 26;
356}
357
358inline fn toUpper(c: u8) u8 {
359 return if (isLower(c)) (c & 0x5f) else c;
360}
361
362fn caseInEql(a: []const u8, b: []const u8) bool {
363 if (a.len != b.len) return false;
364
365 for (a) |_, i| {
366 if (toUpper(a[i]) != toUpper(b[i])) {
367 return false;
368 }
369 }
370
371 return true;
372}
373
374pub fn parseFloat(comptime T: type, s: []const u8) T {
375 var r = FloatRepr{
376 .negative = false,
377 .exponent = 0,
378 .mantissa = 0,
379 };
380
381 if (caseInEql(s, "nan")) {
382 return std.math.nan(T);
383 } else if (caseInEql(s, "inf") or caseInEql(s, "+inf")) {
384 return std.math.inf(T);
385 } else if (caseInEql(s, "-inf")) {
386 return -std.math.inf(T);
387 }
388
389 return switch (parseRepr(s, &r)) {
390 ParseResult.Ok => convertRepr(T, r),
391 ParseResult.PlusZero => 0.0,
392 ParseResult.MinusZero => -T(0.0),
393 ParseResult.PlusInf => std.math.inf(T),
394 ParseResult.MinusInf => -std.math.inf(T),
395 };
396}
397
398test "fmt.parseFloat" {
399 const assert = std.debug.assert;
400 const approxEq = std.math.approxEq;
401 const epsilon = 1e-7;
402
403 inline for ([]type{ f32, f64, f128 }) |T| {
404 const Z = @IntType(false, T.bit_count);
405
406 assert(parseFloat(T, "0") == 0.0);
407 assert(parseFloat(T, "+0") == 0.0);
408 assert(parseFloat(T, "-0") == 0.0);
409
410 assert(approxEq(T, parseFloat(T, "3.141"), 3.141, epsilon));
411 assert(approxEq(T, parseFloat(T, "-3.141"), -3.141, epsilon));
412
413 assert(parseFloat(T, "1e-700") == 0);
414 assert(parseFloat(T, "1e+700") == std.math.inf(T));
415
416 assert(@bitCast(Z, parseFloat(T, "nAn")) == @bitCast(Z, std.math.nan(T)));
417 assert(parseFloat(T, "inF") == std.math.inf(T));
418 assert(parseFloat(T, "-INF") == -std.math.inf(T));
419
420 if (T != f16) {
421 assert(approxEq(T, parseFloat(T, "123142.1"), 123142.1, epsilon));
422 assert(approxEq(T, parseFloat(T, "-123142.1124"), T(-123142.1124), epsilon));
423 }
424 }
425}