diff --git a/src/big_int.cpp b/src/big_int.cpp index 0d38f0ab1..9db4a8d55 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -300,11 +300,8 @@ gb_internal void big_int_from_string(BigInt *dst, String const &s, bool *success return; } - // NOTE(Jeroen): A valid integer can never have an exponent larger than 308 (per `max(f64)`). - // As an integer, not even larger than `max(u128)` which has a base 10 exponent of 38. - // But we also use this path to parse float literals like those in `core:math.pow10_f64`, - // so we have to stick with 1e308. - if (exp > 308) { + // NOTE(bill): Just limit the maximum exponent to bigger than the actual maximum to allow for keeping overflows + if (exp > 512) { *success = false; return; } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index a159df6a6..ec812bac1 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2516,6 +2516,28 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i return false; } check_update_float_precision(&v, type); + + if (in_value.kind == ExactValue_Integer) { + bool overflowed = isinf(v.value_float) || isnan(v.value_float); + if (!overflowed) { + switch (type->Basic.kind) { + case Basic_f16: + case Basic_f16le: + case Basic_f16be: + // `check_update_float_precision` only rounds `f16` to `f32` precision, so a value + // that is finite as `f32` but out of range for `f16` must be caught explicitly. + if (isinf(cast(f64)f16_to_f32(f32_to_f16(cast(f32)v.value_float)))) { + overflowed = true; + } + break; + } + } + if (overflowed) { + // Leave `out_value` unset so the diagnostic reports the exact (finite) source value. + return false; + } + } + if (out_value) *out_value = v; switch (type->Basic.kind) { @@ -12607,6 +12629,10 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast t = t_untyped_integer; if (bl->token.kind == Token_Rune) { t = t_untyped_rune; + } else if (string_contains_char(bl->token.string, '.')) { + // NOTE(bill): A floating-point literal whose value happens to be an integer (e.g. `98765.0e309`) keeps + // its exact value as an integer, but still defaults to untyped float because it was written in floating-point form. + t = t_untyped_float; } break; default: diff --git a/src/exact_value.cpp b/src/exact_value.cpp index d768525ad..6e5b84bad 100644 --- a/src/exact_value.cpp +++ b/src/exact_value.cpp @@ -349,6 +349,106 @@ gb_internal f64 float_from_string(String const &string, bool *success = nullptr) */ } +gb_internal ExactValue exact_value_integer_from_decimal_float_string(String const &string, bool *is_integral) { + *is_integral = false; + + BigInt mantissa = {}; + big_int_from_u64(&mantissa, 0); + defer (big_int_dealloc(&mantissa)); + BigInt ten = {}; + big_int_from_u64(&ten, 10); + defer (big_int_dealloc(&ten)); + BigInt digit = {}; + defer (big_int_dealloc(&digit)); + + isize i = 0; + i64 frac_digits = 0; + bool seen_dot = false; + for (; i < string.len; i++) { + u8 c = string.text[i]; + if (c == '_') { + continue; + } + if (c == '.') { + if (seen_dot) { + return {ExactValue_Invalid}; + } + seen_dot = true; + continue; + } + if (c == 'e' || c == 'E') { + break; + } + if (!gb_char_is_digit(cast(char)c)) { + // NOTE(bill): Not a plain base-10 float literal (the tokenizer should have prevented this). + return {ExactValue_Invalid}; + } + big_int_from_u64(&digit, u64_digit_value(cast(Rune)c)); + big_int_mul_eq(&mantissa, &ten); + big_int_add_eq(&mantissa, &digit); + if (seen_dot) { + frac_digits += 1; + } + } + + i64 exp = 0; + bool exp_negative = false; + if (i < string.len && (string.text[i] == 'e' || string.text[i] == 'E')) { + i += 1; + if (i < string.len && (string.text[i] == '+' || string.text[i] == '-')) { + exp_negative = string.text[i] == '-'; + i += 1; + } + isize exp_digits = 0; + for (; i < string.len; i++) { + u8 c = string.text[i]; + if (c == '_') { + continue; + } + if (!gb_char_is_digit(cast(char)c)) { + return {ExactValue_Invalid}; + } + if (exp <= 512) { + // NOTE(bill): clamp so it cannot overflow; anything past the cap is rejected below + exp = exp*10 + cast(i64)u64_digit_value(cast(Rune)c); + } + exp_digits += 1; + } + if (exp_digits == 0) { + return {ExactValue_Invalid}; + } + } + + i64 signed_exp = exp_negative ? -exp : exp; + i64 effective_exp = signed_exp - frac_digits; + if (effective_exp < 0) { + // NOTE(bill): The value has a fractional part, so it is not an integer; let the caller parse it as a float. + return {ExactValue_Invalid}; + } + + *is_integral = true; + + // NOTE(bill): Guard against pathological allocations; kept consistent with `big_int_from_string`. + if (signed_exp > 512) { + return {ExactValue_Invalid}; + } + + bool success = true; + BigInt scale = {}; + mp_init(&scale); + defer (big_int_dealloc(&scale)); + big_int_exp_u64(&scale, &ten, cast(u64)effective_exp, &success); + if (!success) { + return {ExactValue_Invalid}; + } + + ExactValue result = {ExactValue_Integer}; + result.value_integer = {0}; + mp_init(&result.value_integer); + big_int_mul(&result.value_integer, &mantissa, &scale); + return result; +} + gb_internal ExactValue exact_value_float_from_string(String string) { if (string.len > 2 && string[0] == '0' && string[1] == 'h') { @@ -383,6 +483,16 @@ gb_internal ExactValue exact_value_float_from_string(String string) { return exact_value_integer_from_string(string); } + { + // NOTE(bill): A decimal literal whose value is an integer (e.g. `98765.0e309`) is kept + // as an exact arbitrary-precision integer so it is not silently rounded to `+Inf` by `strtod` + bool is_integral = false; + ExactValue v = exact_value_integer_from_decimal_float_string(string, &is_integral); + if (is_integral) { + return v; + } + } + bool success; f64 f = float_from_string(string, &success); if (!success) {