authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-11 16:44:41+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-11 16:44:41+02:00
logcbe468a787e93d57ca64799e715ea7493f4a6ebb
tree399a64733142b46d1568982258234db1be107222
parentb6aebc41177dec5d7dddcc7594e5435d54f17963
parent4affe94d79c048fd2dc0c071146e0e842f97b63e

Merge pull request 'Sema: allow @round, @floor, @ceil, and @trunc to coerce to integer types' (#30906) from adria/zig:sema-rounding-casts into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30906

5 files changed, 307 insertions(+), 15 deletions(-)

lib/std/zig/AstGen.zig+41-4
...@@ -9266,10 +9266,10 @@ fn builtinCall(...@@ -9266,10 +9266,10 @@ fn builtinCall(
9266 .log => return floatUnOp(gz, scope, ri, node, params[0], .log),9266 .log => return floatUnOp(gz, scope, ri, node, params[0], .log),
9267 .log2 => return floatUnOp(gz, scope, ri, node, params[0], .log2),9267 .log2 => return floatUnOp(gz, scope, ri, node, params[0], .log2),
9268 .log10 => return floatUnOp(gz, scope, ri, node, params[0], .log10),9268 .log10 => return floatUnOp(gz, scope, ri, node, params[0], .log10),
9269 .floor => return floatUnOp(gz, scope, ri, node, params[0], .floor),9269 .floor => return floatRoundOp(gz, scope, ri, node, params[0], .floor),
9270 .ceil => return floatUnOp(gz, scope, ri, node, params[0], .ceil),9270 .ceil => return floatRoundOp(gz, scope, ri, node, params[0], .ceil),
9271 .trunc => return floatUnOp(gz, scope, ri, node, params[0], .trunc),9271 .trunc => return floatRoundOp(gz, scope, ri, node, params[0], .trunc),
9272 .round => return floatUnOp(gz, scope, ri, node, params[0], .round),9272 .round => return floatRoundOp(gz, scope, ri, node, params[0], .round),
92739273
9274 .int_from_float => return typeCast(gz, scope, ri, node, params[0], .int_from_float, builtin_name),9274 .int_from_float => return typeCast(gz, scope, ri, node, params[0], .int_from_float, builtin_name),
9275 .float_from_int => return typeCast(gz, scope, ri, node, params[0], .float_from_int, builtin_name),9275 .float_from_int => return typeCast(gz, scope, ri, node, params[0], .float_from_int, builtin_name),
...@@ -9819,6 +9819,43 @@ fn simpleUnOp(...@@ -9819,6 +9819,43 @@ fn simpleUnOp(
9819 return rvalue(gz, ri, result, node);9819 return rvalue(gz, ri, result, node);
9820}9820}
98219821
9822fn floatRoundOp(
9823 gz: *GenZir,
9824 scope: *Scope,
9825 ri: ResultInfo,
9826 node: Ast.Node.Index,
9827 operand_node: Ast.Node.Index,
9828 float_tag: Zir.Inst.Tag,
9829) InnerError!Zir.Inst.Ref {
9830 if (try ri.rl.resultType(gz, node)) |dest_type| {
9831 const cursor = maybeAdvanceSourceCursorToMainToken(gz, node);
9832
9833 const operand_ty_inst = try gz.addExtendedPayload(.round_op_ty, Zir.Inst.UnNode{
9834 .node = gz.nodeIndexToRelative(node),
9835 .operand = dest_type,
9836 });
9837
9838 const operand = try expr(gz, scope, .{ .rl = .{ .coerced_ty = operand_ty_inst } }, operand_node);
9839
9840 try emitDbgStmt(gz, cursor);
9841 const round_op: Zir.Inst.RoundOp = switch (float_tag) {
9842 .round => .round,
9843 .floor => .floor,
9844 .ceil => .ceil,
9845 .trunc => .trunc,
9846 else => unreachable,
9847 };
9848 const result = try gz.addExtendedPayloadSmall(.round_op, @intFromEnum(round_op), Zir.Inst.BinNode{
9849 .node = gz.nodeIndexToRelative(node),
9850 .lhs = dest_type,
9851 .rhs = operand,
9852 });
9853 return rvalue(gz, ri, result, node);
9854 } else {
9855 return floatUnOp(gz, scope, ri, node, operand_node, float_tag);
9856 }
9857}
9858
9822fn floatUnOp(9859fn floatUnOp(
9823 gz: *GenZir,9860 gz: *GenZir,
9824 scope: *Scope,9861 scope: *Scope,
lib/std/zig/Zir.zig+17
...@@ -2009,6 +2009,14 @@ pub const Inst = struct {...@@ -2009,6 +2009,14 @@ pub const Inst = struct {
2009 /// `operand` is payload index to `BinNode`.2009 /// `operand` is payload index to `BinNode`.
2010 /// `small` is unused.2010 /// `small` is unused.
2011 shl_with_overflow,2011 shl_with_overflow,
2012 /// `@round`, `@floor`, `@ceil`, or `@trunc`, with a result type.
2013 /// `operand` is payload index to `BinNode`.
2014 /// `small` is a `RoundOp` representing the specific operation being performed.
2015 round_op,
2016 /// Returns the type for the operand of a rounding op.
2017 /// `operand` is `UnNode`.
2018 /// `small` is unused.
2019 round_op_ty,
2012 /// `operand` is payload index to `UnNode`.2020 /// `operand` is payload index to `UnNode`.
2013 c_undef,2021 c_undef,
2014 /// `operand` is payload index to `UnNode`.2022 /// `operand` is payload index to `UnNode`.
...@@ -3233,6 +3241,13 @@ pub const Inst = struct {...@@ -3233,6 +3241,13 @@ pub const Inst = struct {
3233 string_to_union_field_attrs,3241 string_to_union_field_attrs,
3234 };3242 };
32353243
3244 pub const RoundOp = enum(u16) {
3245 round,
3246 floor,
3247 ceil,
3248 trunc,
3249 };
3250
3236 pub const UnNode = struct {3251 pub const UnNode = struct {
3237 node: Ast.Node.Offset,3252 node: Ast.Node.Offset,
3238 operand: Ref,3253 operand: Ref,
...@@ -4344,6 +4359,7 @@ fn findTrackableInner(...@@ -4344,6 +4359,7 @@ fn findTrackableInner(
4344 .sub_with_overflow,4359 .sub_with_overflow,
4345 .mul_with_overflow,4360 .mul_with_overflow,
4346 .shl_with_overflow,4361 .shl_with_overflow,
4362 .round_op,
4347 .c_undef,4363 .c_undef,
4348 .c_include,4364 .c_include,
4349 .c_define,4365 .c_define,
...@@ -4385,6 +4401,7 @@ fn findTrackableInner(...@@ -4385,6 +4401,7 @@ fn findTrackableInner(
4385 .dbg_empty_stmt,4401 .dbg_empty_stmt,
4386 .astgen_error,4402 .astgen_error,
4387 .float_op_result_ty,4403 .float_op_result_ty,
4404 .round_op_ty,
4388 => return,4405 => return,
43894406
4390 // `@TypeOf` has a body.4407 // `@TypeOf` has a body.
src/Sema.zig+173-11
...@@ -1405,6 +1405,8 @@ fn analyzeBodyInner(...@@ -1405,6 +1405,8 @@ fn analyzeBodyInner(
1405 .@"asm" => try sema.zirAsm( block, extended, false),1405 .@"asm" => try sema.zirAsm( block, extended, false),
1406 .asm_expr => try sema.zirAsm( block, extended, true),1406 .asm_expr => try sema.zirAsm( block, extended, true),
1407 .typeof_peer => try sema.zirTypeofPeer( block, extended, inst),1407 .typeof_peer => try sema.zirTypeofPeer( block, extended, inst),
1408 .round_op => try sema.zirRoundCast( block, extended),
1409 .round_op_ty => try sema.zirRoundOpType( block, extended),
1408 .compile_log => try sema.zirCompileLog( block, extended),1410 .compile_log => try sema.zirCompileLog( block, extended),
1409 .min_multi => try sema.zirMinMaxMulti( block, extended, .min),1411 .min_multi => try sema.zirMinMaxMulti( block, extended, .min),
1410 .max_multi => try sema.zirMinMaxMulti( block, extended, .max),1412 .max_multi => try sema.zirMinMaxMulti( block, extended, .max),
...@@ -19685,21 +19687,16 @@ fn maybeConstantUnaryMath(...@@ -19685,21 +19687,16 @@ fn maybeConstantUnaryMath(
19685 return null;19687 return null;
19686}19688}
1968719689
19688fn zirUnaryMath(19690fn unaryMath(
19689 sema: *Sema,19691 sema: *Sema,
19690 block: *Block,19692 block: *Block,
19691 inst: Zir.Inst.Index,19693 operand_src: LazySrcLoc,
19694 operand: Air.Inst.Ref,
19692 air_tag: Air.Inst.Tag,19695 air_tag: Air.Inst.Tag,
19693 comptime eval: fn (Value, Type, Allocator, Zcu.PerThread) Allocator.Error!Value,19696 comptime eval: fn (Value, Type, Allocator, Zcu.PerThread) Allocator.Error!Value,
19694) CompileError!Air.Inst.Ref {19697) CompileError!Air.Inst.Ref {
19695 const tracy = trace(@src());
19696 defer tracy.end();
19697
19698 const pt = sema.pt;19698 const pt = sema.pt;
19699 const zcu = pt.zcu;19699 const zcu = pt.zcu;
19700 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
19701 const operand = sema.resolveInst(inst_data.operand);
19702 const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
19703 const operand_ty = sema.typeOf(operand);19700 const operand_ty = sema.typeOf(operand);
19704 const scalar_ty = operand_ty.scalarType(zcu);19701 const scalar_ty = operand_ty.scalarType(zcu);
1970519702
...@@ -19719,6 +19716,23 @@ fn zirUnaryMath(...@@ -19719,6 +19716,23 @@ fn zirUnaryMath(
19719 };19716 };
19720}19717}
1972119718
19719fn zirUnaryMath(
19720 sema: *Sema,
19721 block: *Block,
19722 inst: Zir.Inst.Index,
19723 air_tag: Air.Inst.Tag,
19724 comptime eval: fn (Value, Type, Allocator, Zcu.PerThread) Allocator.Error!Value,
19725) CompileError!Air.Inst.Ref {
19726 const tracy = trace(@src());
19727 defer tracy.end();
19728
19729 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
19730 const operand = sema.resolveInst(inst_data.operand);
19731 const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
19732
19733 return sema.unaryMath(block, operand_src, operand, air_tag, eval);
19734}
19735
19722fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {19736fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
19723 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;19737 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
19724 const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);19738 const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
...@@ -20900,6 +20914,130 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro...@@ -20900,6 +20914,130 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
20900 }, dest_ty, operand);20914 }, dest_ty, operand);
20901}20915}
2090220916
20917fn zirRoundCast(
20918 sema: *Sema,
20919 block: *Block,
20920 extended: Zir.Inst.Extended.InstData,
20921) CompileError!Air.Inst.Ref {
20922 const pt = sema.pt;
20923 const zcu = pt.zcu;
20924 const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
20925 const src = block.nodeOffset(extra.node);
20926 const operand_src = block.builtinCallArgSrc(extra.node, 0);
20927
20928 const operand = sema.resolveInst(extra.rhs);
20929
20930 const round_op: Zir.Inst.RoundOp = @enumFromInt(extended.small);
20931 const mode: IntFromFloatMode = switch (round_op) {
20932 .round => .round,
20933 .floor => .floor,
20934 .ceil => .ceil,
20935 .trunc => .truncate,
20936 };
20937
20938 const dest_ty = (try sema.resolveTypeOrPoison(block, src, extra.lhs) orelse switch (mode) {
20939 // zig fmt: off
20940 .round => return sema.unaryMath(block, operand_src, operand, .round, Value.round),
20941 .floor => return sema.unaryMath(block, operand_src, operand, .floor, Value.floor),
20942 .ceil => return sema.unaryMath(block, operand_src, operand, .ceil, Value.ceil),
20943 .truncate => return sema.unaryMath(block, operand_src, operand, .trunc_float, Value.trunc),
20944 // zig fmt: on
20945 .exact => unreachable,
20946 }).optEuBaseType(zcu);
20947
20948 const operand_ty = sema.typeOf(operand);
20949
20950 try sema.checkVectorizableBinaryOperands(block, operand_src, dest_ty, operand_ty, src, operand_src);
20951
20952 const dest_scalar_ty = dest_ty.scalarType(zcu);
20953 const operand_scalar_ty = operand_ty.scalarType(zcu);
20954
20955 switch (operand_scalar_ty.zigTypeTag(zcu)) {
20956 .comptime_float, .float => {},
20957 else => return sema.fail(
20958 block,
20959 operand_src,
20960 "expected float or vector type, found '{f}'",
20961 .{operand_ty.fmt(pt)},
20962 ),
20963 }
20964
20965 switch (dest_scalar_ty.zigTypeTag(zcu)) {
20966 .float, .comptime_float => {
20967 const coerced_operand = try sema.coerce(block, dest_ty, operand, operand_src);
20968
20969 const result_ref = switch (mode) {
20970 .round => try sema.maybeConstantUnaryMath(coerced_operand, dest_ty, Value.round),
20971 .floor => try sema.maybeConstantUnaryMath(coerced_operand, dest_ty, Value.floor),
20972 .ceil => try sema.maybeConstantUnaryMath(coerced_operand, dest_ty, Value.ceil),
20973 .truncate => try sema.maybeConstantUnaryMath(coerced_operand, dest_ty, Value.trunc),
20974 .exact => unreachable,
20975 };
20976
20977 if (result_ref) |ref| return ref;
20978
20979 const air_tag: Air.Inst.Tag = switch (mode) {
20980 .round => .round,
20981 .floor => .floor,
20982 .ceil => .ceil,
20983 .truncate => .trunc_float,
20984 .exact => unreachable,
20985 };
20986
20987 try sema.requireRuntimeBlock(block, operand_src, null);
20988 return block.addUnOp(air_tag, coerced_operand);
20989 },
20990 .int, .comptime_int => {},
20991 else => return sema.fail(
20992 block,
20993 src,
20994 "expected integer, float, or vector of either integers or floats, found '{f}'",
20995 .{dest_ty.fmt(pt)},
20996 ),
20997 }
20998
20999 if (sema.resolveValue(operand)) |operand_val| {
21000 const result_val = try sema.intFromFloat(block, operand_src, operand_val, operand_ty, dest_ty, mode);
21001 return .fromValue(result_val);
21002 } else if (dest_scalar_ty.zigTypeTag(zcu) == .comptime_int) {
21003 return sema.failWithNeededComptime(block, operand_src, .{ .simple = .casted_to_comptime_int });
21004 }
21005
21006 try sema.requireRuntimeBlock(block, src, operand_src);
21007
21008 if (dest_scalar_ty.intInfo(zcu).bits == 0) {
21009 if (block.wantSafety()) {
21010 const abs_ref = try block.addTyOp(.abs, operand_ty, operand);
21011 const is_vector = dest_ty.zigTypeTag(zcu) == .vector;
21012 const max_abs_ref = if (is_vector) try block.addReduce(abs_ref, .Max) else abs_ref;
21013 const one_ref = Air.internedToRef((try pt.floatValue(operand_scalar_ty, 1.0)).toIntern());
21014 const ok_ref = try block.addBinOp(.cmp_lt, max_abs_ref, one_ref);
21015 try sema.addSafetyCheck(block, src, ok_ref, .integer_part_out_of_bounds);
21016 }
21017 const scalar_val = try pt.intValue(dest_scalar_ty, 0);
21018 return Air.internedToRef((try sema.splat(dest_ty, scalar_val)).toIntern());
21019 }
21020
21021 const safe = block.wantSafety();
21022
21023 if (safe) {
21024 try sema.preparePanicId(src, .integer_part_out_of_bounds);
21025 }
21026
21027 const uncasted_result: Air.Inst.Ref = switch (mode) {
21028 .truncate => operand,
21029 .round => try block.addUnOp(.round, operand),
21030 .floor => try block.addUnOp(.floor, operand),
21031 .ceil => try block.addUnOp(.ceil, operand),
21032 .exact => unreachable,
21033 };
21034 const air_cast_tag: Air.Inst.Tag = switch (block.float_mode) {
21035 .optimized => if (safe) .int_from_float_optimized_safe else .int_from_float_optimized,
21036 .strict => if (safe) .int_from_float_safe else .int_from_float,
21037 };
21038 return block.addTyOp(air_cast_tag, dest_ty, uncasted_result);
21039}
21040
20903fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {21041fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
20904 const pt = sema.pt;21042 const pt = sema.pt;
20905 const zcu = pt.zcu;21043 const zcu = pt.zcu;
...@@ -25078,6 +25216,23 @@ fn zirFloatOpResultType(sema: *Sema, block: *Block, extended: Zir.Inst.Extended....@@ -25078,6 +25216,23 @@ fn zirFloatOpResultType(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.
25078 return .fromType(float_ty);25216 return .fromType(float_ty);
25079}25217}
2508025218
25219fn zirRoundOpType(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
25220 const pt = sema.pt;
25221 const zcu = pt.zcu;
25222 const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
25223 const operand_src = block.builtinCallArgSrc(extra.node, 0);
25224
25225 const dest_ty = try sema.resolveTypeOrPoison(block, operand_src, extra.operand) orelse {
25226 return .generic_poison_type;
25227 };
25228
25229 const float_ty = dest_ty.optEuBaseType(zcu);
25230 switch (float_ty.scalarType(zcu).zigTypeTag(zcu)) {
25231 .float, .comptime_float => return .fromType(float_ty),
25232 else => return .comptime_float_type,
25233 }
25234}
25235
25081fn requireRuntimeBlock(sema: *Sema, block: *Block, src: LazySrcLoc, runtime_src: ?LazySrcLoc) !void {25236fn requireRuntimeBlock(sema: *Sema, block: *Block, src: LazySrcLoc, runtime_src: ?LazySrcLoc) !void {
25082 if (block.isComptime()) {25237 if (block.isComptime()) {
25083 const msg, const fail_block = msg: {25238 const msg, const fail_block = msg: {
...@@ -33467,7 +33622,7 @@ fn structFieldIndex(...@@ -33467,7 +33622,7 @@ fn structFieldIndex(
33467 return sema.failWithBadStructFieldAccess(block, struct_ty, struct_type, field_src, field_name);33622 return sema.failWithBadStructFieldAccess(block, struct_ty, struct_type, field_src, field_name);
33468}33623}
3346933624
33470const IntFromFloatMode = enum { exact, truncate };33625const IntFromFloatMode = enum { exact, truncate, round, floor, ceil };
3347133626
33472fn intFromFloat(33627fn intFromFloat(
33473 sema: *Sema,33628 sema: *Sema,
...@@ -33505,7 +33660,14 @@ fn intFromFloatScalar(...@@ -33505,7 +33660,14 @@ fn intFromFloatScalar(
3350533660
33506 if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src, vec_idx);33661 if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src, vec_idx);
3350733662
33508 const float = val.toFloat(f128, zcu);33663 var float = val.toFloat(f128, zcu);
33664 switch (mode) {
33665 .round => float = @round(float),
33666 .floor => float = @floor(float),
33667 .ceil => float = @ceil(float),
33668 .truncate, .exact => {},
33669 }
33670
33509 if (std.math.isNan(float)) {33671 if (std.math.isNan(float)) {
33510 return sema.fail(block, src, "float value NaN cannot be stored in integer type '{f}'", .{33672 return sema.fail(block, src, "float value NaN cannot be stored in integer type '{f}'", .{
33511 int_ty.fmt(pt),33673 int_ty.fmt(pt),
...@@ -33530,7 +33692,7 @@ fn intFromFloatScalar(...@@ -33530,7 +33692,7 @@ fn intFromFloatScalar(
33530 "fractional component prevents float value '{f}' from coercion to type '{f}'",33692 "fractional component prevents float value '{f}' from coercion to type '{f}'",
33531 .{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },33693 .{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },
33532 ),33694 ),
33533 .truncate => {},33695 .truncate, .round, .floor, .ceil => {},
33534 },33696 },
33535 .exact => {},33697 .exact => {},
33536 }33698 }
src/print_zir.zig+12
...@@ -570,6 +570,7 @@ const Writer = struct {...@@ -570,6 +570,7 @@ const Writer = struct {
570 .float_op_result_ty,570 .float_op_result_ty,
571 .reify_tuple,571 .reify_tuple,
572 .reify_pointer_sentinel_ty,572 .reify_pointer_sentinel_ty,
573 .round_op_ty,
573 => {574 => {
574 const inst_data = self.code.extraData(Zir.Inst.UnNode, extended.operand).data;575 const inst_data = self.code.extraData(Zir.Inst.UnNode, extended.operand).data;
575 try self.writeInstRef(stream, inst_data.operand);576 try self.writeInstRef(stream, inst_data.operand);
...@@ -593,6 +594,17 @@ const Writer = struct {...@@ -593,6 +594,17 @@ const Writer = struct {
593 try self.writeSrcNode(stream, inst_data.node);594 try self.writeSrcNode(stream, inst_data.node);
594 },595 },
595596
597 .round_op => {
598 const round_op: Zir.Inst.RoundOp = @enumFromInt(extended.small);
599 const inst_data = self.code.extraData(Zir.Inst.BinNode, extended.operand).data;
600 try stream.print("{s}, ", .{@tagName(round_op)});
601 try self.writeInstRef(stream, inst_data.lhs);
602 try stream.writeAll(", ");
603 try self.writeInstRef(stream, inst_data.rhs);
604 try stream.writeAll(")) ");
605 try self.writeSrcNode(stream, inst_data.node);
606 },
607
596 .reify_slice_arg_ty => {608 .reify_slice_arg_ty => {
597 const reify_slice_arg_info: Zir.Inst.ReifySliceArgInfo = @enumFromInt(extended.small);609 const reify_slice_arg_info: Zir.Inst.ReifySliceArgInfo = @enumFromInt(extended.small);
598 const extra = self.code.extraData(Zir.Inst.UnNode, extended.operand).data;610 const extra = self.code.extraData(Zir.Inst.UnNode, extended.operand).data;
test/behavior/cast.zig+64
...@@ -116,6 +116,10 @@ test "@floatFromInt" {...@@ -116,6 +116,10 @@ test "@floatFromInt" {
116 const f = @as(f32, @floatFromInt(k));116 const f = @as(f32, @floatFromInt(k));
117 const i = @as(i32, @intFromFloat(f));117 const i = @as(i32, @intFromFloat(f));
118 try expect(i == k);118 try expect(i == k);
119 try expect(@as(i32, @round(f)) == k);
120 try expect(@as(i32, @floor(f)) == k);
121 try expect(@as(i32, @ceil(f)) == k);
122 try expect(@as(i32, @trunc(f)) == k);
119 }123 }
120 };124 };
121 try S.doTheTest();125 try S.doTheTest();
...@@ -139,6 +143,10 @@ test "@floatFromInt(f80)" {...@@ -139,6 +143,10 @@ test "@floatFromInt(f80)" {
139 const f = @as(f80, @floatFromInt(k));143 const f = @as(f80, @floatFromInt(k));
140 const i = @as(Int, @intFromFloat(f));144 const i = @as(Int, @intFromFloat(f));
141 try expect(i == k);145 try expect(i == k);
146 try expect(@as(Int, @round(f)) == k);
147 try expect(@as(Int, @floor(f)) == k);
148 try expect(@as(Int, @ceil(f)) == k);
149 try expect(@as(Int, @trunc(f)) == k);
142 }150 }
143 };151 };
144 try S.doTheTest(i31);152 try S.doTheTest(i31);
...@@ -167,6 +175,10 @@ test "type coercion from int to float" {...@@ -167,6 +175,10 @@ test "type coercion from int to float" {
167 try std.testing.expectEqual(int, @as(Int, @intFromFloat(float)));175 try std.testing.expectEqual(int, @as(Int, @intFromFloat(float)));
168 try std.testing.expectEqual(int, @as(Int, @intFromFloat(@ceil(float))));176 try std.testing.expectEqual(int, @as(Int, @intFromFloat(@ceil(float))));
169 try std.testing.expectEqual(int, @as(Int, @intFromFloat(@floor(float))));177 try std.testing.expectEqual(int, @as(Int, @intFromFloat(@floor(float))));
178 try std.testing.expectEqual(int, @as(Int, @round(float)));
179 try std.testing.expectEqual(int, @as(Int, @ceil(float)));
180 try std.testing.expectEqual(int, @as(Int, @floor(float)));
181 try std.testing.expectEqual(int, @as(Int, @trunc(float)));
170 }182 }
171183
172 // Exhaustively check that all possible values of the integer type can184 // Exhaustively check that all possible values of the integer type can
...@@ -230,12 +242,54 @@ fn testIntFromFloats() !void {...@@ -230,12 +242,54 @@ fn testIntFromFloats() !void {
230 try expectIntFromFloat(f32, 255.1, u8, 255);242 try expectIntFromFloat(f32, 255.1, u8, 255);
231 try expectIntFromFloat(f32, 127.2, i8, 127);243 try expectIntFromFloat(f32, 127.2, i8, 127);
232 try expectIntFromFloat(f32, -128.2, i8, -128);244 try expectIntFromFloat(f32, -128.2, i8, -128);
245
246 try expectRoundCast(f32, 255.1, u8, 255);
247 try expectFloorCast(f32, 255.1, u8, 255);
248 try expectTruncCast(f32, 255.1, u8, 255);
249
250 try expectRoundCast(f32, 127.2, i8, 127);
251 try expectFloorCast(f32, 127.2, i8, 127);
252 try expectTruncCast(f32, 127.2, i8, 127);
253
254 try expectRoundCast(f32, -128.2, i8, -128);
255 try expectCeilCast(f32, -128.2, i8, -128);
256 try expectTruncCast(f32, -128.2, i8, -128);
257}
258
259test "rounding builtins with anytype and context propagation" {
260 const S = struct {
261 const x: i32 = 10;
262 fn check(expected: anytype, actual: anytype) !void {
263 try expectEqual(expected, actual);
264 }
265 };
266 try expectEqual(@as(f32, 1.0), @round(@as(f32, 1.4)));
267 try S.check(@as(f32, 1.0), @round(@as(f32, 1.4)));
268
269 const y: f64 = @floor(@floatFromInt(S.x));
270 try expect(y == 10.0);
271
272 try expectEqual(1.0, @round(1.4));
273 try S.check(1.0, @round(1.4));
233}274}
234275
235fn expectIntFromFloat(comptime F: type, f: F, comptime I: type, i: I) !void {276fn expectIntFromFloat(comptime F: type, f: F, comptime I: type, i: I) !void {
236 try expect(@as(I, @intFromFloat(f)) == i);277 try expect(@as(I, @intFromFloat(f)) == i);
237}278}
238279
280fn expectRoundCast(comptime F: type, f: F, comptime I: type, i: I) !void {
281 try expect(@as(I, @round(f)) == i);
282}
283fn expectFloorCast(comptime F: type, f: F, comptime I: type, i: I) !void {
284 try expect(@as(I, @floor(f)) == i);
285}
286fn expectCeilCast(comptime F: type, f: F, comptime I: type, i: I) !void {
287 try expect(@as(I, @ceil(f)) == i);
288}
289fn expectTruncCast(comptime F: type, f: F, comptime I: type, i: I) !void {
290 try expect(@as(I, @trunc(f)) == i);
291}
292
239test "implicitly cast indirect pointer to maybe-indirect pointer" {293test "implicitly cast indirect pointer to maybe-indirect pointer" {
240 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO294 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
241 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;295 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
...@@ -1309,6 +1363,12 @@ test "comptime float casts" {...@@ -1309,6 +1363,12 @@ test "comptime float casts" {
13091363
1310 try expectIntFromFloat(comptime_int, 1234, i16, 1234);1364 try expectIntFromFloat(comptime_int, 1234, i16, 1234);
1311 try expectIntFromFloat(comptime_float, 12.3, comptime_int, 12);1365 try expectIntFromFloat(comptime_float, 12.3, comptime_int, 12);
1366
1367 try expectRoundCast(comptime_float, 12.3, comptime_int, 12);
1368
1369 try expectFloorCast(comptime_float, 12.3, comptime_int, 12);
1370 try expectCeilCast(comptime_float, 12.3, comptime_int, 13);
1371 try expectTruncCast(comptime_float, 12.3, comptime_int, 12);
1312}1372}
13131373
1314test "pointer reinterpret const float to int" {1374test "pointer reinterpret const float to int" {
...@@ -1756,6 +1816,10 @@ test "intFromFloat to zero-bit int" {...@@ -1756,6 +1816,10 @@ test "intFromFloat to zero-bit int" {
17561816
1757 const a: f32 = 0.0;1817 const a: f32 = 0.0;
1758 try comptime std.testing.expect(@as(u0, @intFromFloat(a)) == 0);1818 try comptime std.testing.expect(@as(u0, @intFromFloat(a)) == 0);
1819 try comptime std.testing.expect(@as(u0, @round(a)) == 0);
1820 try comptime std.testing.expect(@as(u0, @floor(a)) == 0);
1821 try comptime std.testing.expect(@as(u0, @ceil(a)) == 0);
1822 try comptime std.testing.expect(@as(u0, @trunc(a)) == 0);
1759}1823}
17601824
1761test "peer type resolution of function pointer and function body" {1825test "peer type resolution of function pointer and function body" {