diff --git a/IronPython.slnx b/IronPython.slnx index b2e45da30..e8a180378 100644 --- a/IronPython.slnx +++ b/IronPython.slnx @@ -65,5 +65,6 @@ + diff --git a/src/core/IronPython.StdLib b/src/core/IronPython.StdLib index 6e08c8264..ffa4f648d 160000 --- a/src/core/IronPython.StdLib +++ b/src/core/IronPython.StdLib @@ -1 +1 @@ -Subproject commit 6e08c8264990fc712d122a7e8e44bd11dd964b5f +Subproject commit ffa4f648d640f6ea99218bbb8aceaa208a4fa84f diff --git a/src/core/IronPython/IronPython.csproj b/src/core/IronPython/IronPython.csproj index 79cb7650b..a8dcf92ed 100644 --- a/src/core/IronPython/IronPython.csproj +++ b/src/core/IronPython/IronPython.csproj @@ -83,6 +83,13 @@ + + + false + Analyzer + + + diff --git a/src/core/IronPython/Modules/sys.cs b/src/core/IronPython/Modules/sys.cs index aab7ef41d..40f542f20 100644 --- a/src/core/IronPython/Modules/sys.cs +++ b/src/core/IronPython/Modules/sys.cs @@ -119,7 +119,7 @@ public static void setcheckinterval(int value) { throw PythonOps.NotImplementedError("IronPython does not support sys.setcheckinterval"); } - // warnoptions is set by PythonContext and updated on each reload + // warnoptions is set by PythonContext and updated on each reload public static PythonTuple exc_info(CodeContext/*!*/ context) { return PythonOps.GetExceptionInfo(context); @@ -294,7 +294,7 @@ public override string __repr__(CodeContext context) { #if PROFILE_SUPPORT // not enabled because we don't yet support tracing built-in functions. Doing so is a little - // difficult because it's hard to flip tracing on/off for them w/o a perf overhead in the + // difficult because it's hard to flip tracing on/off for them w/o a perf overhead in the // non-profiling case. public static void setprofile(CodeContext/*!*/ context, TracebackDelegate o) { PythonContext pyContext = context.LanguageContext; @@ -489,11 +489,7 @@ public override string __repr__(CodeContext context) { } } -#if NETCOREAPP3_1_OR_GREATER public static string float_repr_style = "short"; -#else - public static string float_repr_style = "legacy"; -#endif public static floatinfo float_info = new floatinfo( Double.MaxValue, // DBL_MAX @@ -603,7 +599,7 @@ public static void PerformModuleReload(PythonContext/*!*/ context, PythonDiction dict["platform"] = PythonContext.GetPlatform(); - // !!! These fields do need to be reset on "reload(sys)". However, the initial value is specified by the + // !!! These fields do need to be reset on "reload(sys)". However, the initial value is specified by the // engine elsewhere. For now, we initialize them just once to some default value dict["warnoptions"] = new PythonList(0); diff --git a/src/core/IronPython/Runtime/Operations/FloatOps.cs b/src/core/IronPython/Runtime/Operations/FloatOps.cs index 4424be6e0..3e722e95e 100644 --- a/src/core/IronPython/Runtime/Operations/FloatOps.cs +++ b/src/core/IronPython/Runtime/Operations/FloatOps.cs @@ -733,27 +733,8 @@ public static bool NotEquals(Double x, int y) { return x != y; } - internal static string Repr(CodeContext/*!*/ context, double self, bool trailingZeroAfterWholeFloat) { - if (double.IsNaN(self)) { - return "nan"; - } - - // first format using Python's specific formatting rules... - string res = StringFormatter.Format(context, "%.17g", self, trailingZeroAfterWholeFloat: trailingZeroAfterWholeFloat); - if (LiteralParser.ParseFloat(res) == self) { - return res; - } -#if NETCOREAPP3_1_OR_GREATER - Debug.Assert(false); -#endif - - // if it's not round trippable though use .NET's round-trip format - res = self.ToString("R", CultureInfo.InvariantCulture); - if (trailingZeroAfterWholeFloat && !res.Contains('.')) { - res += ".0"; - } - return res; - } + internal static string Repr(CodeContext/*!*/ context, double self, bool trailingZeroAfterWholeFloat) + => Schubfach.dtoa(self, trailingZeroAfterWholeFloat); public static string __repr__(CodeContext/*!*/ context, double self) => Repr(context, self, trailingZeroAfterWholeFloat: true); diff --git a/src/core/IronPython/Runtime/Schubfach.cs b/src/core/IronPython/Runtime/Schubfach.cs new file mode 100644 index 000000000..7250fc7e3 --- /dev/null +++ b/src/core/IronPython/Runtime/Schubfach.cs @@ -0,0 +1,308 @@ +// Implementation of the Schubfach algorithm: +// https://fmt.dev/papers/Schubfach4.pdf. +// Copyright (c) 2025 Victor Zverovich +// Distributed under the MIT license. +// Ported from schubfach.cc of https://github.com/vitaut/schubfach. + +#nullable enable + +using System; + +namespace IronPython.Runtime { + internal static partial class Schubfach { + private const int buffer_size = 24; // schubfach.cc is 25, but we don't need the extra byte for null-terminator. + + // pow10_significands is generated by IronPython.SourceGenerator.SchubfachGenerator. + + // Computes 128-bit result of multiplication of two 64-bit unsigned integers. + private static ulong umul128(ulong x, ulong y, out ulong lo) { +#if NET + return Math.BigMul(x, y, out lo); +#else + const ulong mask = ~0u; + + ulong a = x >> 32; + ulong b = x & mask; + ulong c = y >> 32; + ulong d = y & mask; + + ulong ac = a * c; + ulong bc = b * c; + ulong ad = a * d; + ulong bd = b * d; + + ulong intermediate = (bd >> 32) + (ad & mask) + (bc & mask); + + lo = (intermediate << 32) + (bd & mask); + return ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32); +#endif + } + + // Computes upper 64 bits of multiplication of pow10 and scaled_sig with + // modified round-to-odd rounding of the result, + // where pow10 = (pow10_hi << 63) | pow10_lo. + private static ulong umul192_upper64_modified(ulong pow10_hi, ulong pow10_lo, ulong scaled_sig) { + ulong x_hi = umul128(pow10_lo, scaled_sig, out _); + ulong y_hi = umul128(pow10_hi, scaled_sig, out ulong y_lo); + ulong z = (y_lo >> 1) + x_hi; + ulong result = y_hi + (z >> 63); + const ulong mask = ((ulong)1 << 63) - 1; + // OR with 1 if z is not divisible by 2**63. + return result | (((z & mask) + mask) >> 63); + } + + // log10_2_sig = round(log10(2) * 2**log10_2_exp) + private const long log10_2_sig = 661_971_961_083; + private const int log10_2_exp = 41; + + // Computes floor(log10(pow(2, e))) for e <= 5456721. + private static int floor_log10_pow2(int e) { + return (int)(e * log10_2_sig >> log10_2_exp); + } + + private static void write8digits(Span buffer, ref int pos, uint n) { + // Based on Division-Free Binary-to-Decimal Conversion: + // https://inria.hal.science/hal-00864293/. + const int shift = 28; + const ulong magic = 193_428_131_138_340_668; + uint y = (uint)((umul128(((ulong)n + 1) << shift, magic, out _) >> 20) - 1); + for (int i = 0; i < 8; ++i) { + uint t = 10 * y; + buffer[pos++] = (char)('0' + (t >> shift)); + y = t & ((1u << shift) - 1); + } + } + + private static readonly ulong[] pow10 = [ + 1, + 10, + 100, + 1_000, + 10_000, + 100_000, + 1_000_000, + 10_000_000, + 100_000_000, + 1_000_000_000, + 10_000_000_000, + 100_000_000_000, + 1_000_000_000_000, + 10_000_000_000_000, + 100_000_000_000_000, + 1_000_000_000_000_000, + 10_000_000_000_000_000, + 100_000_000_000_000_000, + ]; + + // Rewrites the digits in buffer[start..pos] in fixed notation if Python's + // repr would, i.e. for -4 < dec_point <= 16. + private static bool try_write_fixed(Span buffer, ref int pos, int start, int dec_exp, bool add_dot_0) { + int num_digits = pos - start; + int dec_point = dec_exp + 1; + if (dec_point <= -4 || dec_point > 16) return false; + + if (dec_point <= 0) { + // 0.000ddd + int prefix = 2 - dec_point; + buffer.Slice(start, num_digits).CopyTo(buffer.Slice(start + prefix)); + buffer[start] = '0'; + buffer[start + 1] = '.'; + buffer.Slice(start + 2, -dec_point).Fill('0'); + pos += prefix; + } else if (dec_point >= num_digits) { + // ddd000.0 + buffer.Slice(pos, dec_point - num_digits).Fill('0'); + pos += dec_point - num_digits; + if (add_dot_0) { + buffer[pos++] = '.'; + buffer[pos++] = '0'; + } + } else { + // ddd.ddd + buffer.Slice(start + dec_point, num_digits - dec_point).CopyTo(buffer.Slice(start + dec_point + 1)); + buffer[start + dec_point] = '.'; + ++pos; + } + return true; + } + + // Writes the decimal FP number dec_sig * 10**dec_exp to buffer. + private static string write(bool is_negative, ulong dec_sig, int dec_exp, bool add_dot_0) { + Span buffer = stackalloc char[buffer_size]; + int pos = 0; + if (is_negative) buffer[pos++] = '-'; + + int len = floor_log10_pow2(64 - countl_zero(dec_sig)); // 64 = std::numeric_limits::digits + if (dec_sig >= pow10[len]) ++len; + dec_sig *= pow10[17 - len]; // 17 = std::numeric_limits::max_digits10 + dec_exp += len - 1; + + const int pow10_8 = 100_000_000; + int start = pos; + uint hi = (uint)(dec_sig / pow10_8); + buffer[pos++] = (char)('0' + hi / pow10_8); + // schubfach.cc adds the . here; we add it later so try_write_fixed can place it. + write8digits(buffer, ref pos, hi % pow10_8); + uint lo = (uint)(dec_sig % pow10_8); + if (lo != 0) write8digits(buffer, ref pos, lo); + + // Remove trailing zeros. + while (buffer[pos - 1] == '0') --pos; + + if (try_write_fixed(buffer, ref pos, start, dec_exp, add_dot_0)) { + return buffer.Slice(0, pos).ToString(); + } + + // Fallback to scientific notation if fixed notation is not used. + int num_digits = pos - start; + if (num_digits > 1) { + buffer.Slice(start + 1, num_digits - 1).CopyTo(buffer.Slice(start + 2)); + buffer[start + 1] = '.'; + ++pos; + } + + buffer[pos++] = 'e'; + char sign = '+'; + if (dec_exp < 0) { + sign = '-'; + dec_exp = -dec_exp; + } + buffer[pos++] = sign; + if (dec_exp >= 100) { + buffer[pos++] = (char)('0' + dec_exp / 100); + dec_exp %= 100; + } + buffer[pos++] = (char)('0' + dec_exp / 10); + buffer[pos++] = (char)('0' + dec_exp % 10); + return buffer.Slice(0, pos).ToString(); + } + + /// Returns the shortest decimal representation of `value`, formatted like Python's repr. + public static string dtoa(double value, bool add_dot_0 = true) { + long bitsLong = BitConverter.DoubleToInt64Bits(value); + bool is_negative = bitsLong < 0; + ulong bits = unchecked((ulong)bitsLong); + + const int num_sig_bits = 53 - 1; // 53 = std::numeric_limits::digits + const int exp_mask = 0x7ff; + int bin_exp = (int)(bits >> num_sig_bits) & exp_mask; + + const ulong implicit_bit = (ulong)1 << num_sig_bits; + ulong bin_sig = bits & (implicit_bit - 1); // binary significand + + bool regular = bin_sig != 0; + if (((bin_exp + 1) & exp_mask) <= 1) { + if (bin_exp != 0) return bin_sig != 0 ? "nan" : is_negative ? "-inf" : "inf"; + if (bin_sig == 0) return is_negative ? (add_dot_0 ? "-0.0" : "-0") : (add_dot_0 ? "0.0" : "0"); + // Handle subnormals. + bin_sig ^= implicit_bit; + ++bin_exp; + regular = true; + } + bin_sig ^= implicit_bit; + bin_exp -= num_sig_bits + 1023; // Remove the exponent bias. + + // Handle small integers. + if ((bin_exp < 0) & (bin_exp >= -num_sig_bits)) { + ulong f = bin_sig >> -bin_exp; + if (f << -bin_exp == bin_sig) { + return write(is_negative, f, 0, add_dot_0); + } + } + + // Shift the significand so that boundaries are integer. + ulong bin_sig_shifted = bin_sig << 2; + + // Compute the shifted boundaries of the rounding interval (Rv). + ulong lower = bin_sig_shifted - (regular ? 2u : 1u); + ulong upper = bin_sig_shifted + 2; + + // log10_3_over_4_sig = round(log10(3/4) * 2**log10_2_exp) + const long log10_3_over_4_sig = -274_743_187_321; + + // Compute the decimal exponent as floor(log10(2**bin_exp)) if regular or + // floor(log10(3/4 * 2**bin_exp)) otherwise, without branching. + int dec_exp = (int)((bin_exp * log10_2_sig + (!regular ? log10_3_over_4_sig : 0)) >> log10_2_exp); + + const int dec_exp_min = -292; + int pow10_index = 2 * (-dec_exp - dec_exp_min); + ulong pow10_hi = pow10_significands[pow10_index], pow10_lo = pow10_significands[pow10_index + 1]; + + // round(log2(10) * 2**log2_pow10_exp) + 1 + const int log2_pow10_sig = 217_707, log2_pow10_exp = 16; + + // floor(log2(10**-dec_exp)) + int pow10_bin_exp = -dec_exp * log2_pow10_sig >> log2_pow10_exp; + // pow10 = ((pow10_hi << 63) | pow10_lo) * 2**(pow10_bin_exp - 126 + 1) + + // Shift to ensure the intermediate result in umul192_upper64_modified has + // a fixed 128-bit fractional width. For example, 3 * 2**59 and 3 * 2**60 + // both have dec_exp = 2 and dividing them by 10**dec_exp would have the + // decimal point in different (bit) positions without the shift: + // 3 * 2**59 / 100 = 1.72...e+16 (shift = 3) + // 3 * 2**60 / 100 = 3.45...e+16 (shift = 4) + int shift = bin_exp + pow10_bin_exp + 2; + + ulong scaled_sig = umul192_upper64_modified(pow10_hi, pow10_lo, bin_sig_shifted << shift); + + // Compute the estimates of lower and upper bounds of the rounding interval + // by multiplying them by the power of 10 and applying modified rounding. + lower = umul192_upper64_modified(pow10_hi, pow10_lo, lower << shift); + upper = umul192_upper64_modified(pow10_hi, pow10_lo, upper << shift); + + ulong dec_sig_under = scaled_sig >> 2; + ulong bin_sig_lsb = bin_sig & 1; + if (dec_sig_under >= 10) { + // Compute the significands of the under- and overestimate. + ulong dec_sig_under2 = 10 * (dec_sig_under / 10); + ulong dec_sig_over2 = dec_sig_under2 + 10; + // Check if the under- and overestimates are in the interval. + bool under_in2 = lower + bin_sig_lsb <= (dec_sig_under2 << 2); + bool over_in2 = (dec_sig_over2 << 2) + bin_sig_lsb <= upper; + if (under_in2 != over_in2) { + return write(is_negative, under_in2 ? dec_sig_under2 : dec_sig_over2, dec_exp, add_dot_0); + } + } + + ulong dec_sig_over = dec_sig_under + 1; + bool under_in = lower + bin_sig_lsb <= (dec_sig_under << 2); + bool over_in = (dec_sig_over << 2) + bin_sig_lsb <= upper; + if (under_in != over_in) { + // Only one of dec_sig_under or dec_sig_over are in the rounding interval. + return write(is_negative, under_in ? dec_sig_under : dec_sig_over, dec_exp, add_dot_0); + } + + // Both dec_sig_under and dec_sig_over are in the interval - pick the closest. + int cmp = unchecked((int)(scaled_sig - ((dec_sig_under + dec_sig_over) << 1))); + bool under_closer = cmp < 0 || cmp == 0 && (dec_sig_under & 1) == 0; + return write(is_negative, under_closer ? dec_sig_under : dec_sig_over, dec_exp, add_dot_0); + } + + // std::countl_zero + private static int countl_zero(ulong x) { +#if NET + return System.Numerics.BitOperations.LeadingZeroCount(x); +#else + if (x == 0) return 64; + // fill trailing zeroes with ones + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x |= x >> 32; + // deBruijn sequence, k=2, n=6 (2^6=64) + return 63 - debruijn[(int)(unchecked(x * 0x03f79d71b4cb0a89ul) >> 58)]; +#endif + } + +#if !NET + private static ReadOnlySpan debruijn => [ + 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, + 54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, + 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45, + 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63, + ]; +#endif + } +} diff --git a/src/roslyn/IronPython.Analyzer/IronPython.Analyzer.csproj b/src/roslyn/IronPython.Analyzer/IronPython.Analyzer.csproj index 38c0895fe..75988068e 100644 --- a/src/roslyn/IronPython.Analyzer/IronPython.Analyzer.csproj +++ b/src/roslyn/IronPython.Analyzer/IronPython.Analyzer.csproj @@ -3,6 +3,7 @@ netstandard2.0 true + true diff --git a/src/roslyn/IronPython.SourceGenerator/IronPython.SourceGenerator.csproj b/src/roslyn/IronPython.SourceGenerator/IronPython.SourceGenerator.csproj new file mode 100644 index 000000000..fa20195c2 --- /dev/null +++ b/src/roslyn/IronPython.SourceGenerator/IronPython.SourceGenerator.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + true + true + + + + + + + + diff --git a/src/roslyn/IronPython.SourceGenerator/SchubfachGenerator.cs b/src/roslyn/IronPython.SourceGenerator/SchubfachGenerator.cs new file mode 100644 index 000000000..6ed8adcc7 --- /dev/null +++ b/src/roslyn/IronPython.SourceGenerator/SchubfachGenerator.cs @@ -0,0 +1,59 @@ +// Power of 10 overestimates generator for the Schubfach algorithm: +// https://fmt.dev/papers/Schubfach4.pdf. +// Copyright (c) 2025 Victor Zverovich +// Distributed under the MIT license. +// Ported from gen-pow10.py of https://github.com/vitaut/schubfach. + +using System; +using System.Numerics; +using System.Text; + +using Microsoft.CodeAnalysis; + +namespace IronPython.SourceGenerator { + [Generator(LanguageNames.CSharp)] + public class SchubfachGenerator : IIncrementalGenerator { + public void Initialize(IncrementalGeneratorInitializationContext context) { + context.RegisterPostInitializationOutput(ctx => ctx.AddSource("Schubfach.pow10_significands.g.cs", Generate())); + } + + private static string Generate() { + var sb = new StringBuilder(); + sb.AppendLine("// "); + sb.AppendLine(); + sb.AppendLine("namespace IronPython.Runtime {"); + sb.AppendLine(" internal static partial class Schubfach {"); + sb.AppendLine(" // 126-bit significands of overestimates of powers of 10."); + sb.AppendLine(" // Generated with IronPython.SourceGenerator.SchubfachGenerator."); + sb.AppendLine(" private static readonly ulong[] pow10_significands = ["); + + // Range of decimal exponents [K_min, K_max] from the paper. + const int dec_exp_min = -324; + const int dec_exp_max = 292; + + const int num_bits = 126; + + // Negate dec_pow_min and dec_pow_max because we need negative powers 10^-k. + for (int dec_exp = -dec_exp_max; dec_exp < -dec_exp_min + 1; dec_exp++) { + // dec_exp is -k in the paper. + int bin_exp = (int)Math.Floor(dec_exp * (Math.Log(10) / Math.Log(2))) - (num_bits - 1); + BigInteger bin_pow = BigInteger.Pow(2, Math.Abs(bin_exp)); + BigInteger dec_pow = BigInteger.Pow(10, Math.Abs(dec_exp)); + BigInteger result; + if (dec_exp < 0) + result = bin_pow / dec_pow; + else if (bin_exp < 0) + result = dec_pow * bin_pow; + else + result = dec_pow / bin_pow; + result = result + 1; + sb.AppendLine($" 0x{(ulong)(result >> 63):x}, 0x{(ulong)(result & (BigInteger.Pow(2, 63) - 1)):x16}, // {dec_exp,4}"); + } + + sb.AppendLine(" ];"); + sb.AppendLine(" }"); + sb.AppendLine("}"); + return sb.ToString(); + } + } +} diff --git a/tests/suite/modules/io_related/test_cPickle.py b/tests/suite/modules/io_related/test_cPickle.py index da98a63ed..0dbe0d4fe 100644 --- a/tests/suite/modules/io_related/test_cPickle.py +++ b/tests/suite/modules/io_related/test_cPickle.py @@ -430,7 +430,7 @@ def normalize(test): 2:b'c%s\nNewClass_GetState_GetNewArgs\nq<\x00>X\x04\x00\x00\x00arg1q<\x01>K\x02K\x03\x87q<\x02>\x81q<\x03>X\x06\x00\x00\x00state1q<\x04>X\x06\x00\x00\x00state2q<\x05>\x86q<\x06>b.'.replace(b"%s", __name__.encode("ascii")), }), ] - if is_cli or sys.version_info < (3,5): #https://github.com/IronLanguages/main/issues/853 + if not is_cli and sys.version_info < (3,5): tests.append((0.33333333333333331, { 0:b'F0.33333333333333331\n.', diff --git a/tests/suite/modules/type_related/test_array.py b/tests/suite/modules/type_related/test_array.py index 55fe4b111..c0ffcc5d6 100644 --- a/tests/suite/modules/type_related/test_array.py +++ b/tests/suite/modules/type_related/test_array.py @@ -481,12 +481,8 @@ def test_cp9348(self): test_cases[('l', b"\x12\x34\x45\x67\x89\xab\xcd\xef")] = "array('l', [-1167088121787632622])" test_cases[('L', b"\x12\x34\x45\x67\x89\xab\xcd\xef")] = "array('L', [17279655951921918994])" - if is_cli: # https://github.com/IronLanguages/ironpython2/issues/102 - test_cases[('d', b"\x12\x34\x45\x67\x12\x34\x45\x67")] = "array('d', [2.9522485325887698e+189])" - test_cases[('f', b"\x12\x34\x45\x67")] = "array('f', [9.3126672485384569e+23])" - else: - test_cases[('d', b"\x12\x34\x45\x67\x12\x34\x45\x67")] = "array('d', [2.95224853258877e+189])" - test_cases[('f', b"\x12\x34\x45\x67")] = "array('f', [9.312667248538457e+23])" + test_cases[('d', b"\x12\x34\x45\x67\x12\x34\x45\x67")] = "array('d', [2.95224853258877e+189])" + test_cases[('f', b"\x12\x34\x45\x67")] = "array('f', [9.312667248538457e+23])" for key in test_cases.keys(): type_code, param = key diff --git a/tests/suite/stdlib/test_float.py b/tests/suite/stdlib/test_float.py index 7a7834d84..e851387b3 100644 --- a/tests/suite/stdlib/test_float.py +++ b/tests/suite/stdlib/test_float.py @@ -8,7 +8,7 @@ import sys -from iptest import is_ironpython, generate_suite, run_test +from iptest import is_ironpython, generate_suite, run_test, is_netcoreapp import test.test_float @@ -18,14 +18,17 @@ def load_tests(loader, standard_tests, pattern): if is_ironpython: failing_tests = [ test.test_float.FormatTestCase('test_format'), - test.test_float.FormatTestCase('test_format_testfile'), test.test_float.FormatTestCase('test_issue5864'), test.test_float.InfNanTest('test_nan_signs'), - test.test_float.ReprTestCase('test_short_repr'), test.test_float.RoundTestCase('test_large_n'), test.test_float.RoundTestCase('test_matches_float_format'), test.test_float.RoundTestCase('test_previous_round_bugs'), ] + if not is_netcoreapp: + failing_tests += [ + test.test_float.FormatTestCase('test_format_testfile'), + test.test_float.ReprTestCase('test_short_repr'), + ] if sys.version_info >= (3, 6): failing_tests += [ test.test_float.GeneralFloatCases('test_underscores'), # https://github.com/IronLanguages/ironpython3/issues/105 diff --git a/tests/suite/test_float_repr.py b/tests/suite/test_float_repr.py new file mode 100644 index 000000000..b632f2898 --- /dev/null +++ b/tests/suite/test_float_repr.py @@ -0,0 +1,158 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the Apache 2.0 License. +# See the LICENSE file in the project root for more information. + +""" +Tests for float repr. The floats are built from their bits or from exact +integers so these tests do not depend on the accuracy of decimal string parsing. +""" + +import struct +import unittest + +from iptest import run_test, skipUnlessIronPython + +def from_bits(bits): + return struct.unpack('> 63, g & ((1 << 63) - 1)] + + self.assertEqual(list(table), expected) + +run_test(__name__) diff --git a/tests/suite/test_formatting.py b/tests/suite/test_formatting.py index 6ecce4e4c..dbabff9c7 100644 --- a/tests/suite/test_formatting.py +++ b/tests/suite/test_formatting.py @@ -393,21 +393,6 @@ def test_format_testfile(self): """the following is borrowed from stdlib""" expected_failures = [] - if is_cli: - expected_failures = """ -%r 0.03 -> 0.03 -%r 0.04 -> 0.04 -%r 0.05 -> 0.05 -%r 9999999999999999 -> 1e+16 -%r 1e16 -> 1e+16 -%r 1.001e-4 -> 0.0001001 -%r 1.00000000001e-4 -> 0.000100000000001 -%r 1.0000000001e-4 -> 0.00010000000001 -%r 0.9999999999999999e-4 -> 9.999999999999999e-05 -%r 0.999999999999e-4 -> 9.99999999999e-05 -%r 0.999e-4 -> 9.99e-05 -%r 1e-5 -> 1e-05 -""".strip().split("\n") if not format_rounds_to_even: expected_failures += """