From 51a5c3aa60764e2ec7521d240ee1a7587183d352 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 26 Feb 2026 11:08:36 +0300 Subject: [PATCH] cgen: fix incorrect f64 -> u64 conversions (fixes #20944) --- vlib/v/gen/c/cgen.v | 6 ++++++ vlib/v/gen/c/cheaders.v | 14 ++++++++++++++ vlib/v/tests/casts/cast_f64_to_u64_boundary_test.v | 14 ++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 vlib/v/tests/casts/cast_f64_to_u64_boundary_test.v diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index bda8e3c7c..218d2dd4f 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1036,6 +1036,7 @@ pub fn (mut g Gen) init() { } else { g.cheaders.writeln(c_headers) } + g.cheaders.writeln(c_float_to_unsigned_conversion_functions) if !g.pref.skip_unused || g.table.used_features.safe_int { g.cheaders.writeln(c_unsigned_comparison_functions) } @@ -6009,6 +6010,11 @@ fn (mut g Gen) cast_expr(node ast.CastExpr) { } g.expr(node.expr) } + } else if !node_typ_is_option && final_sym.kind == .u64 + && final_expr_sym.kind in [.f32, .f64, .float_literal] { + g.write('_v_f64_to_u64((double)(') + g.expr(node.expr) + g.write('))') } else if (expr_type == ast.bool_type && node_typ.is_int()) || node_typ == ast.bool_type { if node_typ_is_option { g.expr_with_opt(node.expr, expr_type, node_typ) diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index d760f53ae..d68adf1cb 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -281,6 +281,20 @@ static inline bool _us64_le(uint64_t a, int64_t b) { return a <= INT64_MAX && (i static inline bool _us64_lt(uint64_t a, int64_t b) { return a < INT64_MAX && (int64_t)a < b; } ' +const c_float_to_unsigned_conversion_functions = ' +// deterministic float -> u64 conversions for explicit V casts +// direct C casts are undefined for out-of-range values +static inline uint64_t _v_f64_to_u64(double x) { + if (!(x >= 0.0)) { + return 0; + } + if (x >= 18446744073709551616.0) { + return UINT64_MAX; + } + return (uint64_t)x; +} +' + const c_helper_macros = '//============================== HELPER C MACROS =============================*/ // _SLIT0 is used as NULL string for literal arguments // `"" s` is used to enforce a string literal argument diff --git a/vlib/v/tests/casts/cast_f64_to_u64_boundary_test.v b/vlib/v/tests/casts/cast_f64_to_u64_boundary_test.v new file mode 100644 index 000000000..e3ca552b3 --- /dev/null +++ b/vlib/v/tests/casts/cast_f64_to_u64_boundary_test.v @@ -0,0 +1,14 @@ +import math + +fn test_f64_to_u64_boundary_values() { + max_u64 := u64(-1) + pow_63 := math.pow(2, 63) + pow_64 := math.pow(2, 64) + high_step := pow_63 + 2048.0 + + assert u64(-1.0) == 0 + assert u64(pow_63) == u64(1) << 63 + assert u64(high_step) == (u64(1) << 63) + u64(2048) + assert u64(pow_64) == max_u64 + assert u64(f64(max_u64)) == max_u64 +} -- 2.39.5