module std::core::string; import std::math; // Float parsing based on code in Musl floatscan.c by Rich Felker. // Musl uses the MIT license, copied below: // ---------------------------------------------------------------------- // Copyright © 2005-2014 Rich Felker, et al. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ---------------------------------------------------------------------- const KMAX = 128; const MASK = KMAX - 1; const B1B_DIG = 2; const uint[2] B1B_MAX = { 9007199, 254740991 }; <* @require chars.len > 0 *> macro double? decfloat(char[] chars, int $bits, int $emin, int sign) { uint[KMAX] x; const uint[2] TH = B1B_MAX; int emax = - $emin - $bits + 3; const int[*] P10S = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; usz index; bool got_digit = chars[0] == '0'; bool got_rad; long lrp, dc; int k, j, lnz; usz len = chars.len; usz last_char = len - 1; assert(len); char c @noinit; // Skip past first characters while ((c = chars[index]) == '0') { if (index == last_char) return sign * 0.0; index++; } if (c == '.') { got_rad = true; if (index == last_char) { if (!got_digit) return MALFORMED_FLOAT?; return sign * 0.0; } if (index != last_char && (c = chars[++index]) == '0') { lrp--; got_digit = true; while (last_char != index && (c = chars[++index]) == '0') { lrp--; } } } while (c - '0' < 10u || c == '.') { switch { case c == '.': if (got_rad) return MALFORMED_FLOAT?; got_rad = true; lrp = dc; case k < KMAX - 3: dc++; if (c != '0') lnz = (int)dc; if (j) { x[k] = x[k] * 10 + c - '0'; } else { x[k] = c - '0'; } if (++j == 9) { k++; j = 0; } got_digit = true; default: dc++; if (c != '0') x[KMAX - 4] |= 1; } if (index == last_char) break; assert(index < last_char); c = chars[++index]; } if (!got_rad) lrp = dc; if (!got_digit) return MALFORMED_FLOAT?; if ((c | 32) == 'e') { if (last_char == index) return MALFORMED_FLOAT?; long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT?!; lrp += e10; } else if (index != last_char) { return MALFORMED_FLOAT?; } // Handle zero specially to avoid nasty special cases later if (!x[0]) return sign * 0.0; // Optimize small integers (w/no exponent) and over/under-flow if (lrp == dc && dc < 10 && ($bits > 30 || (ulong)x[0] >> $bits == 0)) return sign * (double)x[0]; if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE?; if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?; // Align incomplete final B1B digit if (j) { for (; j < 9; j++) x[k] *= 10; k++; j = 0; } int a; int z = k; int e2; long rp = lrp; // Optimize small to mid-size integers (even in exp. notation) if (lnz < 9 && lnz <= rp && rp < 18) { if (rp == 9) return sign * (double)x[0]; if (rp < 9) return sign * (double)x[0] / P10S[8 - rp]; int bitlim = $bits - 3 * (int)(rp - 9); if (bitlim > 30 || x[0] >> bitlim == 0) return sign * (double)x[0] * P10S[rp - 10]; } // Align radix point to B1B digit boundary if (rp % 9) { long rpm9 = rp >= 0 ? rp % 9 : rp % 9 + 9; int p10 = P10S[8 - rpm9]; uint carry = 0; for (k = a; k != z; k++) { uint tmp = x[k] % p10; x[k] = x[k] / p10 + carry; carry = 1000000000 / p10 * tmp; if (k == a && !x[k]) { a = (a + 1) & MASK; rp -= 9; } } if (carry) x[z++] = carry; rp += 9 - rpm9; } // Upscale until desired number of bits are left of radix point while (rp < 9 * B1B_DIG || (rp == 9 * B1B_DIG && x[a] < TH[0])) { uint carry = 0; e2 -= 29; for (k = (z - 1) & MASK; ; k = (k - 1) & MASK) { ulong tmp = (ulong)x[k] << 29 + carry; if (tmp > 1000000000) { carry = (uint)(tmp / 1000000000); x[k] = (uint)(tmp % 1000000000); } else { carry = 0; x[k] = (uint)tmp; } if (k == (z - 1) & MASK && k != a && !x[k]) z = k; if (k == a) break; } if (carry) { rp += 9; a = (a - 1) & MASK; if (a == z) { z = (z - 1) & MASK; x[(z - 1) & MASK] |= x[z]; } x[a] = carry; } } // Downscale until exactly number of bits are left of radix point while (true) { uint carry = 0; int sh = 1; int i; for (i = 0; i < B1B_DIG; i++) { k = (a + i) & MASK; if (k == z || x[k] < TH[i]) { i = B1B_DIG; break; } if (x[(a + i) & MASK] > TH[i]) break; } if (i == B1B_DIG && rp == 9 * B1B_DIG) break; if (rp > 9 + 9 * B1B_DIG) sh = 9; e2 += sh; for (k = a; k != z; k = (k+1) & MASK) { uint tmp = x[k] & (1 << sh - 1); x[k] = x[k] >> sh + carry; carry = (1000000000 >> sh) * tmp; if (k == a && !x[k]) { a = (a + 1) & MASK; i--; rp -= 9; } } if (carry) { if ((z + 1) & MASK != a) { x[z] = carry; z = (z + 1) & MASK; } else { x[(z - 1) & MASK] |= 1; } } } // Assemble desired bits into floating point variable double y; int i; for (i = 0; i < B1B_DIG; i++) { if ((a + i) & MASK == z) x[(z = (z + 1) & MASK) - 1] = 0; y = 1000000000.0 * y + x[(a + i) & MASK]; } y *= sign; bool denormal; // Limit precision for denormal results uint bits = $bits; if (bits > math::DOUBLE_MANT_DIG + e2 - $emin) { bits = math::DOUBLE_MANT_DIG + e2 - $emin; if (bits < 0) bits = 0; denormal = true; } // Calculate bias term to force rounding, move out lower bits double bias; double frac; if (bits < math::DOUBLE_MANT_DIG) { bias = math::copysign(math::scalbn(1, 2 * math::DOUBLE_MANT_DIG - bits - 1), y); frac = y % math::scalbn(1, math::DOUBLE_MANT_DIG - bits); y -= frac; y += bias; } // Process tail of decimal input so it can affect rounding if ((a + i) & MASK != z) { uint t = x[(a + i) & MASK]; switch { case t < 500000000 && (t || (a + i + 1) & MASK != z): frac += 0.25 * sign; case t > 500000000: frac += 0.75 * sign; case t == 500000000: if ((a + i + 1) & MASK == z) { frac += 0.5 * sign; } else { frac += 0.75 * sign; } } if (math::DOUBLE_MANT_DIG - bits >= 2 && !(frac % 1)) frac++; } y += frac; y -= bias; if (((e2 + math::DOUBLE_MANT_DIG) & int.max) > emax - 5) { if (math::abs(y) >= 0x1p53) { if (denormal && bits == math::DOUBLE_MANT_DIG + e2 - $emin) denormal = false; y *= 0.5; e2++; } if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT?; } return math::scalbn(y, e2); } macro double? hexfloat(char[] chars, int $bits, int $emin, int sign) { double scale = 1; uint x; long rp; long dc; char c @noinit; bool got_rad; bool got_digit; bool got_tail; usz len = chars.len; usz last_char = len - 1; usz index; double y; // Skip past first characters while ((c = chars[index]) == '0') { if (index == last_char) return 0.0; index++; } if (c == '.') { got_rad = true; if (index == last_char) { if (!got_digit) return MALFORMED_FLOAT?; return sign * 0.0; } if (index != last_char && (c = chars[++index]) == '0') { rp--; got_digit = true; while (last_char != index && (c = chars[++index]) == '0') { rp--; } } } while ((c - '0') < 10u || ((c | 32) - 'a') < 6u || c == '.') { if (c == '.') { if (got_rad) return MALFORMED_FLOAT?; got_rad = true; rp = dc; } else { got_digit = true; int d = c > '9' ? ((c | 32) + 10 - 'a') : (c - '0'); switch { case dc < 8: x = x * 16 + d; case dc < math::DOUBLE_MANT_DIG / 4 + 1: y += d * (scale /= 16); got_tail = true; case d && !got_tail: y += 0.5 * scale; got_tail = true; } dc++; } if (index == last_char) break; c = chars[++index]; } if (!got_digit) return MALFORMED_FLOAT?; if (!got_rad) rp = dc; for (; dc < 8; dc++) x *= 16; long e2; if ((c | 32) == 'p') { long e2val = String.to_long((String)chars[index + 1..]) ?? (MALFORMED_FLOAT?)!; e2 = e2val; } e2 += 4 * rp - 32; if (!x) return sign * 0.0; if (e2 > -$emin) return FLOAT_OUT_OF_RANGE?; if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?; while (x < 0x80000000) { if (y >= 0.5) { x += x + 1; y += y - 1; } else { x += x; y += y; } e2--; } int bits = $bits; if ($bits > 32 + e2 - $emin) { bits = (int)(32 + e2 - $emin); if (bits < 0) bits = 0; } double bias; if (bits < math::DOUBLE_MANT_DIG) { bias = math::copysign(math::scalbn(1, 32 + math::DOUBLE_MANT_DIG - bits - 1), (double)sign); } if (bits < 32 && y && !(x & 1)) { x++; y = 0; } y = bias + sign * (double)x + sign * y; y -= bias; if (!y) return FLOAT_OUT_OF_RANGE?; return math::scalbn(y, (int)e2); } macro String.to_real(chars, $Type) @private { int sign = 1; $switch $Type: $case float: const int BITS = math::FLOAT_MANT_DIG; const int EMIN = math::FLOAT_MIN_EXP - BITS; $case double: const int BITS = math::DOUBLE_MANT_DIG; const int EMIN = math::DOUBLE_MIN_EXP - BITS; $case float128: $error "Not yet supported"; $default: $error "Unexpected type"; $endswitch chars = chars.trim(); if (!chars.len) return MALFORMED_FLOAT?; if (chars.len != 1) { switch (chars[0]) { case '-': sign = -1; nextcase; case '+': chars = chars[1..]; } } chars = chars.trim(); if (!chars.len) return MALFORMED_FLOAT?; if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf; if (chars == "NAN" || chars == "nan") return $Type.nan; if (chars.len > 2 && chars[0] == '0' && (chars[1] | 32) == 'x') { return ($Type)hexfloat((char[])chars[2..], BITS, EMIN, sign); } return ($Type)decfloat((char[])chars, BITS, EMIN, sign); }