| author | |
| committer | |
| log | 484cc15366eb2654be07e449c69ff9b4d04d5973 |
| tree | e0acce4416e3f6fd78c08cd5906d0284dc94ba26 |
| parent | 5082e85de95d491b52fbff2c64cfd5b7190ddc10 |
If the float can store all possible values of the integer without
rounding, coercion is allowed. The integer's precision must be less than
or equal to the float's significand precision.
Closes #186146 files changed, 151 insertions(+), 2 deletions(-)
doc/langref.html.in+10| ... | @@ -3449,6 +3449,16 @@ void do_a_thing(struct Foo *foo) { | ... | @@ -3449,6 +3449,16 @@ void do_a_thing(struct Foo *foo) { |
| 3449 | </p> | 3449 | </p> |
| 3450 | {#code|test_integer_widening.zig#} | 3450 | {#code|test_integer_widening.zig#} |
| 3451 | 3451 | ||
| 3452 | {#header_close#} | ||
| 3453 | {#header_open|Type Coercion: Int to Float#} | ||
| 3454 | <p> | ||
| 3455 | {#link|Integers#} coerce to {#link|Floats#} if every possible integer value can be stored in the float | ||
| 3456 | without rounding (i.e. the integer's precision does not exceed the float's significand precision). | ||
| 3457 | Larger integer types that cannot be safely coerced must be explicitly casted with {#link|@floatFromInt#}. | ||
| 3458 | </p> | ||
| 3459 | {#code|test_int_to_float_coercion.zig#} | ||
| 3460 | {#code|test_failed_int_to_float_coercion.zig#} | ||
| 3461 | |||
| 3452 | {#header_close#} | 3462 | {#header_close#} |
| 3453 | {#header_open|Type Coercion: Float to Int#} | 3463 | {#header_open|Type Coercion: Float to Int#} |
| 3454 | <p> | 3464 | <p> |
doc/langref/test_failed_int_to_float_coercion.zig created+8| ... | @@ -0,0 +1,8 @@ | ||
| 1 | test "integer type is too large for implicit cast to float" { | ||
| 2 | var int: u25 = 123; | ||
| 3 | _ = &int; | ||
| 4 | const float: f32 = int; | ||
| 5 | _ = float; | ||
| 6 | } | ||
| 7 | |||
| 8 | // test_error= | ||
doc/langref/test_int_to_float_coercion.zig created+12| ... | @@ -0,0 +1,12 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const expectEqual = std.testing.expectEqual; | ||
| 3 | |||
| 4 | test "implicit integer to float" { | ||
| 5 | var int: u8 = 123; | ||
| 6 | _ = &int; | ||
| 7 | const float: f32 = int; | ||
| 8 | const int_from_float: u8 = @intFromFloat(float); | ||
| 9 | try expectEqual(int, int_from_float); | ||
| 10 | } | ||
| 11 | |||
| 12 | // test | ||
src/Sema.zig+16-2| ... | @@ -28715,7 +28715,7 @@ pub fn coerce( | ... | @@ -28715,7 +28715,7 @@ pub fn coerce( |
| 28715 | }; | 28715 | }; |
| 28716 | } | 28716 | } |
| 28717 | 28717 | ||
| 28718 | const CoersionError = CompileError || error{ | 28718 | const CoercionError = CompileError || error{ |
| 28719 | /// When coerce is called recursively, this error should be returned instead of using `fail` | 28719 | /// When coerce is called recursively, this error should be returned instead of using `fail` |
| 28720 | /// to ensure correct types in compile errors. | 28720 | /// to ensure correct types in compile errors. |
| 28721 | NotCoercible, | 28721 | NotCoercible, |
| ... | @@ -28754,7 +28754,7 @@ fn coerceExtra( | ... | @@ -28754,7 +28754,7 @@ fn coerceExtra( |
| 28754 | inst: Air.Inst.Ref, | 28754 | inst: Air.Inst.Ref, |
| 28755 | inst_src: LazySrcLoc, | 28755 | inst_src: LazySrcLoc, |
| 28756 | opts: CoerceOpts, | 28756 | opts: CoerceOpts, |
| 28757 | ) CoersionError!Air.Inst.Ref { | 28757 | ) CoercionError!Air.Inst.Ref { |
| 28758 | const pt = sema.pt; | 28758 | const pt = sema.pt; |
| 28759 | const zcu = pt.zcu; | 28759 | const zcu = pt.zcu; |
| 28760 | const comp = zcu.comp; | 28760 | const comp = zcu.comp; |
| ... | @@ -29186,6 +29186,20 @@ fn coerceExtra( | ... | @@ -29186,6 +29186,20 @@ fn coerceExtra( |
| 29186 | if (!opts.report_err) return error.NotCoercible; | 29186 | if (!opts.report_err) return error.NotCoercible; |
| 29187 | return sema.failWithNeededComptime(block, inst_src, .{ .simple = .casted_to_comptime_float }); | 29187 | return sema.failWithNeededComptime(block, inst_src, .{ .simple = .casted_to_comptime_float }); |
| 29188 | } | 29188 | } |
| 29189 | const int_info = inst_ty.intInfo(zcu); | ||
| 29190 | const int_precision = int_info.bits - @intFromBool(int_info.signedness == .signed); | ||
| 29191 | const float_precision: u8 = switch (dest_ty.toIntern()) { | ||
| 29192 | .f16_type => 11, | ||
| 29193 | .f32_type => 24, | ||
| 29194 | .f64_type => 53, | ||
| 29195 | .f80_type => 64, | ||
| 29196 | .f128_type => 113, | ||
| 29197 | else => unreachable, | ||
| 29198 | }; | ||
| 29199 | if (int_precision <= float_precision) { | ||
| 29200 | try sema.requireRuntimeBlock(block, inst_src, null); | ||
| 29201 | return block.addTyOp(.float_from_int, dest_ty, inst); | ||
| 29202 | } | ||
| 29189 | break :int; | 29203 | break :int; |
| 29190 | }; | 29204 | }; |
| 29191 | const result_val = try val.floatFromIntAdvanced(sema.arena, inst_ty, dest_ty, pt, .sema); | 29205 | const result_val = try val.floatFromIntAdvanced(sema.arena, inst_ty, dest_ty, pt, .sema); |
test/behavior/cast.zig+53| ... | @@ -157,6 +157,59 @@ test "@floatFromInt(f80)" { | ... | @@ -157,6 +157,59 @@ test "@floatFromInt(f80)" { |
| 157 | try comptime S.doTheTest(i256); | 157 | try comptime S.doTheTest(i256); |
| 158 | } | 158 | } |
| 159 | 159 | ||
| 160 | test "type coercion from int to float" { | ||
| 161 | const check = struct { | ||
| 162 | // Check that an integer value can be coerced to a float type and | ||
| 163 | // then converted back to the original value without rounding issues. | ||
| 164 | fn value(Float: type, int: anytype) !void { | ||
| 165 | const float: Float = int; | ||
| 166 | const Int = @TypeOf(int); | ||
| 167 | try std.testing.expectEqual(int, @as(Int, @intFromFloat(float))); | ||
| 168 | try std.testing.expectEqual(int, @as(Int, @intFromFloat(@ceil(float)))); | ||
| 169 | try std.testing.expectEqual(int, @as(Int, @intFromFloat(@floor(float)))); | ||
| 170 | } | ||
| 171 | |||
| 172 | // Exhaustively check that all possible values of the integer type can | ||
| 173 | // safely be coerced to the float type. | ||
| 174 | fn allValues(Float: type, Int: type) !void { | ||
| 175 | var int: Int = std.math.minInt(Int); | ||
| 176 | while (int < std.math.maxInt(Int)) : (int += 1) | ||
| 177 | try value(Float, int); | ||
| 178 | } | ||
| 179 | |||
| 180 | // Check that the min and max values of the integer type can safely be | ||
| 181 | // coerced to the float type. | ||
| 182 | fn edgeValues(Float: type, Int: type) !void { | ||
| 183 | var int: Int = std.math.minInt(Int); | ||
| 184 | try value(Float, int); | ||
| 185 | int = std.math.maxInt(Int); | ||
| 186 | try value(Float, int); | ||
| 187 | } | ||
| 188 | }; | ||
| 189 | |||
| 190 | try check.allValues(f16, u11); | ||
| 191 | try check.allValues(f16, i12); | ||
| 192 | |||
| 193 | try check.edgeValues(f32, u24); | ||
| 194 | try check.edgeValues(f32, i25); | ||
| 195 | |||
| 196 | try check.edgeValues(f64, u53); | ||
| 197 | try check.edgeValues(f64, i54); | ||
| 198 | |||
| 199 | try check.edgeValues(f80, u64); | ||
| 200 | try check.edgeValues(f80, i65); | ||
| 201 | |||
| 202 | try check.edgeValues(f128, u113); | ||
| 203 | try check.edgeValues(f128, i114); | ||
| 204 | |||
| 205 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | ||
| 206 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; | ||
| 207 | |||
| 208 | // Basic sanity check that the coercions work for vectors too. | ||
| 209 | const int_vec: @Vector(2, u24) = @splat(123); | ||
| 210 | try check.value(@Vector(2, f32), int_vec); | ||
| 211 | } | ||
| 212 | |||
| 160 | test "@intFromFloat" { | 213 | test "@intFromFloat" { |
| 161 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | 214 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 162 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | 215 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
test/cases/compile_errors/coerce_int_to_float.zig created+52| ... | @@ -0,0 +1,52 @@ | ||
| 1 | // Test that integer types above a certain size will not coerce to a float. | ||
| 2 | |||
| 3 | fn testCoerce(Float: type, Int: type) void { | ||
| 4 | var i: Int = 0; | ||
| 5 | _ = &i; | ||
| 6 | _ = @as(Float, i); | ||
| 7 | } | ||
| 8 | |||
| 9 | export fn entry() void { | ||
| 10 | testCoerce(f16, u11); // Okay | ||
| 11 | testCoerce(f16, u12); // Too big | ||
| 12 | |||
| 13 | testCoerce(f16, i12); | ||
| 14 | testCoerce(f16, i13); | ||
| 15 | |||
| 16 | testCoerce(f32, u24); | ||
| 17 | testCoerce(f32, u25); | ||
| 18 | |||
| 19 | testCoerce(f32, i25); | ||
| 20 | testCoerce(f32, i26); | ||
| 21 | |||
| 22 | testCoerce(f64, u53); | ||
| 23 | testCoerce(f64, u54); | ||
| 24 | |||
| 25 | testCoerce(f64, i54); | ||
| 26 | testCoerce(f64, i55); | ||
| 27 | |||
| 28 | testCoerce(f80, u64); | ||
| 29 | testCoerce(f80, u65); | ||
| 30 | |||
| 31 | testCoerce(f80, i65); | ||
| 32 | testCoerce(f80, i66); | ||
| 33 | |||
| 34 | testCoerce(f128, u113); | ||
| 35 | testCoerce(f128, u114); | ||
| 36 | |||
| 37 | testCoerce(f128, i114); | ||
| 38 | testCoerce(f128, i115); | ||
| 39 | } | ||
| 40 | |||
| 41 | // error | ||
| 42 | // | ||
| 43 | // :6:20: error: expected type 'f16', found 'u12' | ||
| 44 | // :6:20: error: expected type 'f16', found 'i13' | ||
| 45 | // :6:20: error: expected type 'f32', found 'u25' | ||
| 46 | // :6:20: error: expected type 'f32', found 'i26' | ||
| 47 | // :6:20: error: expected type 'f64', found 'u54' | ||
| 48 | // :6:20: error: expected type 'f64', found 'i55' | ||
| 49 | // :6:20: error: expected type 'f80', found 'u65' | ||
| 50 | // :6:20: error: expected type 'f80', found 'i66' | ||
| 51 | // :6:20: error: expected type 'f128', found 'u114' | ||
| 52 | // :6:20: error: expected type 'f128', found 'i115' | ||