authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-06-17 11:02:03+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-06-17 11:02:03+01:00
log561fdd0ed3d93a373f126f4df01caf813ad32fec
tree285a0c41e4951ff6eb503b65dff92b6020538355
parent080ee25ecf1991d85038716ca6199ae8dc31c8f5
parente498d8da3c7a957fe3754db8f3517d71f7fc650e
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24188 from mlugg/intfromfloat-safety

Absorb std.math.big.rational logic into std.math.big.int; fix `@intFromFloat` safety check

39 files changed, 1341 insertions(+), 1082 deletions(-)

lib/compiler/aro/aro/Value.zig+16-26
...@@ -148,35 +148,25 @@ pub fn floatToInt(v: *Value, dest_ty: Type, comp: *Compilation) !FloatToIntChang...@@ -148,35 +148,25 @@ pub fn floatToInt(v: *Value, dest_ty: Type, comp: *Compilation) !FloatToIntChang
148 return .out_of_range;148 return .out_of_range;
149 }149 }
150150
151 const had_fraction = @rem(float_val, 1) != 0;
152 const is_negative = std.math.signbit(float_val);
153 const floored = @floor(@abs(float_val));
154
155 var rational = try std.math.big.Rational.init(comp.gpa);
156 defer rational.deinit();
157 rational.setFloat(f128, floored) catch |err| switch (err) {
158 error.NonFiniteFloat => {
159 v.* = .{};
160 return .overflow;
161 },
162 error.OutOfMemory => return error.OutOfMemory,
163 };
164
165 // The float is reduced in rational.setFloat, so we assert that denominator is equal to one
166 const big_one = BigIntConst{ .limbs = &.{1}, .positive = true };
167 assert(rational.q.toConst().eqlAbs(big_one));
168
169 if (is_negative) {
170 rational.negate();
171 }
172
173 const signedness = dest_ty.signedness(comp);151 const signedness = dest_ty.signedness(comp);
174 const bits: usize = @intCast(dest_ty.bitSizeof(comp).?);152 const bits: usize = @intCast(dest_ty.bitSizeof(comp).?);
175153
176 // rational.p.truncate(rational.p.toConst(), signedness: Signedness, bit_count: usize)154 var big_int: std.math.big.int.Mutable = .{
177 const fits = rational.p.fitsInTwosComp(signedness, bits);155 .limbs = try comp.gpa.alloc(std.math.big.Limb, @max(
178 v.* = try intern(comp, .{ .int = .{ .big_int = rational.p.toConst() } });156 std.math.big.int.calcLimbLen(float_val),
179 try rational.p.truncate(&rational.p, signedness, bits);157 std.math.big.int.calcTwosCompLimbCount(bits),
158 )),
159 .len = undefined,
160 .positive = undefined,
161 };
162 const had_fraction = switch (big_int.setFloat(float_val, .trunc)) {
163 .inexact => true,
164 .exact => false,
165 };
166
167 const fits = big_int.toConst().fitsInTwosComp(signedness, bits);
168 v.* = try intern(comp, .{ .int = .{ .big_int = big_int.toConst() } });
169 big_int.truncate(big_int.toConst(), signedness, bits);
180170
181 if (!was_zero and v.isZero(comp)) return .nonzero_to_zero;171 if (!was_zero and v.isZero(comp)) return .nonzero_to_zero;
182 if (!fits) return .out_of_range;172 if (!fits) return .out_of_range;
lib/std/math.zig+1
...@@ -45,6 +45,7 @@ pub const rad_per_deg = 0.017453292519943295769236907684886127134428718885417254...@@ -45,6 +45,7 @@ pub const rad_per_deg = 0.017453292519943295769236907684886127134428718885417254
45/// 180.0/pi45/// 180.0/pi
46pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861;46pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861;
4747
48pub const FloatRepr = float.FloatRepr;
48pub const floatExponentBits = float.floatExponentBits;49pub const floatExponentBits = float.floatExponentBits;
49pub const floatMantissaBits = float.floatMantissaBits;50pub const floatMantissaBits = float.floatMantissaBits;
50pub const floatFractionalBits = float.floatFractionalBits;51pub const floatFractionalBits = float.floatFractionalBits;
lib/std/math/big.zig-2
...@@ -1,7 +1,6 @@...@@ -1,7 +1,6 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const assert = std.debug.assert;2const assert = std.debug.assert;
33
4pub const Rational = @import("big/rational.zig").Rational;
5pub const int = @import("big/int.zig");4pub const int = @import("big/int.zig");
6pub const Limb = usize;5pub const Limb = usize;
7const limb_info = @typeInfo(Limb).int;6const limb_info = @typeInfo(Limb).int;
...@@ -18,7 +17,6 @@ comptime {...@@ -18,7 +17,6 @@ comptime {
1817
19test {18test {
20 _ = int;19 _ = int;
21 _ = Rational;
22 _ = Limb;20 _ = Limb;
23 _ = SignedLimb;21 _ = SignedLimb;
24 _ = DoubleLimb;22 _ = DoubleLimb;
lib/std/math/big/int.zig+182-46
...@@ -18,17 +18,28 @@ const Signedness = std.builtin.Signedness;...@@ -18,17 +18,28 @@ const Signedness = std.builtin.Signedness;
18const native_endian = builtin.cpu.arch.endian();18const native_endian = builtin.cpu.arch.endian();
1919
20/// Returns the number of limbs needed to store `scalar`, which must be a20/// Returns the number of limbs needed to store `scalar`, which must be a
21/// primitive integer value.21/// primitive integer or float value.
22/// Note: A comptime-known upper bound of this value that may be used22/// Note: A comptime-known upper bound of this value that may be used
23/// instead if `scalar` is not already comptime-known is23/// instead if `scalar` is not already comptime-known is
24/// `calcTwosCompLimbCount(@typeInfo(@TypeOf(scalar)).int.bits)`24/// `calcTwosCompLimbCount(@typeInfo(@TypeOf(scalar)).int.bits)`
25pub fn calcLimbLen(scalar: anytype) usize {25pub fn calcLimbLen(scalar: anytype) usize {
26 if (scalar == 0) {26 switch (@typeInfo(@TypeOf(scalar))) {
27 return 1;27 .int, .comptime_int => {
28 if (scalar == 0) return 1;
29 const w_value = @abs(scalar);
30 return @as(usize, @intCast(@divFloor(@as(Limb, @intCast(math.log2(w_value))), limb_bits) + 1));
31 },
32 .float => {
33 const repr: std.math.FloatRepr(@TypeOf(scalar)) = @bitCast(scalar);
34 return switch (repr.exponent) {
35 .denormal => 1,
36 else => return calcNonZeroTwosCompLimbCount(@as(usize, 2) + @max(repr.exponent.unbias(), 0)),
37 .infinite => 0,
38 };
39 },
40 .comptime_float => return calcLimbLen(@as(f128, scalar)),
41 else => @compileError("expected float or int, got " ++ @typeName(@TypeOf(scalar))),
28 }42 }
29
30 const w_value = @abs(scalar);
31 return @as(usize, @intCast(@divFloor(@as(Limb, @intCast(math.log2(w_value))), limb_bits) + 1));
32}43}
3344
34pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize {45pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize {
...@@ -134,6 +145,22 @@ pub const TwosCompIntLimit = enum {...@@ -134,6 +145,22 @@ pub const TwosCompIntLimit = enum {
134 max,145 max,
135};146};
136147
148pub const Round = enum {
149 /// Round to the nearest representable value, with ties broken by the representation
150 /// that ends with a 0 bit.
151 nearest_even,
152 /// Round away from zero.
153 away,
154 /// Round towards zero.
155 trunc,
156 /// Round towards negative infinity.
157 floor,
158 /// Round towards positive infinity.
159 ceil,
160};
161
162pub const Exactness = enum { inexact, exact };
163
137/// A arbitrary-precision big integer, with a fixed set of mutable limbs.164/// A arbitrary-precision big integer, with a fixed set of mutable limbs.
138pub const Mutable = struct {165pub const Mutable = struct {
139 /// Raw digits. These are:166 /// Raw digits. These are:
...@@ -155,6 +182,20 @@ pub const Mutable = struct {...@@ -155,6 +182,20 @@ pub const Mutable = struct {
155 };182 };
156 }183 }
157184
185 pub const ConvertError = Const.ConvertError;
186
187 /// Convert `self` to `Int`.
188 ///
189 /// Returns an error if self cannot be narrowed into the requested type without truncation.
190 pub fn toInt(self: Mutable, comptime Int: type) ConvertError!Int {
191 return self.toConst().toInt(Int);
192 }
193
194 /// Convert `self` to `Float`.
195 pub fn toFloat(self: Mutable, comptime Float: type, round: Round) struct { Float, Exactness } {
196 return self.toConst().toFloat(Float, round);
197 }
198
158 /// Returns true if `a == 0`.199 /// Returns true if `a == 0`.
159 pub fn eqlZero(self: Mutable) bool {200 pub fn eqlZero(self: Mutable) bool {
160 return self.toConst().eqlZero();201 return self.toConst().eqlZero();
...@@ -401,6 +442,65 @@ pub const Mutable = struct {...@@ -401,6 +442,65 @@ pub const Mutable = struct {
401 }442 }
402 }443 }
403444
445 /// Sets the Mutable to a float value rounded according to `round`.
446 /// Returns whether the conversion was exact (`round` had no effect on the result).
447 pub fn setFloat(self: *Mutable, value: anytype, round: Round) Exactness {
448 const Float = @TypeOf(value);
449 if (Float == comptime_float) return self.setFloat(@as(f128, value), round);
450 const abs_value = @abs(value);
451 if (abs_value < 1.0) {
452 if (abs_value == 0.0) {
453 self.set(0);
454 return .exact;
455 }
456 self.set(@as(i2, round: switch (round) {
457 .nearest_even => if (abs_value <= 0.5) 0 else continue :round .away,
458 .away => if (value < 0.0) -1 else 1,
459 .trunc => 0,
460 .floor => -@as(i2, @intFromBool(value < 0.0)),
461 .ceil => @intFromBool(value > 0.0),
462 }));
463 return .inexact;
464 }
465 const Repr = std.math.FloatRepr(Float);
466 const repr: Repr = @bitCast(value);
467 const exponent = repr.exponent.unbias();
468 assert(exponent >= 0);
469 const int_bit: Repr.Mantissa = 1 << (@bitSizeOf(Repr.Mantissa) - 1);
470 const mantissa = int_bit | repr.mantissa;
471 if (exponent >= @bitSizeOf(Repr.Normalized.Fraction)) {
472 self.set(mantissa);
473 self.shiftLeft(self.toConst(), @intCast(exponent - @bitSizeOf(Repr.Normalized.Fraction)));
474 self.positive = repr.sign == .positive;
475 return .exact;
476 }
477 self.set(mantissa >> @intCast(@bitSizeOf(Repr.Normalized.Fraction) - exponent));
478 const round_bits: Repr.Normalized.Fraction = @truncate(mantissa << @intCast(exponent));
479 if (round_bits == 0) {
480 self.positive = repr.sign == .positive;
481 return .exact;
482 }
483 round: switch (round) {
484 .nearest_even => {
485 const half: Repr.Normalized.Fraction = 1 << (@bitSizeOf(Repr.Normalized.Fraction) - 1);
486 if (round_bits >= half) self.addScalar(self.toConst(), 1);
487 if (round_bits == half) self.limbs[0] &= ~@as(Limb, 1);
488 },
489 .away => self.addScalar(self.toConst(), 1),
490 .trunc => {},
491 .floor => switch (repr.sign) {
492 .positive => {},
493 .negative => continue :round .away,
494 },
495 .ceil => switch (repr.sign) {
496 .positive => continue :round .away,
497 .negative => {},
498 },
499 }
500 self.positive = repr.sign == .positive;
501 return .inexact;
502 }
503
404 /// r = a + scalar504 /// r = a + scalar
405 ///505 ///
406 /// r and a may be aliases.506 /// r and a may be aliases.
...@@ -2117,25 +2217,25 @@ pub const Const = struct {...@@ -2117,25 +2217,25 @@ pub const Const = struct {
2117 /// Deprecated; use `toInt`.2217 /// Deprecated; use `toInt`.
2118 pub const to = toInt;2218 pub const to = toInt;
21192219
2120 /// Convert self to integer type T.2220 /// Convert `self` to `Int`.
2121 ///2221 ///
2122 /// Returns an error if self cannot be narrowed into the requested type without truncation.2222 /// Returns an error if self cannot be narrowed into the requested type without truncation.
2123 pub fn toInt(self: Const, comptime T: type) ConvertError!T {2223 pub fn toInt(self: Const, comptime Int: type) ConvertError!Int {
2124 switch (@typeInfo(T)) {2224 switch (@typeInfo(Int)) {
2125 .int => |info| {2225 .int => |info| {
2126 // Make sure -0 is handled correctly.2226 // Make sure -0 is handled correctly.
2127 if (self.eqlZero()) return 0;2227 if (self.eqlZero()) return 0;
21282228
2129 const UT = std.meta.Int(.unsigned, info.bits);2229 const Unsigned = std.meta.Int(.unsigned, info.bits);
21302230
2131 if (!self.fitsInTwosComp(info.signedness, info.bits)) {2231 if (!self.fitsInTwosComp(info.signedness, info.bits)) {
2132 return error.TargetTooSmall;2232 return error.TargetTooSmall;
2133 }2233 }
21342234
2135 var r: UT = 0;2235 var r: Unsigned = 0;
21362236
2137 if (@sizeOf(UT) <= @sizeOf(Limb)) {2237 if (@sizeOf(Unsigned) <= @sizeOf(Limb)) {
2138 r = @as(UT, @intCast(self.limbs[0]));2238 r = @intCast(self.limbs[0]);
2139 } else {2239 } else {
2140 for (self.limbs[0..self.limbs.len], 0..) |_, ri| {2240 for (self.limbs[0..self.limbs.len], 0..) |_, ri| {
2141 const limb = self.limbs[self.limbs.len - ri - 1];2241 const limb = self.limbs[self.limbs.len - ri - 1];
...@@ -2145,40 +2245,76 @@ pub const Const = struct {...@@ -2145,40 +2245,76 @@ pub const Const = struct {
2145 }2245 }
21462246
2147 if (info.signedness == .unsigned) {2247 if (info.signedness == .unsigned) {
2148 return if (self.positive) @as(T, @intCast(r)) else error.NegativeIntoUnsigned;2248 return if (self.positive) @intCast(r) else error.NegativeIntoUnsigned;
2149 } else {2249 } else {
2150 if (self.positive) {2250 if (self.positive) {
2151 return @intCast(r);2251 return @intCast(r);
2152 } else {2252 } else {
2153 if (math.cast(T, r)) |ok| {2253 if (math.cast(Int, r)) |ok| {
2154 return -ok;2254 return -ok;
2155 } else {2255 } else {
2156 return minInt(T);2256 return minInt(Int);
2157 }2257 }
2158 }2258 }
2159 }2259 }
2160 },2260 },
2161 else => @compileError("expected int type, found '" ++ @typeName(T) ++ "'"),2261 else => @compileError("expected int type, found '" ++ @typeName(Int) ++ "'"),
2162 }2262 }
2163 }2263 }
21642264
2165 /// Convert self to float type T.2265 /// Convert self to `Float`.
2166 pub fn toFloat(self: Const, comptime T: type) T {2266 pub fn toFloat(self: Const, comptime Float: type, round: Round) struct { Float, Exactness } {
2167 if (self.limbs.len == 0) return 0;2267 if (Float == comptime_float) return self.toFloat(f128, round);
21682268 const normalized_abs: Const = .{
2169 const base = std.math.maxInt(std.math.big.Limb) + 1;2269 .limbs = self.limbs[0..llnormalize(self.limbs)],
2170 var result: f128 = 0;2270 .positive = true,
2171 var i: usize = self.limbs.len;2271 };
2172 while (i != 0) {2272 if (normalized_abs.eqlZero()) return .{ if (self.positive) 0.0 else -0.0, .exact };
2173 i -= 1;2273
2174 const limb: f128 = @floatFromInt(self.limbs[i]);2274 const Repr = std.math.FloatRepr(Float);
2175 result = @mulAdd(f128, base, result, limb);2275 var mantissa_limbs: [calcNonZeroTwosCompLimbCount(1 + @bitSizeOf(Repr.Mantissa))]Limb = undefined;
2176 }2276 var mantissa: Mutable = .{
2177 if (self.positive) {2277 .limbs = &mantissa_limbs,
2178 return @floatCast(result);2278 .positive = undefined,
2179 } else {2279 .len = undefined,
2180 return @floatCast(-result);2280 };
2181 }2281 var exponent = normalized_abs.bitCountAbs() - 1;
2282 const exactness: Exactness = exactness: {
2283 if (exponent <= @bitSizeOf(Repr.Normalized.Fraction)) {
2284 mantissa.shiftLeft(normalized_abs, @intCast(@bitSizeOf(Repr.Normalized.Fraction) - exponent));
2285 break :exactness .exact;
2286 }
2287 const shift: usize = @intCast(exponent - @bitSizeOf(Repr.Normalized.Fraction));
2288 mantissa.shiftRight(normalized_abs, shift);
2289 const final_limb_index = (shift - 1) / limb_bits;
2290 const round_bits = normalized_abs.limbs[final_limb_index] << @truncate(-%shift) |
2291 @intFromBool(!std.mem.allEqual(Limb, normalized_abs.limbs[0..final_limb_index], 0));
2292 if (round_bits == 0) break :exactness .exact;
2293 round: switch (round) {
2294 .nearest_even => {
2295 const half: Limb = 1 << (limb_bits - 1);
2296 if (round_bits >= half) mantissa.addScalar(mantissa.toConst(), 1);
2297 if (round_bits == half) mantissa.limbs[0] &= ~@as(Limb, 1);
2298 },
2299 .away => mantissa.addScalar(mantissa.toConst(), 1),
2300 .trunc => {},
2301 .floor => if (!self.positive) continue :round .away,
2302 .ceil => if (self.positive) continue :round .away,
2303 }
2304 break :exactness .inexact;
2305 };
2306 const normalized_res: Repr.Normalized = .{
2307 .fraction = @truncate(mantissa.toInt(Repr.Mantissa) catch |err| switch (err) {
2308 error.NegativeIntoUnsigned => unreachable,
2309 error.TargetTooSmall => fraction: {
2310 assert(mantissa.toConst().orderAgainstScalar(1 << @bitSizeOf(Repr.Mantissa)).compare(.eq));
2311 exponent += 1;
2312 break :fraction 1 << (@bitSizeOf(Repr.Mantissa) - 1);
2313 },
2314 }),
2315 .exponent = std.math.lossyCast(Repr.Normalized.Exponent, exponent),
2316 };
2317 return .{ normalized_res.reconstruct(if (self.positive) .positive else .negative), exactness };
2182 }2318 }
21832319
2184 /// To allow `std.fmt.format` to work with this type.2320 /// To allow `std.fmt.format` to work with this type.
...@@ -2739,16 +2875,16 @@ pub const Managed = struct {...@@ -2739,16 +2875,16 @@ pub const Managed = struct {
2739 /// Deprecated; use `toInt`.2875 /// Deprecated; use `toInt`.
2740 pub const to = toInt;2876 pub const to = toInt;
27412877
2742 /// Convert self to integer type T.2878 /// Convert `self` to `Int`.
2743 ///2879 ///
2744 /// Returns an error if self cannot be narrowed into the requested type without truncation.2880 /// Returns an error if self cannot be narrowed into the requested type without truncation.
2745 pub fn toInt(self: Managed, comptime T: type) ConvertError!T {2881 pub fn toInt(self: Managed, comptime Int: type) ConvertError!Int {
2746 return self.toConst().toInt(T);2882 return self.toConst().toInt(Int);
2747 }2883 }
27482884
2749 /// Convert self to float type T.2885 /// Convert `self` to `Float`.
2750 pub fn toFloat(self: Managed, comptime T: type) T {2886 pub fn toFloat(self: Managed, comptime Float: type, round: Round) struct { Float, Exactness } {
2751 return self.toConst().toFloat(T);2887 return self.toConst().toFloat(Float, round);
2752 }2888 }
27532889
2754 /// Set self from the string representation `value`.2890 /// Set self from the string representation `value`.
...@@ -3807,7 +3943,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) usize {...@@ -3807,7 +3943,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) usize {
38073943
3808 // if the most significant limb becomes 0 after the shift3944 // if the most significant limb becomes 0 after the shift
3809 const shrink = a[a.len - 1] >> bit_shift == 0;3945 const shrink = a[a.len - 1] >> bit_shift == 0;
3810 std.debug.assert(r.len >= a.len - @intFromBool(!shrink));3946 std.debug.assert(r.len >= a.len - @intFromBool(shrink));
38113947
3812 var i: usize = 0;3948 var i: usize = 0;
3813 while (i < a.len - 1) : (i += 1) {3949 while (i < a.len - 1) : (i += 1) {
...@@ -4240,7 +4376,7 @@ test {...@@ -4240,7 +4376,7 @@ test {
42404376
4241const testing_allocator = std.testing.allocator;4377const testing_allocator = std.testing.allocator;
4242test "llshl shift by whole number of limb" {4378test "llshl shift by whole number of limb" {
4243 const padding = std.math.maxInt(Limb);4379 const padding = maxInt(Limb);
42444380
4245 var r: [10]Limb = @splat(padding);4381 var r: [10]Limb = @splat(padding);
42464382
...@@ -4390,8 +4526,8 @@ test "llshr to 0" {...@@ -4390,8 +4526,8 @@ test "llshr to 0" {
4390 try testOneShiftCase(.llshr, .{1, &.{0}, &.{1}});4526 try testOneShiftCase(.llshr, .{1, &.{0}, &.{1}});
4391 try testOneShiftCase(.llshr, .{5, &.{0}, &.{1}});4527 try testOneShiftCase(.llshr, .{5, &.{0}, &.{1}});
4392 try testOneShiftCase(.llshr, .{65, &.{0}, &.{0, 1}});4528 try testOneShiftCase(.llshr, .{65, &.{0}, &.{0, 1}});
4393 try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, std.math.maxInt(Limb)}});4529 try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, maxInt(Limb)}});
4394 try testOneShiftCase(.llshr, .{193, &.{0}, &.{std.math.maxInt(Limb), 1, std.math.maxInt(Limb)}});4530 try testOneShiftCase(.llshr, .{193, &.{0}, &.{maxInt(Limb), 1, maxInt(Limb)}});
4395 try testOneShiftCase(.llshr, .{193, &.{0}, &.{0xdeadbeef, 0xabcdefab, 0x1234}});4531 try testOneShiftCase(.llshr, .{193, &.{0}, &.{0xdeadbeef, 0xabcdefab, 0x1234}});
4396 // zig fmt: on4532 // zig fmt: on
4397}4533}
...@@ -4475,7 +4611,7 @@ fn testOneShiftCase(comptime function: enum { llshr, llshl }, case: Case) !void...@@ -4475,7 +4611,7 @@ fn testOneShiftCase(comptime function: enum { llshr, llshl }, case: Case) !void
4475}4611}
44764612
4477fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case) !void {4613fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case) !void {
4478 const padding = std.math.maxInt(Limb);4614 const padding = maxInt(Limb);
4479 var r: [20]Limb = @splat(padding);4615 var r: [20]Limb = @splat(padding);
44804616
4481 const shift = case[0];4617 const shift = case[0];
...@@ -4492,7 +4628,7 @@ fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case...@@ -4492,7 +4628,7 @@ fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case
4492}4628}
44934629
4494fn testOneShiftCaseAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case, shift_direction: isize) !void {4630fn testOneShiftCaseAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case, shift_direction: isize) !void {
4495 const padding = std.math.maxInt(Limb);4631 const padding = maxInt(Limb);
4496 var r: [60]Limb = @splat(padding);4632 var r: [60]Limb = @splat(padding);
4497 const base = 20;4633 const base = 20;
44984634
lib/std/math/big/int_test.zig+408
...@@ -17,6 +17,12 @@ const minInt = std.math.minInt;...@@ -17,6 +17,12 @@ const minInt = std.math.minInt;
17// They will still run on larger than this and should pass, but the multi-limb code-paths17// They will still run on larger than this and should pass, but the multi-limb code-paths
18// may be untested in some cases.18// may be untested in some cases.
1919
20fn expectNormalized(expected: comptime_int, actual: std.math.big.int.Const) !void {
21 try testing.expectEqual(expected >= 0, actual.positive);
22 try testing.expectEqual(std.math.big.int.calcLimbLen(expected), actual.limbs.len);
23 try testing.expect(actual.orderAgainstScalar(expected).compare(.eq));
24}
25
20test "comptime_int set" {26test "comptime_int set" {
21 comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab;27 comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab;
22 var a = try Managed.initSet(testing.allocator, s);28 var a = try Managed.initSet(testing.allocator, s);
...@@ -85,6 +91,408 @@ test "to target too small error" {...@@ -85,6 +91,408 @@ test "to target too small error" {
85 try testing.expectError(error.TargetTooSmall, a.toInt(u8));91 try testing.expectError(error.TargetTooSmall, a.toInt(u8));
86}92}
8793
94fn setFloat(comptime Float: type) !void {
95 var res_limbs: [std.math.big.int.calcNonZeroTwosCompLimbCount(11)]Limb = undefined;
96 var res: Mutable = .{
97 .limbs = &res_limbs,
98 .len = undefined,
99 .positive = undefined,
100 };
101
102 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .nearest_even));
103 try expectNormalized(-1 << 10, res.toConst());
104 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .away));
105 try expectNormalized(-1 << 10, res.toConst());
106 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .trunc));
107 try expectNormalized(-1 << 10, res.toConst());
108 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .floor));
109 try expectNormalized(-1 << 10, res.toConst());
110 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .ceil));
111 try expectNormalized(-1 << 10, res.toConst());
112
113 try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .nearest_even));
114 try expectNormalized(-2, res.toConst());
115 try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .away));
116 try expectNormalized(-2, res.toConst());
117 try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .trunc));
118 try expectNormalized(-2, res.toConst());
119 try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .floor));
120 try expectNormalized(-2, res.toConst());
121 try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .ceil));
122 try expectNormalized(-2, res.toConst());
123
124 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .nearest_even));
125 try expectNormalized(-2, res.toConst());
126 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .away));
127 try expectNormalized(-2, res.toConst());
128 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .trunc));
129 try expectNormalized(-1, res.toConst());
130 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .floor));
131 try expectNormalized(-2, res.toConst());
132 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .ceil));
133 try expectNormalized(-1, res.toConst());
134
135 try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .nearest_even));
136 try expectNormalized(-1, res.toConst());
137 try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .away));
138 try expectNormalized(-1, res.toConst());
139 try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .trunc));
140 try expectNormalized(-1, res.toConst());
141 try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .floor));
142 try expectNormalized(-1, res.toConst());
143 try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .ceil));
144 try expectNormalized(-1, res.toConst());
145
146 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .nearest_even));
147 try expectNormalized(-1, res.toConst());
148 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .away));
149 try expectNormalized(-1, res.toConst());
150 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .trunc));
151 try expectNormalized(0, res.toConst());
152 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .floor));
153 try expectNormalized(-1, res.toConst());
154 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .ceil));
155 try expectNormalized(0, res.toConst());
156
157 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .nearest_even));
158 try expectNormalized(0, res.toConst());
159 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .away));
160 try expectNormalized(-1, res.toConst());
161 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .trunc));
162 try expectNormalized(0, res.toConst());
163 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .floor));
164 try expectNormalized(-1, res.toConst());
165 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .ceil));
166 try expectNormalized(0, res.toConst());
167
168 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .nearest_even));
169 try expectNormalized(0, res.toConst());
170 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .away));
171 try expectNormalized(-1, res.toConst());
172 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .trunc));
173 try expectNormalized(0, res.toConst());
174 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .floor));
175 try expectNormalized(-1, res.toConst());
176 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .ceil));
177 try expectNormalized(0, res.toConst());
178
179 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .nearest_even));
180 try expectNormalized(0, res.toConst());
181 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .away));
182 try expectNormalized(0, res.toConst());
183 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .trunc));
184 try expectNormalized(0, res.toConst());
185 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .floor));
186 try expectNormalized(0, res.toConst());
187 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .ceil));
188 try expectNormalized(0, res.toConst());
189
190 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .nearest_even));
191 try expectNormalized(0, res.toConst());
192 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .away));
193 try expectNormalized(0, res.toConst());
194 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .trunc));
195 try expectNormalized(0, res.toConst());
196 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .floor));
197 try expectNormalized(0, res.toConst());
198 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .ceil));
199 try expectNormalized(0, res.toConst());
200
201 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .nearest_even));
202 try expectNormalized(0, res.toConst());
203 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .away));
204 try expectNormalized(1, res.toConst());
205 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .trunc));
206 try expectNormalized(0, res.toConst());
207 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .floor));
208 try expectNormalized(0, res.toConst());
209 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .ceil));
210 try expectNormalized(1, res.toConst());
211
212 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .nearest_even));
213 try expectNormalized(0, res.toConst());
214 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .away));
215 try expectNormalized(1, res.toConst());
216 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .trunc));
217 try expectNormalized(0, res.toConst());
218 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .floor));
219 try expectNormalized(0, res.toConst());
220 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .ceil));
221 try expectNormalized(1, res.toConst());
222
223 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .nearest_even));
224 try expectNormalized(1, res.toConst());
225 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .away));
226 try expectNormalized(1, res.toConst());
227 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .trunc));
228 try expectNormalized(0, res.toConst());
229 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .floor));
230 try expectNormalized(0, res.toConst());
231 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .ceil));
232 try expectNormalized(1, res.toConst());
233
234 try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .nearest_even));
235 try expectNormalized(1, res.toConst());
236 try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .away));
237 try expectNormalized(1, res.toConst());
238 try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .trunc));
239 try expectNormalized(1, res.toConst());
240 try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .floor));
241 try expectNormalized(1, res.toConst());
242 try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .ceil));
243 try expectNormalized(1, res.toConst());
244
245 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .nearest_even));
246 try expectNormalized(2, res.toConst());
247 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .away));
248 try expectNormalized(2, res.toConst());
249 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .trunc));
250 try expectNormalized(1, res.toConst());
251 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .floor));
252 try expectNormalized(1, res.toConst());
253 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .ceil));
254 try expectNormalized(2, res.toConst());
255
256 try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .nearest_even));
257 try expectNormalized(2, res.toConst());
258 try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .away));
259 try expectNormalized(2, res.toConst());
260 try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .trunc));
261 try expectNormalized(2, res.toConst());
262 try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .floor));
263 try expectNormalized(2, res.toConst());
264 try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .ceil));
265 try expectNormalized(2, res.toConst());
266
267 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .nearest_even));
268 try expectNormalized(1 << 10, res.toConst());
269 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .away));
270 try expectNormalized(1 << 10, res.toConst());
271 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .trunc));
272 try expectNormalized(1 << 10, res.toConst());
273 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .floor));
274 try expectNormalized(1 << 10, res.toConst());
275 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .ceil));
276 try expectNormalized(1 << 10, res.toConst());
277}
278test setFloat {
279 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
280
281 try setFloat(f16);
282 try setFloat(f32);
283 try setFloat(f64);
284 try setFloat(f80);
285 try setFloat(f128);
286 try setFloat(c_longdouble);
287 try setFloat(comptime_float);
288}
289
290fn toFloat(comptime Float: type) !void {
291 const Result = struct { Float, std.math.big.int.Exactness };
292 const fractional_bits = std.math.floatFractionalBits(Float);
293
294 var int_limbs: [
295 std.math.big.int.calcNonZeroTwosCompLimbCount(2 + fractional_bits)
296 ]Limb = undefined;
297 var int: Mutable = .{
298 .limbs = &int_limbs,
299 .len = undefined,
300 .positive = undefined,
301 };
302
303 int.set(-(1 << (fractional_bits + 1)) - 1);
304 try testing.expectEqual(
305 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
306 int.toFloat(Float, .nearest_even),
307 );
308 try testing.expectEqual(
309 Result{ comptime std.math.nextAfter(
310 Float,
311 -std.math.ldexp(@as(Float, 1), fractional_bits + 1),
312 -std.math.inf(Float),
313 ), .inexact },
314 int.toFloat(Float, .away),
315 );
316 try testing.expectEqual(
317 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
318 int.toFloat(Float, .trunc),
319 );
320 try testing.expectEqual(
321 Result{ comptime std.math.nextAfter(
322 Float,
323 -std.math.ldexp(@as(Float, 1), fractional_bits + 1),
324 -std.math.inf(Float),
325 ), .inexact },
326 int.toFloat(Float, .floor),
327 );
328 try testing.expectEqual(
329 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
330 int.toFloat(Float, .ceil),
331 );
332
333 int.set(-1 << (fractional_bits + 1));
334 try testing.expectEqual(
335 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
336 int.toFloat(Float, .nearest_even),
337 );
338 try testing.expectEqual(
339 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
340 int.toFloat(Float, .away),
341 );
342 try testing.expectEqual(
343 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
344 int.toFloat(Float, .trunc),
345 );
346 try testing.expectEqual(
347 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
348 int.toFloat(Float, .floor),
349 );
350 try testing.expectEqual(
351 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
352 int.toFloat(Float, .ceil),
353 );
354
355 int.set(-(1 << (fractional_bits + 1)) + 1);
356 try testing.expectEqual(
357 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
358 int.toFloat(Float, .nearest_even),
359 );
360 try testing.expectEqual(
361 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
362 int.toFloat(Float, .away),
363 );
364 try testing.expectEqual(
365 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
366 int.toFloat(Float, .trunc),
367 );
368 try testing.expectEqual(
369 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
370 int.toFloat(Float, .floor),
371 );
372 try testing.expectEqual(
373 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
374 int.toFloat(Float, .ceil),
375 );
376
377 int.set(-1 << 10);
378 try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .nearest_even));
379 try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .away));
380 try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .trunc));
381 try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .floor));
382 try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .ceil));
383
384 int.set(-1);
385 try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .nearest_even));
386 try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .away));
387 try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .trunc));
388 try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .floor));
389 try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .ceil));
390
391 int.set(0);
392 try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .nearest_even));
393 try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .away));
394 try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .trunc));
395 try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .floor));
396 try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .ceil));
397
398 int.set(1);
399 try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .nearest_even));
400 try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .away));
401 try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .trunc));
402 try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .floor));
403 try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .ceil));
404
405 int.set(1 << 10);
406 try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .nearest_even));
407 try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .away));
408 try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .trunc));
409 try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .floor));
410 try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .ceil));
411
412 int.set((1 << (fractional_bits + 1)) - 1);
413 try testing.expectEqual(
414 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
415 int.toFloat(Float, .nearest_even),
416 );
417 try testing.expectEqual(
418 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
419 int.toFloat(Float, .away),
420 );
421 try testing.expectEqual(
422 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
423 int.toFloat(Float, .trunc),
424 );
425 try testing.expectEqual(
426 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
427 int.toFloat(Float, .floor),
428 );
429 try testing.expectEqual(
430 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
431 int.toFloat(Float, .ceil),
432 );
433
434 int.set(1 << (fractional_bits + 1));
435 try testing.expectEqual(
436 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
437 int.toFloat(Float, .nearest_even),
438 );
439 try testing.expectEqual(
440 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
441 int.toFloat(Float, .away),
442 );
443 try testing.expectEqual(
444 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
445 int.toFloat(Float, .trunc),
446 );
447 try testing.expectEqual(
448 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
449 int.toFloat(Float, .floor),
450 );
451 try testing.expectEqual(
452 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
453 int.toFloat(Float, .ceil),
454 );
455
456 int.set((1 << (fractional_bits + 1)) + 1);
457 try testing.expectEqual(
458 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
459 int.toFloat(Float, .nearest_even),
460 );
461 try testing.expectEqual(
462 Result{ comptime std.math.nextAfter(
463 Float,
464 std.math.ldexp(@as(Float, 1), fractional_bits + 1),
465 std.math.inf(Float),
466 ), .inexact },
467 int.toFloat(Float, .away),
468 );
469 try testing.expectEqual(
470 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
471 int.toFloat(Float, .trunc),
472 );
473 try testing.expectEqual(
474 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
475 int.toFloat(Float, .floor),
476 );
477 try testing.expectEqual(
478 Result{ comptime std.math.nextAfter(
479 Float,
480 std.math.ldexp(@as(Float, 1), fractional_bits + 1),
481 std.math.inf(Float),
482 ), .inexact },
483 int.toFloat(Float, .ceil),
484 );
485}
486test toFloat {
487 if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24191
488 try toFloat(f16);
489 try toFloat(f32);
490 try toFloat(f64);
491 try toFloat(f80);
492 try toFloat(f128);
493 try toFloat(c_longdouble);
494}
495
88test "normalize" {496test "normalize" {
89 var a = try Managed.init(testing.allocator);497 var a = try Managed.init(testing.allocator);
90 defer a.deinit();498 defer a.deinit();
lib/std/math/big/rational.zig deleted-820
...@@ -1,820 +0,0 @@
1const std = @import("../../std.zig");
2const builtin = @import("builtin");
3const debug = std.debug;
4const math = std.math;
5const mem = std.mem;
6const testing = std.testing;
7const Allocator = mem.Allocator;
8
9const Limb = std.math.big.Limb;
10const DoubleLimb = std.math.big.DoubleLimb;
11const Int = std.math.big.int.Managed;
12const IntConst = std.math.big.int.Const;
13
14/// An arbitrary-precision rational number.
15///
16/// Memory is allocated as needed for operations to ensure full precision is kept. The precision
17/// of a Rational is only bounded by memory.
18///
19/// Rational's are always normalized. That is, for a Rational r = p/q where p and q are integers,
20/// gcd(p, q) = 1 always.
21///
22/// TODO rework this to store its own allocator and use a non-managed big int, to avoid double
23/// allocator storage.
24pub const Rational = struct {
25 /// Numerator. Determines the sign of the Rational.
26 p: Int,
27
28 /// Denominator. Sign is ignored.
29 q: Int,
30
31 /// Create a new Rational. A small amount of memory will be allocated on initialization.
32 /// This will be 2 * Int.default_capacity.
33 pub fn init(a: Allocator) !Rational {
34 var p = try Int.init(a);
35 errdefer p.deinit();
36 return Rational{
37 .p = p,
38 .q = try Int.initSet(a, 1),
39 };
40 }
41
42 /// Frees all memory associated with a Rational.
43 pub fn deinit(self: *Rational) void {
44 self.p.deinit();
45 self.q.deinit();
46 }
47
48 /// Set a Rational from a primitive integer type.
49 pub fn setInt(self: *Rational, a: anytype) !void {
50 try self.p.set(a);
51 try self.q.set(1);
52 }
53
54 /// Set a Rational from a string of the form `A/B` where A and B are base-10 integers.
55 pub fn setFloatString(self: *Rational, str: []const u8) !void {
56 // TODO: Accept a/b fractions and exponent form
57 if (str.len == 0) {
58 return error.InvalidFloatString;
59 }
60
61 const State = enum {
62 Integer,
63 Fractional,
64 };
65
66 var state = State.Integer;
67 var point: ?usize = null;
68
69 var start: usize = 0;
70 if (str[0] == '-') {
71 start += 1;
72 }
73
74 for (str, 0..) |c, i| {
75 switch (state) {
76 State.Integer => {
77 switch (c) {
78 '.' => {
79 state = State.Fractional;
80 point = i;
81 },
82 '0'...'9' => {
83 // okay
84 },
85 else => {
86 return error.InvalidFloatString;
87 },
88 }
89 },
90 State.Fractional => {
91 switch (c) {
92 '0'...'9' => {
93 // okay
94 },
95 else => {
96 return error.InvalidFloatString;
97 },
98 }
99 },
100 }
101 }
102
103 // TODO: batch the multiplies by 10
104 if (point) |i| {
105 try self.p.setString(10, str[0..i]);
106
107 const base = IntConst{ .limbs = &[_]Limb{10}, .positive = true };
108 var local_buf: [@sizeOf(Limb) * Int.default_capacity]u8 align(@alignOf(Limb)) = undefined;
109 var fba = std.heap.FixedBufferAllocator.init(&local_buf);
110 const base_managed = try base.toManaged(fba.allocator());
111
112 var j: usize = start;
113 while (j < str.len - i - 1) : (j += 1) {
114 try self.p.ensureMulCapacity(self.p.toConst(), base);
115 try self.p.mul(&self.p, &base_managed);
116 }
117
118 try self.q.setString(10, str[i + 1 ..]);
119 try self.p.add(&self.p, &self.q);
120
121 try self.q.set(1);
122 var k: usize = i + 1;
123 while (k < str.len) : (k += 1) {
124 try self.q.mul(&self.q, &base_managed);
125 }
126
127 try self.reduce();
128 } else {
129 try self.p.setString(10, str[0..]);
130 try self.q.set(1);
131 }
132 }
133
134 /// Set a Rational from a floating-point value. The rational will have enough precision to
135 /// completely represent the provided float.
136 pub fn setFloat(self: *Rational, comptime T: type, f: T) !void {
137 // Translated from golang.go/src/math/big/rat.go.
138 debug.assert(@typeInfo(T) == .float);
139
140 const UnsignedInt = std.meta.Int(.unsigned, @typeInfo(T).float.bits);
141 const f_bits = @as(UnsignedInt, @bitCast(f));
142
143 const exponent_bits = math.floatExponentBits(T);
144 const exponent_bias = (1 << (exponent_bits - 1)) - 1;
145 const mantissa_bits = math.floatMantissaBits(T);
146
147 const exponent_mask = (1 << exponent_bits) - 1;
148 const mantissa_mask = (1 << mantissa_bits) - 1;
149
150 var exponent = @as(i16, @intCast((f_bits >> mantissa_bits) & exponent_mask));
151 var mantissa = f_bits & mantissa_mask;
152
153 switch (exponent) {
154 exponent_mask => {
155 return error.NonFiniteFloat;
156 },
157 0 => {
158 // denormal
159 exponent -= exponent_bias - 1;
160 },
161 else => {
162 // normal
163 mantissa |= 1 << mantissa_bits;
164 exponent -= exponent_bias;
165 },
166 }
167
168 var shift: i16 = mantissa_bits - exponent;
169
170 // factor out powers of two early from rational
171 while (mantissa & 1 == 0 and shift > 0) {
172 mantissa >>= 1;
173 shift -= 1;
174 }
175
176 try self.p.set(mantissa);
177 self.p.setSign(f >= 0);
178
179 try self.q.set(1);
180 if (shift >= 0) {
181 try self.q.shiftLeft(&self.q, @as(usize, @intCast(shift)));
182 } else {
183 try self.p.shiftLeft(&self.p, @as(usize, @intCast(-shift)));
184 }
185
186 try self.reduce();
187 }
188
189 /// Return a floating-point value that is the closest value to a Rational.
190 ///
191 /// The result may not be exact if the Rational is too precise or too large for the
192 /// target type.
193 pub fn toFloat(self: Rational, comptime T: type) !T {
194 // Translated from golang.go/src/math/big/rat.go.
195 // TODO: Indicate whether the result is not exact.
196 debug.assert(@typeInfo(T) == .float);
197
198 const fsize = @typeInfo(T).float.bits;
199 const BitReprType = std.meta.Int(.unsigned, fsize);
200
201 const msize = math.floatMantissaBits(T);
202 const msize1 = msize + 1;
203 const msize2 = msize1 + 1;
204
205 const esize = math.floatExponentBits(T);
206 const ebias = (1 << (esize - 1)) - 1;
207 const emin = 1 - ebias;
208
209 if (self.p.eqlZero()) {
210 return 0;
211 }
212
213 // 1. left-shift a or sub so that a/b is in [1 << msize1, 1 << (msize2 + 1)]
214 var exp = @as(isize, @intCast(self.p.bitCountTwosComp())) - @as(isize, @intCast(self.q.bitCountTwosComp()));
215
216 var a2 = try self.p.clone();
217 defer a2.deinit();
218
219 var b2 = try self.q.clone();
220 defer b2.deinit();
221
222 const shift = msize2 - exp;
223 if (shift >= 0) {
224 try a2.shiftLeft(&a2, @as(usize, @intCast(shift)));
225 } else {
226 try b2.shiftLeft(&b2, @as(usize, @intCast(-shift)));
227 }
228
229 // 2. compute quotient and remainder
230 var q = try Int.init(self.p.allocator);
231 defer q.deinit();
232
233 // unused
234 var r = try Int.init(self.p.allocator);
235 defer r.deinit();
236
237 try Int.divTrunc(&q, &r, &a2, &b2);
238
239 var mantissa = extractLowBits(q, BitReprType);
240 var have_rem = r.len() > 0;
241
242 // 3. q didn't fit in msize2 bits, redo division b2 << 1
243 if (mantissa >> msize2 == 1) {
244 if (mantissa & 1 == 1) {
245 have_rem = true;
246 }
247 mantissa >>= 1;
248 exp += 1;
249 }
250 if (mantissa >> msize1 != 1) {
251 // NOTE: This can be hit if the limb size is small (u8/16).
252 @panic("unexpected bits in result");
253 }
254
255 // 4. Rounding
256 if (emin - msize <= exp and exp <= emin) {
257 // denormal
258 const shift1 = @as(math.Log2Int(BitReprType), @intCast(emin - (exp - 1)));
259 const lost_bits = mantissa & ((@as(BitReprType, @intCast(1)) << shift1) - 1);
260 have_rem = have_rem or lost_bits != 0;
261 mantissa >>= shift1;
262 exp = 2 - ebias;
263 }
264
265 // round q using round-half-to-even
266 var exact = !have_rem;
267 if (mantissa & 1 != 0) {
268 exact = false;
269 if (have_rem or (mantissa & 2 != 0)) {
270 mantissa += 1;
271 if (mantissa >= 1 << msize2) {
272 // 11...1 => 100...0
273 mantissa >>= 1;
274 exp += 1;
275 }
276 }
277 }
278 mantissa >>= 1;
279
280 const f = math.scalbn(@as(T, @floatFromInt(mantissa)), @as(i32, @intCast(exp - msize1)));
281 if (math.isInf(f)) {
282 exact = false;
283 }
284
285 return if (self.p.isPositive()) f else -f;
286 }
287
288 /// Set a rational from an integer ratio.
289 pub fn setRatio(self: *Rational, p: anytype, q: anytype) !void {
290 try self.p.set(p);
291 try self.q.set(q);
292
293 self.p.setSign(@intFromBool(self.p.isPositive()) ^ @intFromBool(self.q.isPositive()) == 0);
294 self.q.setSign(true);
295
296 try self.reduce();
297
298 if (self.q.eqlZero()) {
299 @panic("cannot set rational with denominator = 0");
300 }
301 }
302
303 /// Set a Rational directly from an Int.
304 pub fn copyInt(self: *Rational, a: Int) !void {
305 try self.p.copy(a.toConst());
306 try self.q.set(1);
307 }
308
309 /// Set a Rational directly from a ratio of two Int's.
310 pub fn copyRatio(self: *Rational, a: Int, b: Int) !void {
311 try self.p.copy(a.toConst());
312 try self.q.copy(b.toConst());
313
314 self.p.setSign(@intFromBool(self.p.isPositive()) ^ @intFromBool(self.q.isPositive()) == 0);
315 self.q.setSign(true);
316
317 try self.reduce();
318 }
319
320 /// Make a Rational positive.
321 pub fn abs(r: *Rational) void {
322 r.p.abs();
323 }
324
325 /// Negate the sign of a Rational.
326 pub fn negate(r: *Rational) void {
327 r.p.negate();
328 }
329
330 /// Efficiently swap a Rational with another. This swaps the limb pointers and a full copy is not
331 /// performed. The address of the limbs field will not be the same after this function.
332 pub fn swap(r: *Rational, other: *Rational) void {
333 r.p.swap(&other.p);
334 r.q.swap(&other.q);
335 }
336
337 /// Returns math.Order.lt, math.Order.eq, math.Order.gt if a < b, a == b or
338 /// a > b respectively.
339 pub fn order(a: Rational, b: Rational) !math.Order {
340 return cmpInternal(a, b, false);
341 }
342
343 /// Returns math.Order.lt, math.Order.eq, math.Order.gt if |a| < |b|, |a| ==
344 /// |b| or |a| > |b| respectively.
345 pub fn orderAbs(a: Rational, b: Rational) !math.Order {
346 return cmpInternal(a, b, true);
347 }
348
349 // p/q > x/y iff p*y > x*q
350 fn cmpInternal(a: Rational, b: Rational, is_abs: bool) !math.Order {
351 // TODO: Would a div compare algorithm of sorts be viable and quicker? Can we avoid
352 // the memory allocations here?
353 var q = try Int.init(a.p.allocator);
354 defer q.deinit();
355
356 var p = try Int.init(b.p.allocator);
357 defer p.deinit();
358
359 try q.mul(&a.p, &b.q);
360 try p.mul(&b.p, &a.q);
361
362 return if (is_abs) q.orderAbs(p) else q.order(p);
363 }
364
365 /// rma = a + b.
366 ///
367 /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
368 ///
369 /// Returns an error if memory could not be allocated.
370 pub fn add(rma: *Rational, a: Rational, b: Rational) !void {
371 var r = rma;
372 var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr;
373
374 var sr: Rational = undefined;
375 if (aliased) {
376 sr = try Rational.init(rma.p.allocator);
377 r = &sr;
378 aliased = true;
379 }
380 defer if (aliased) {
381 rma.swap(r);
382 r.deinit();
383 };
384
385 try r.p.mul(&a.p, &b.q);
386 try r.q.mul(&b.p, &a.q);
387 try r.p.add(&r.p, &r.q);
388
389 try r.q.mul(&a.q, &b.q);
390 try r.reduce();
391 }
392
393 /// rma = a - b.
394 ///
395 /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
396 ///
397 /// Returns an error if memory could not be allocated.
398 pub fn sub(rma: *Rational, a: Rational, b: Rational) !void {
399 var r = rma;
400 var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr;
401
402 var sr: Rational = undefined;
403 if (aliased) {
404 sr = try Rational.init(rma.p.allocator);
405 r = &sr;
406 aliased = true;
407 }
408 defer if (aliased) {
409 rma.swap(r);
410 r.deinit();
411 };
412
413 try r.p.mul(&a.p, &b.q);
414 try r.q.mul(&b.p, &a.q);
415 try r.p.sub(&r.p, &r.q);
416
417 try r.q.mul(&a.q, &b.q);
418 try r.reduce();
419 }
420
421 /// rma = a * b.
422 ///
423 /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
424 ///
425 /// Returns an error if memory could not be allocated.
426 pub fn mul(r: *Rational, a: Rational, b: Rational) !void {
427 try r.p.mul(&a.p, &b.p);
428 try r.q.mul(&a.q, &b.q);
429 try r.reduce();
430 }
431
432 /// rma = a / b.
433 ///
434 /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
435 ///
436 /// Returns an error if memory could not be allocated.
437 pub fn div(r: *Rational, a: Rational, b: Rational) !void {
438 if (b.p.eqlZero()) {
439 @panic("division by zero");
440 }
441
442 try r.p.mul(&a.p, &b.q);
443 try r.q.mul(&b.p, &a.q);
444 try r.reduce();
445 }
446
447 /// Invert the numerator and denominator fields of a Rational. p/q => q/p.
448 pub fn invert(r: *Rational) void {
449 Int.swap(&r.p, &r.q);
450 }
451
452 // reduce r/q such that gcd(r, q) = 1
453 fn reduce(r: *Rational) !void {
454 var a = try Int.init(r.p.allocator);
455 defer a.deinit();
456
457 const sign = r.p.isPositive();
458 r.p.abs();
459 try a.gcd(&r.p, &r.q);
460 r.p.setSign(sign);
461
462 const one = IntConst{ .limbs = &[_]Limb{1}, .positive = true };
463 if (a.toConst().order(one) != .eq) {
464 var unused = try Int.init(r.p.allocator);
465 defer unused.deinit();
466
467 // TODO: divexact would be useful here
468 // TODO: don't copy r.q for div
469 try Int.divTrunc(&r.p, &unused, &r.p, &a);
470 try Int.divTrunc(&r.q, &unused, &r.q, &a);
471 }
472 }
473};
474
475fn extractLowBits(a: Int, comptime T: type) T {
476 debug.assert(@typeInfo(T) == .int);
477
478 const t_bits = @typeInfo(T).int.bits;
479 const limb_bits = @typeInfo(Limb).int.bits;
480 if (t_bits <= limb_bits) {
481 return @as(T, @truncate(a.limbs[0]));
482 } else {
483 var r: T = 0;
484 comptime var i: usize = 0;
485
486 // Remainder is always 0 since if t_bits >= limb_bits -> Limb | T and both
487 // are powers of two.
488 inline while (i < t_bits / limb_bits) : (i += 1) {
489 r |= math.shl(T, a.limbs[i], i * limb_bits);
490 }
491
492 return r;
493 }
494}
495
496test extractLowBits {
497 var a = try Int.initSet(testing.allocator, 0x11112222333344441234567887654321);
498 defer a.deinit();
499
500 const a1 = extractLowBits(a, u8);
501 try testing.expect(a1 == 0x21);
502
503 const a2 = extractLowBits(a, u16);
504 try testing.expect(a2 == 0x4321);
505
506 const a3 = extractLowBits(a, u32);
507 try testing.expect(a3 == 0x87654321);
508
509 const a4 = extractLowBits(a, u64);
510 try testing.expect(a4 == 0x1234567887654321);
511
512 const a5 = extractLowBits(a, u128);
513 try testing.expect(a5 == 0x11112222333344441234567887654321);
514}
515
516test "set" {
517 var a = try Rational.init(testing.allocator);
518 defer a.deinit();
519
520 try a.setInt(5);
521 try testing.expect((try a.p.toInt(u32)) == 5);
522 try testing.expect((try a.q.toInt(u32)) == 1);
523
524 try a.setRatio(7, 3);
525 try testing.expect((try a.p.toInt(u32)) == 7);
526 try testing.expect((try a.q.toInt(u32)) == 3);
527
528 try a.setRatio(9, 3);
529 try testing.expect((try a.p.toInt(i32)) == 3);
530 try testing.expect((try a.q.toInt(i32)) == 1);
531
532 try a.setRatio(-9, 3);
533 try testing.expect((try a.p.toInt(i32)) == -3);
534 try testing.expect((try a.q.toInt(i32)) == 1);
535
536 try a.setRatio(9, -3);
537 try testing.expect((try a.p.toInt(i32)) == -3);
538 try testing.expect((try a.q.toInt(i32)) == 1);
539
540 try a.setRatio(-9, -3);
541 try testing.expect((try a.p.toInt(i32)) == 3);
542 try testing.expect((try a.q.toInt(i32)) == 1);
543}
544
545test "setFloat" {
546 var a = try Rational.init(testing.allocator);
547 defer a.deinit();
548
549 try a.setFloat(f64, 2.5);
550 try testing.expect((try a.p.toInt(i32)) == 5);
551 try testing.expect((try a.q.toInt(i32)) == 2);
552
553 try a.setFloat(f32, -2.5);
554 try testing.expect((try a.p.toInt(i32)) == -5);
555 try testing.expect((try a.q.toInt(i32)) == 2);
556
557 try a.setFloat(f32, 3.141593);
558
559 // = 3.14159297943115234375
560 try testing.expect((try a.p.toInt(u32)) == 3294199);
561 try testing.expect((try a.q.toInt(u32)) == 1048576);
562
563 try a.setFloat(f64, 72.141593120712409172417410926841290461290467124);
564
565 // = 72.1415931207124145885245525278151035308837890625
566 try testing.expect((try a.p.toInt(u128)) == 5076513310880537);
567 try testing.expect((try a.q.toInt(u128)) == 70368744177664);
568}
569
570test "setFloatString" {
571 var a = try Rational.init(testing.allocator);
572 defer a.deinit();
573
574 try a.setFloatString("72.14159312071241458852455252781510353");
575
576 // = 72.1415931207124145885245525278151035308837890625
577 try testing.expect((try a.p.toInt(u128)) == 7214159312071241458852455252781510353);
578 try testing.expect((try a.q.toInt(u128)) == 100000000000000000000000000000000000);
579}
580
581test "toFloat" {
582 var a = try Rational.init(testing.allocator);
583 defer a.deinit();
584
585 // = 3.14159297943115234375
586 try a.setRatio(3294199, 1048576);
587 try testing.expect((try a.toFloat(f64)) == 3.14159297943115234375);
588
589 // = 72.1415931207124145885245525278151035308837890625
590 try a.setRatio(5076513310880537, 70368744177664);
591 try testing.expect((try a.toFloat(f64)) == 72.141593120712409172417410926841290461290467124);
592}
593
594test "set/to Float round-trip" {
595 var a = try Rational.init(testing.allocator);
596 defer a.deinit();
597 var prng = std.Random.DefaultPrng.init(std.testing.random_seed);
598 const random = prng.random();
599 var i: usize = 0;
600 while (i < 512) : (i += 1) {
601 const r = random.float(f64);
602 try a.setFloat(f64, r);
603 try testing.expect((try a.toFloat(f64)) == r);
604 }
605}
606
607test "copy" {
608 var a = try Rational.init(testing.allocator);
609 defer a.deinit();
610
611 var b = try Int.initSet(testing.allocator, 5);
612 defer b.deinit();
613
614 try a.copyInt(b);
615 try testing.expect((try a.p.toInt(u32)) == 5);
616 try testing.expect((try a.q.toInt(u32)) == 1);
617
618 var c = try Int.initSet(testing.allocator, 7);
619 defer c.deinit();
620 var d = try Int.initSet(testing.allocator, 3);
621 defer d.deinit();
622
623 try a.copyRatio(c, d);
624 try testing.expect((try a.p.toInt(u32)) == 7);
625 try testing.expect((try a.q.toInt(u32)) == 3);
626
627 var e = try Int.initSet(testing.allocator, 9);
628 defer e.deinit();
629 var f = try Int.initSet(testing.allocator, 3);
630 defer f.deinit();
631
632 try a.copyRatio(e, f);
633 try testing.expect((try a.p.toInt(u32)) == 3);
634 try testing.expect((try a.q.toInt(u32)) == 1);
635}
636
637test "negate" {
638 var a = try Rational.init(testing.allocator);
639 defer a.deinit();
640
641 try a.setInt(-50);
642 try testing.expect((try a.p.toInt(i32)) == -50);
643 try testing.expect((try a.q.toInt(i32)) == 1);
644
645 a.negate();
646 try testing.expect((try a.p.toInt(i32)) == 50);
647 try testing.expect((try a.q.toInt(i32)) == 1);
648
649 a.negate();
650 try testing.expect((try a.p.toInt(i32)) == -50);
651 try testing.expect((try a.q.toInt(i32)) == 1);
652}
653
654test "abs" {
655 var a = try Rational.init(testing.allocator);
656 defer a.deinit();
657
658 try a.setInt(-50);
659 try testing.expect((try a.p.toInt(i32)) == -50);
660 try testing.expect((try a.q.toInt(i32)) == 1);
661
662 a.abs();
663 try testing.expect((try a.p.toInt(i32)) == 50);
664 try testing.expect((try a.q.toInt(i32)) == 1);
665
666 a.abs();
667 try testing.expect((try a.p.toInt(i32)) == 50);
668 try testing.expect((try a.q.toInt(i32)) == 1);
669}
670
671test "swap" {
672 var a = try Rational.init(testing.allocator);
673 defer a.deinit();
674 var b = try Rational.init(testing.allocator);
675 defer b.deinit();
676
677 try a.setRatio(50, 23);
678 try b.setRatio(17, 3);
679
680 try testing.expect((try a.p.toInt(u32)) == 50);
681 try testing.expect((try a.q.toInt(u32)) == 23);
682
683 try testing.expect((try b.p.toInt(u32)) == 17);
684 try testing.expect((try b.q.toInt(u32)) == 3);
685
686 a.swap(&b);
687
688 try testing.expect((try a.p.toInt(u32)) == 17);
689 try testing.expect((try a.q.toInt(u32)) == 3);
690
691 try testing.expect((try b.p.toInt(u32)) == 50);
692 try testing.expect((try b.q.toInt(u32)) == 23);
693}
694
695test "order" {
696 var a = try Rational.init(testing.allocator);
697 defer a.deinit();
698 var b = try Rational.init(testing.allocator);
699 defer b.deinit();
700
701 try a.setRatio(500, 231);
702 try b.setRatio(18903, 8584);
703 try testing.expect((try a.order(b)) == .lt);
704
705 try a.setRatio(890, 10);
706 try b.setRatio(89, 1);
707 try testing.expect((try a.order(b)) == .eq);
708}
709
710test "order/orderAbs with negative" {
711 var a = try Rational.init(testing.allocator);
712 defer a.deinit();
713 var b = try Rational.init(testing.allocator);
714 defer b.deinit();
715
716 try a.setRatio(1, 1);
717 try b.setRatio(-2, 1);
718 try testing.expect((try a.order(b)) == .gt);
719 try testing.expect((try a.orderAbs(b)) == .lt);
720}
721
722test "add single-limb" {
723 var a = try Rational.init(testing.allocator);
724 defer a.deinit();
725 var b = try Rational.init(testing.allocator);
726 defer b.deinit();
727
728 try a.setRatio(500, 231);
729 try b.setRatio(18903, 8584);
730 try testing.expect((try a.order(b)) == .lt);
731
732 try a.setRatio(890, 10);
733 try b.setRatio(89, 1);
734 try testing.expect((try a.order(b)) == .eq);
735}
736
737test "add" {
738 var a = try Rational.init(testing.allocator);
739 defer a.deinit();
740 var b = try Rational.init(testing.allocator);
741 defer b.deinit();
742 var r = try Rational.init(testing.allocator);
743 defer r.deinit();
744
745 try a.setRatio(78923, 23341);
746 try b.setRatio(123097, 12441414);
747 try a.add(a, b);
748
749 try r.setRatio(984786924199, 290395044174);
750 try testing.expect((try a.order(r)) == .eq);
751}
752
753test "sub" {
754 var a = try Rational.init(testing.allocator);
755 defer a.deinit();
756 var b = try Rational.init(testing.allocator);
757 defer b.deinit();
758 var r = try Rational.init(testing.allocator);
759 defer r.deinit();
760
761 try a.setRatio(78923, 23341);
762 try b.setRatio(123097, 12441414);
763 try a.sub(a, b);
764
765 try r.setRatio(979040510045, 290395044174);
766 try testing.expect((try a.order(r)) == .eq);
767}
768
769test "mul" {
770 var a = try Rational.init(testing.allocator);
771 defer a.deinit();
772 var b = try Rational.init(testing.allocator);
773 defer b.deinit();
774 var r = try Rational.init(testing.allocator);
775 defer r.deinit();
776
777 try a.setRatio(78923, 23341);
778 try b.setRatio(123097, 12441414);
779 try a.mul(a, b);
780
781 try r.setRatio(571481443, 17082061422);
782 try testing.expect((try a.order(r)) == .eq);
783}
784
785test "div" {
786 {
787 var a = try Rational.init(testing.allocator);
788 defer a.deinit();
789 var b = try Rational.init(testing.allocator);
790 defer b.deinit();
791 var r = try Rational.init(testing.allocator);
792 defer r.deinit();
793
794 try a.setRatio(78923, 23341);
795 try b.setRatio(123097, 12441414);
796 try a.div(a, b);
797
798 try r.setRatio(75531824394, 221015929);
799 try testing.expect((try a.order(r)) == .eq);
800 }
801
802 {
803 var a = try Rational.init(testing.allocator);
804 defer a.deinit();
805 var r = try Rational.init(testing.allocator);
806 defer r.deinit();
807
808 try a.setRatio(78923, 23341);
809 a.invert();
810
811 try r.setRatio(23341, 78923);
812 try testing.expect((try a.order(r)) == .eq);
813
814 try a.setRatio(-78923, 23341);
815 a.invert();
816
817 try r.setRatio(-23341, 78923);
818 try testing.expect((try a.order(r)) == .eq);
819 }
820}
lib/std/math/float.zig+113
...@@ -4,6 +4,119 @@ const assert = std.debug.assert;...@@ -4,6 +4,119 @@ const assert = std.debug.assert;
4const expect = std.testing.expect;4const expect = std.testing.expect;
5const expectEqual = std.testing.expectEqual;5const expectEqual = std.testing.expectEqual;
66
7pub const Sign = enum(u1) { positive, negative };
8
9pub fn FloatRepr(comptime Float: type) type {
10 const fractional_bits = floatFractionalBits(Float);
11 const exponent_bits = floatExponentBits(Float);
12 return packed struct {
13 const Repr = @This();
14
15 mantissa: StoredMantissa,
16 exponent: BiasedExponent,
17 sign: Sign,
18
19 pub const StoredMantissa = @Type(.{ .int = .{
20 .signedness = .unsigned,
21 .bits = floatMantissaBits(Float),
22 } });
23 pub const Mantissa = @Type(.{ .int = .{
24 .signedness = .unsigned,
25 .bits = 1 + fractional_bits,
26 } });
27 pub const Exponent = @Type(.{ .int = .{
28 .signedness = .signed,
29 .bits = exponent_bits,
30 } });
31 pub const BiasedExponent = enum(@Type(.{ .int = .{
32 .signedness = .unsigned,
33 .bits = exponent_bits,
34 } })) {
35 denormal = 0,
36 min_normal = 1,
37 zero = (1 << (exponent_bits - 1)) - 1,
38 max_normal = (1 << exponent_bits) - 2,
39 infinite = (1 << exponent_bits) - 1,
40 _,
41
42 pub const Int = @typeInfo(BiasedExponent).@"enum".tag_type;
43
44 pub fn unbias(biased: BiasedExponent) Exponent {
45 switch (biased) {
46 .denormal => unreachable,
47 else => return @bitCast(@intFromEnum(biased) -% @intFromEnum(BiasedExponent.zero)),
48 .infinite => unreachable,
49 }
50 }
51
52 pub fn bias(unbiased: Exponent) BiasedExponent {
53 return @enumFromInt(@intFromEnum(BiasedExponent.zero) +% @as(Int, @bitCast(unbiased)));
54 }
55 };
56
57 pub const Normalized = struct {
58 fraction: Fraction,
59 exponent: Normalized.Exponent,
60
61 pub const Fraction = @Type(.{ .int = .{
62 .signedness = .unsigned,
63 .bits = fractional_bits,
64 } });
65 pub const Exponent = @Type(.{ .int = .{
66 .signedness = .signed,
67 .bits = 1 + exponent_bits,
68 } });
69
70 /// This currently truncates denormal values, which needs to be fixed before this can be used to
71 /// produce a rounded value.
72 pub fn reconstruct(normalized: Normalized, sign: Sign) Float {
73 if (normalized.exponent > BiasedExponent.max_normal.unbias()) return @bitCast(Repr{
74 .mantissa = 0,
75 .exponent = .infinite,
76 .sign = sign,
77 });
78 const mantissa = @as(Mantissa, 1 << fractional_bits) | normalized.fraction;
79 if (normalized.exponent < BiasedExponent.min_normal.unbias()) return @bitCast(Repr{
80 .mantissa = @truncate(std.math.shr(
81 Mantissa,
82 mantissa,
83 BiasedExponent.min_normal.unbias() - normalized.exponent,
84 )),
85 .exponent = .denormal,
86 .sign = sign,
87 });
88 return @bitCast(Repr{
89 .mantissa = @truncate(mantissa),
90 .exponent = .bias(@intCast(normalized.exponent)),
91 .sign = sign,
92 });
93 }
94 };
95
96 pub const Classified = union(enum) { normalized: Normalized, infinity, nan, invalid };
97 fn classify(repr: Repr) Classified {
98 return switch (repr.exponent) {
99 .denormal => {
100 const mantissa: Mantissa = repr.mantissa;
101 const shift = @clz(mantissa);
102 return .{ .normalized = .{
103 .fraction = @truncate(mantissa << shift),
104 .exponent = @as(Normalized.Exponent, comptime BiasedExponent.min_normal.unbias()) - shift,
105 } };
106 },
107 else => if (repr.mantissa <= std.math.maxInt(Normalized.Fraction)) .{ .normalized = .{
108 .fraction = @intCast(repr.mantissa),
109 .exponent = repr.exponent.unbias(),
110 } } else .invalid,
111 .infinite => switch (repr.mantissa) {
112 0 => .infinity,
113 else => .nan,
114 },
115 };
116 }
117 };
118}
119
7/// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic.120/// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic.
8inline fn mantissaOne(comptime T: type) comptime_int {121inline fn mantissaOne(comptime T: type) comptime_int {
9 return if (@typeInfo(T).float.bits == 80) 1 << floatFractionalBits(T) else 0;122 return if (@typeInfo(T).float.bits == 80) 1 << floatFractionalBits(T) else 0;
lib/std/zon/parse.zig+1-1
...@@ -593,7 +593,7 @@ const Parser = struct {...@@ -593,7 +593,7 @@ const Parser = struct {
593 switch (node.get(self.zoir)) {593 switch (node.get(self.zoir)) {
594 .int_literal => |int| switch (int) {594 .int_literal => |int| switch (int) {
595 .small => |val| return @floatFromInt(val),595 .small => |val| return @floatFromInt(val),
596 .big => |val| return val.toFloat(T),596 .big => |val| return val.toFloat(T, .nearest_even)[0],
597 },597 },
598 .float_literal => |val| return @floatCast(val),598 .float_literal => |val| return @floatCast(val),
599 .pos_inf => return std.math.inf(T),599 .pos_inf => return std.math.inf(T),
src/Air.zig+8
...@@ -683,6 +683,10 @@ pub const Inst = struct {...@@ -683,6 +683,10 @@ pub const Inst = struct {
683 int_from_float,683 int_from_float,
684 /// Same as `int_from_float` with optimized float mode.684 /// Same as `int_from_float` with optimized float mode.
685 int_from_float_optimized,685 int_from_float_optimized,
686 /// Same as `int_from_float`, but with a safety check that the operand is in bounds.
687 int_from_float_safe,
688 /// Same as `int_from_float_optimized`, but with a safety check that the operand is in bounds.
689 int_from_float_optimized_safe,
686 /// Given an integer operand, return the float with the closest mathematical meaning.690 /// Given an integer operand, return the float with the closest mathematical meaning.
687 /// Uses the `ty_op` field.691 /// Uses the `ty_op` field.
688 float_from_int,692 float_from_int,
...@@ -1612,6 +1616,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)...@@ -1612,6 +1616,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
1612 .array_to_slice,1616 .array_to_slice,
1613 .int_from_float,1617 .int_from_float,
1614 .int_from_float_optimized,1618 .int_from_float_optimized,
1619 .int_from_float_safe,
1620 .int_from_float_optimized_safe,
1615 .float_from_int,1621 .float_from_int,
1616 .splat,1622 .splat,
1617 .get_union_tag,1623 .get_union_tag,
...@@ -1842,6 +1848,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {...@@ -1842,6 +1848,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
1842 .sub_safe,1848 .sub_safe,
1843 .mul_safe,1849 .mul_safe,
1844 .intcast_safe,1850 .intcast_safe,
1851 .int_from_float_safe,
1852 .int_from_float_optimized_safe,
1845 => true,1853 => true,
18461854
1847 .add,1855 .add,
src/Air/Legalize.zig+235-36
...@@ -1,7 +1,36 @@...@@ -1,7 +1,36 @@
1pt: Zcu.PerThread,1pt: Zcu.PerThread,
2air_instructions: std.MultiArrayList(Air.Inst),2air_instructions: std.MultiArrayList(Air.Inst),
3air_extra: std.ArrayListUnmanaged(u32),3air_extra: std.ArrayListUnmanaged(u32),
4features: *const Features,4features: if (switch (dev.env) {
5 .bootstrap => @import("../codegen/c.zig").legalizeFeatures(undefined),
6 else => null,
7}) |bootstrap_features| struct {
8 fn init(features: *const Features) @This() {
9 assert(features.eql(bootstrap_features.*));
10 return .{};
11 }
12 /// `inline` to propagate comptime-known result.
13 inline fn has(_: @This(), comptime feature: Feature) bool {
14 return comptime bootstrap_features.contains(feature);
15 }
16 /// `inline` to propagate comptime-known result.
17 fn hasAny(_: @This(), comptime features: []const Feature) bool {
18 return comptime !bootstrap_features.intersectWith(.initMany(features)).eql(.initEmpty());
19 }
20} else struct {
21 features: *const Features,
22 /// `inline` to propagate whether `dev.check` returns.
23 inline fn init(features: *const Features) @This() {
24 dev.check(.legalize);
25 return .{ .features = features };
26 }
27 fn has(rt: @This(), comptime feature: Feature) bool {
28 return rt.features.contains(feature);
29 }
30 fn hasAny(rt: @This(), comptime features: []const Feature) bool {
31 return !rt.features.intersectWith(comptime .initMany(features)).eql(comptime .initEmpty());
32 }
33},
534
6pub const Feature = enum {35pub const Feature = enum {
7 scalarize_add,36 scalarize_add,
...@@ -83,6 +112,8 @@ pub const Feature = enum {...@@ -83,6 +112,8 @@ pub const Feature = enum {
83 scalarize_trunc,112 scalarize_trunc,
84 scalarize_int_from_float,113 scalarize_int_from_float,
85 scalarize_int_from_float_optimized,114 scalarize_int_from_float_optimized,
115 scalarize_int_from_float_safe,
116 scalarize_int_from_float_optimized_safe,
86 scalarize_float_from_int,117 scalarize_float_from_int,
87 scalarize_shuffle_one,118 scalarize_shuffle_one,
88 scalarize_shuffle_two,119 scalarize_shuffle_two,
...@@ -97,6 +128,12 @@ pub const Feature = enum {...@@ -97,6 +128,12 @@ pub const Feature = enum {
97 /// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure.128 /// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure.
98 /// Not compatible with `scalarize_intcast_safe`.129 /// Not compatible with `scalarize_intcast_safe`.
99 expand_intcast_safe,130 expand_intcast_safe,
131 /// Replace `int_from_float_safe` with an explicit safety check which `call`s the panic function on failure.
132 /// Not compatible with `scalarize_int_from_float_safe`.
133 expand_int_from_float_safe,
134 /// Replace `int_from_float_optimized_safe` with an explicit safety check which `call`s the panic function on failure.
135 /// Not compatible with `scalarize_int_from_float_optimized_safe`.
136 expand_int_from_float_optimized_safe,
100 /// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure.137 /// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure.
101 /// Not compatible with `scalarize_add_safe`.138 /// Not compatible with `scalarize_add_safe`.
102 expand_add_safe,139 expand_add_safe,
...@@ -196,10 +233,12 @@ pub const Feature = enum {...@@ -196,10 +233,12 @@ pub const Feature = enum {
196 .trunc => .scalarize_trunc,233 .trunc => .scalarize_trunc,
197 .int_from_float => .scalarize_int_from_float,234 .int_from_float => .scalarize_int_from_float,
198 .int_from_float_optimized => .scalarize_int_from_float_optimized,235 .int_from_float_optimized => .scalarize_int_from_float_optimized,
236 .int_from_float_safe => .scalarize_int_from_float_safe,
237 .int_from_float_optimized_safe => .scalarize_int_from_float_optimized_safe,
199 .float_from_int => .scalarize_float_from_int,238 .float_from_int => .scalarize_float_from_int,
200 .shuffle_one => .scalarize_shuffle_one,239 .shuffle_one => .scalarize_shuffle_one,
201 .shuffle_two => .scalarize_shuffle_two,240 .shuffle_two => .scalarize_shuffle_two,
202 .select => .scalarize_selects,241 .select => .scalarize_select,
203 .mul_add => .scalarize_mul_add,242 .mul_add => .scalarize_mul_add,
204 };243 };
205 }244 }
...@@ -210,13 +249,12 @@ pub const Features = std.enums.EnumSet(Feature);...@@ -210,13 +249,12 @@ pub const Features = std.enums.EnumSet(Feature);
210pub const Error = std.mem.Allocator.Error;249pub const Error = std.mem.Allocator.Error;
211250
212pub fn legalize(air: *Air, pt: Zcu.PerThread, features: *const Features) Error!void {251pub fn legalize(air: *Air, pt: Zcu.PerThread, features: *const Features) Error!void {
213 dev.check(.legalize);
214 assert(!features.eql(comptime .initEmpty())); // backend asked to run legalize, but no features were enabled252 assert(!features.eql(comptime .initEmpty())); // backend asked to run legalize, but no features were enabled
215 var l: Legalize = .{253 var l: Legalize = .{
216 .pt = pt,254 .pt = pt,
217 .air_instructions = air.instructions.toMultiArrayList(),255 .air_instructions = air.instructions.toMultiArrayList(),
218 .air_extra = air.extra,256 .air_extra = air.extra,
219 .features = features,257 .features = .init(features),
220 };258 };
221 defer air.* = l.getTmpAir();259 defer air.* = l.getTmpAir();
222 const main_extra = l.extraData(Air.Block, l.air_extra.items[@intFromEnum(Air.ExtraIndex.main_block)]);260 const main_extra = l.extraData(Air.Block, l.air_extra.items[@intFromEnum(Air.ExtraIndex.main_block)]);
...@@ -278,28 +316,28 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -278,28 +316,28 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
278 .bit_and,316 .bit_and,
279 .bit_or,317 .bit_or,
280 .xor,318 .xor,
281 => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {319 => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
282 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;320 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
283 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);321 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
284 },322 },
285 .add_safe => if (l.features.contains(.expand_add_safe)) {323 .add_safe => if (l.features.has(.expand_add_safe)) {
286 assert(!l.features.contains(.scalarize_add_safe)); // it doesn't make sense to do both324 assert(!l.features.has(.scalarize_add_safe)); // it doesn't make sense to do both
287 continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .add_with_overflow));325 continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .add_with_overflow));
288 } else if (l.features.contains(.scalarize_add_safe)) {326 } else if (l.features.has(.scalarize_add_safe)) {
289 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;327 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
290 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);328 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
291 },329 },
292 .sub_safe => if (l.features.contains(.expand_sub_safe)) {330 .sub_safe => if (l.features.has(.expand_sub_safe)) {
293 assert(!l.features.contains(.scalarize_sub_safe)); // it doesn't make sense to do both331 assert(!l.features.has(.scalarize_sub_safe)); // it doesn't make sense to do both
294 continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .sub_with_overflow));332 continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .sub_with_overflow));
295 } else if (l.features.contains(.scalarize_sub_safe)) {333 } else if (l.features.has(.scalarize_sub_safe)) {
296 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;334 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
297 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);335 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
298 },336 },
299 .mul_safe => if (l.features.contains(.expand_mul_safe)) {337 .mul_safe => if (l.features.has(.expand_mul_safe)) {
300 assert(!l.features.contains(.scalarize_mul_safe)); // it doesn't make sense to do both338 assert(!l.features.has(.scalarize_mul_safe)); // it doesn't make sense to do both
301 continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .mul_with_overflow));339 continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .mul_with_overflow));
302 } else if (l.features.contains(.scalarize_mul_safe)) {340 } else if (l.features.has(.scalarize_mul_safe)) {
303 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;341 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
304 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);342 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
305 },343 },
...@@ -308,7 +346,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -308,7 +346,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
308 .sub_with_overflow,346 .sub_with_overflow,
309 .mul_with_overflow,347 .mul_with_overflow,
310 .shl_with_overflow,348 .shl_with_overflow,
311 => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {349 => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
312 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;350 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
313 if (ty_pl.ty.toType().fieldType(0, zcu).isVector(zcu)) continue :inst l.replaceInst(inst, .block, try l.scalarizeOverflowBlockPayload(inst));351 if (ty_pl.ty.toType().fieldType(0, zcu).isVector(zcu)) continue :inst l.replaceInst(inst, .block, try l.scalarizeOverflowBlockPayload(inst));
314 },352 },
...@@ -320,13 +358,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -320,13 +358,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
320 .shl,358 .shl,
321 .shl_exact,359 .shl_exact,
322 .shl_sat,360 .shl_sat,
323 => |air_tag| if (!l.features.intersectWith(comptime .initMany(&.{361 => |air_tag| if (l.features.hasAny(&.{
324 .unsplat_shift_rhs,362 .unsplat_shift_rhs,
325 .scalarize(air_tag),363 .scalarize(air_tag),
326 })).eql(comptime .initEmpty())) {364 })) {
327 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;365 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
328 if (l.typeOf(bin_op.rhs).isVector(zcu)) {366 if (l.typeOf(bin_op.rhs).isVector(zcu)) {
329 if (l.features.contains(.unsplat_shift_rhs)) {367 if (l.features.has(.unsplat_shift_rhs)) {
330 if (bin_op.rhs.toInterned()) |rhs_ip_index| switch (ip.indexToKey(rhs_ip_index)) {368 if (bin_op.rhs.toInterned()) |rhs_ip_index| switch (ip.indexToKey(rhs_ip_index)) {
331 else => {},369 else => {},
332 .aggregate => |aggregate| switch (aggregate.storage) {370 .aggregate => |aggregate| switch (aggregate.storage) {
...@@ -347,7 +385,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -347,7 +385,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
347 }385 }
348 }386 }
349 }387 }
350 if (l.features.contains(comptime .scalarize(air_tag))) continue :inst try l.scalarize(inst, .bin_op);388 if (l.features.has(comptime .scalarize(air_tag))) continue :inst try l.scalarize(inst, .bin_op);
351 }389 }
352 },390 },
353 inline .not,391 inline .not,
...@@ -364,11 +402,11 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -364,11 +402,11 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
364 .int_from_float,402 .int_from_float,
365 .int_from_float_optimized,403 .int_from_float_optimized,
366 .float_from_int,404 .float_from_int,
367 => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {405 => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
368 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;406 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
369 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);407 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
370 },408 },
371 .bitcast => if (l.features.contains(.scalarize_bitcast)) {409 .bitcast => if (l.features.has(.scalarize_bitcast)) {
372 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;410 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
373411
374 const to_ty = ty_op.ty.toType();412 const to_ty = ty_op.ty.toType();
...@@ -404,10 +442,24 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -404,10 +442,24 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
404 };442 };
405 if (!from_ty_legal) continue :inst l.replaceInst(inst, .block, try l.scalarizeBitcastOperandBlockPayload(inst));443 if (!from_ty_legal) continue :inst l.replaceInst(inst, .block, try l.scalarizeBitcastOperandBlockPayload(inst));
406 },444 },
407 .intcast_safe => if (l.features.contains(.expand_intcast_safe)) {445 .intcast_safe => if (l.features.has(.expand_intcast_safe)) {
408 assert(!l.features.contains(.scalarize_intcast_safe)); // it doesn't make sense to do both446 assert(!l.features.has(.scalarize_intcast_safe)); // it doesn't make sense to do both
409 continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst));447 continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst));
410 } else if (l.features.contains(.scalarize_intcast_safe)) {448 } else if (l.features.has(.scalarize_intcast_safe)) {
449 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
450 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
451 },
452 .int_from_float_safe => if (l.features.has(.expand_int_from_float_safe)) {
453 assert(!l.features.has(.scalarize_int_from_float_safe));
454 continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, false));
455 } else if (l.features.has(.scalarize_int_from_float_safe)) {
456 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
457 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
458 },
459 .int_from_float_optimized_safe => if (l.features.has(.expand_int_from_float_optimized_safe)) {
460 assert(!l.features.has(.scalarize_int_from_float_optimized_safe));
461 continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, true));
462 } else if (l.features.has(.scalarize_int_from_float_optimized_safe)) {
411 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;463 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
412 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);464 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
413 },465 },
...@@ -442,7 +494,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -442,7 +494,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
442 .trunc_float,494 .trunc_float,
443 .neg,495 .neg,
444 .neg_optimized,496 .neg_optimized,
445 => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {497 => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
446 const un_op = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op;498 const un_op = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op;
447 if (l.typeOf(un_op).isVector(zcu)) continue :inst try l.scalarize(inst, .un_op);499 if (l.typeOf(un_op).isVector(zcu)) continue :inst try l.scalarize(inst, .un_op);
448 },500 },
...@@ -459,7 +511,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -459,7 +511,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
459 .cmp_neq,511 .cmp_neq,
460 .cmp_neq_optimized,512 .cmp_neq_optimized,
461 => {},513 => {},
462 inline .cmp_vector, .cmp_vector_optimized => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {514 inline .cmp_vector, .cmp_vector_optimized => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
463 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;515 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
464 if (ty_pl.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .cmp_vector);516 if (ty_pl.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .cmp_vector);
465 },517 },
...@@ -513,13 +565,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -513,13 +565,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
513 .bool_and,565 .bool_and,
514 .bool_or,566 .bool_or,
515 => {},567 => {},
516 .load => if (l.features.contains(.expand_packed_load)) {568 .load => if (l.features.has(.expand_packed_load)) {
517 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;569 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
518 const ptr_info = l.typeOf(ty_op.operand).ptrInfo(zcu);570 const ptr_info = l.typeOf(ty_op.operand).ptrInfo(zcu);
519 if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedLoadBlockPayload(inst));571 if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedLoadBlockPayload(inst));
520 },572 },
521 .ret, .ret_safe, .ret_load => {},573 .ret, .ret_safe, .ret_load => {},
522 .store, .store_safe => if (l.features.contains(.expand_packed_store)) {574 .store, .store_safe => if (l.features.has(.expand_packed_store)) {
523 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;575 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
524 const ptr_info = l.typeOf(bin_op.lhs).ptrInfo(zcu);576 const ptr_info = l.typeOf(bin_op.lhs).ptrInfo(zcu);
525 if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedStoreBlockPayload(inst));577 if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedStoreBlockPayload(inst));
...@@ -542,7 +594,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -542,7 +594,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
542 .struct_field_ptr_index_2,594 .struct_field_ptr_index_2,
543 .struct_field_ptr_index_3,595 .struct_field_ptr_index_3,
544 => {},596 => {},
545 .struct_field_val => if (l.features.contains(.expand_packed_struct_field_val)) {597 .struct_field_val => if (l.features.has(.expand_packed_struct_field_val)) {
546 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;598 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
547 const extra = l.extraData(Air.StructField, ty_pl.payload).data;599 const extra = l.extraData(Air.StructField, ty_pl.payload).data;
548 switch (l.typeOf(extra.struct_operand).containerLayout(zcu)) {600 switch (l.typeOf(extra.struct_operand).containerLayout(zcu)) {
...@@ -564,7 +616,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -564,7 +616,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
564 .ptr_elem_ptr,616 .ptr_elem_ptr,
565 .array_to_slice,617 .array_to_slice,
566 => {},618 => {},
567 .reduce, .reduce_optimized => if (l.features.contains(.reduce_one_elem_to_bitcast)) {619 .reduce, .reduce_optimized => if (l.features.has(.reduce_one_elem_to_bitcast)) {
568 const reduce = l.air_instructions.items(.data)[@intFromEnum(inst)].reduce;620 const reduce = l.air_instructions.items(.data)[@intFromEnum(inst)].reduce;
569 const vector_ty = l.typeOf(reduce.operand);621 const vector_ty = l.typeOf(reduce.operand);
570 switch (vector_ty.vectorLen(zcu)) {622 switch (vector_ty.vectorLen(zcu)) {
...@@ -577,9 +629,9 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -577,9 +629,9 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
577 }629 }
578 },630 },
579 .splat => {},631 .splat => {},
580 .shuffle_one => if (l.features.contains(.scalarize_shuffle_one)) continue :inst try l.scalarize(inst, .shuffle_one),632 .shuffle_one => if (l.features.has(.scalarize_shuffle_one)) continue :inst try l.scalarize(inst, .shuffle_one),
581 .shuffle_two => if (l.features.contains(.scalarize_shuffle_two)) continue :inst try l.scalarize(inst, .shuffle_two),633 .shuffle_two => if (l.features.has(.scalarize_shuffle_two)) continue :inst try l.scalarize(inst, .shuffle_two),
582 .select => if (l.features.contains(.scalarize_select)) continue :inst try l.scalarize(inst, .select),634 .select => if (l.features.has(.scalarize_select)) continue :inst try l.scalarize(inst, .select),
583 .memset,635 .memset,
584 .memset_safe,636 .memset_safe,
585 .memcpy,637 .memcpy,
...@@ -597,7 +649,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -597,7 +649,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
597 .error_name,649 .error_name,
598 .error_set_has_value,650 .error_set_has_value,
599 => {},651 => {},
600 .aggregate_init => if (l.features.contains(.expand_packed_aggregate_init)) {652 .aggregate_init => if (l.features.has(.expand_packed_aggregate_init)) {
601 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;653 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
602 const agg_ty = ty_pl.ty.toType();654 const agg_ty = ty_pl.ty.toType();
603 switch (agg_ty.zigTypeTag(zcu)) {655 switch (agg_ty.zigTypeTag(zcu)) {
...@@ -609,7 +661,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -609,7 +661,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
609 }661 }
610 },662 },
611 .union_init, .prefetch => {},663 .union_init, .prefetch => {},
612 .mul_add => if (l.features.contains(.scalarize_mul_add)) {664 .mul_add => if (l.features.has(.scalarize_mul_add)) {
613 const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op;665 const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op;
614 if (l.typeOf(pl_op.operand).isVector(zcu)) continue :inst try l.scalarize(inst, .pl_op_bin);666 if (l.typeOf(pl_op.operand).isVector(zcu)) continue :inst try l.scalarize(inst, .pl_op_bin);
615 },667 },
...@@ -636,6 +688,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -636,6 +688,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
636}688}
637689
638const ScalarizeForm = enum { un_op, ty_op, bin_op, pl_op_bin, bitcast, cmp_vector, shuffle_one, shuffle_two, select };690const ScalarizeForm = enum { un_op, ty_op, bin_op, pl_op_bin, bitcast, cmp_vector, shuffle_one, shuffle_two, select };
691/// inline to propagate comptime-known `replaceInst` result.
639inline fn scalarize(l: *Legalize, orig_inst: Air.Inst.Index, comptime form: ScalarizeForm) Error!Air.Inst.Tag {692inline fn scalarize(l: *Legalize, orig_inst: Air.Inst.Index, comptime form: ScalarizeForm) Error!Air.Inst.Tag {
640 return l.replaceInst(orig_inst, .block, try l.scalarizeBlockPayload(orig_inst, form));693 return l.replaceInst(orig_inst, .block, try l.scalarizeBlockPayload(orig_inst, form));
641}694}
...@@ -1972,6 +2025,115 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In...@@ -1972,6 +2025,115 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
1972 .payload = try l.addBlockBody(main_block.body()),2025 .payload = try l.addBlockBody(main_block.body()),
1973 } };2026 } };
1974}2027}
2028fn safeIntFromFloatBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, optimized: bool) Error!Air.Inst.Data {
2029 const pt = l.pt;
2030 const zcu = pt.zcu;
2031 const gpa = zcu.gpa;
2032 const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op;
2033
2034 const operand_ref = ty_op.operand;
2035 const operand_ty = l.typeOf(operand_ref);
2036 const dest_ty = ty_op.ty.toType();
2037
2038 const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
2039 const dest_scalar_ty = dest_ty.scalarType(zcu);
2040 const int_info = dest_scalar_ty.intInfo(zcu);
2041
2042 // We emit 9 instructions in the worst case.
2043 var inst_buf: [9]Air.Inst.Index = undefined;
2044 try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len);
2045 var main_block: Block = .init(&inst_buf);
2046
2047 // This check is a bit annoying because of floating-point rounding and the fact that this
2048 // builtin truncates. We'll use a bigint for our calculations, because we need to construct
2049 // integers exceeding the bounds of the result integer type, and we need to convert it to a
2050 // float with a specific rounding mode to avoid errors.
2051 // Our bigint may exceed the twos complement limit by one, so add an extra limb.
2052 const limbs = try gpa.alloc(
2053 std.math.big.Limb,
2054 std.math.big.int.calcTwosCompLimbCount(int_info.bits) + 1,
2055 );
2056 defer gpa.free(limbs);
2057 var big: std.math.big.int.Mutable = .init(limbs, 0);
2058
2059 // Check if the operand is lower than `min_int` when truncated to an integer.
2060 big.setTwosCompIntLimit(.min, int_info.signedness, int_info.bits);
2061 const below_min_inst: Air.Inst.Index = if (!big.positive or big.eqlZero()) bad: {
2062 // `min_int <= 0`, so check for `x <= min_int - 1`.
2063 big.addScalar(big.toConst(), -1);
2064 // For `<=`, we must round the RHS down, so that this value is the first `x` which returns `true`.
2065 const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .floor);
2066 break :bad try main_block.addCmp(l, .lte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
2067 .vector = is_vector,
2068 .optimized = optimized,
2069 });
2070 } else {
2071 // `min_int > 0`, which is currently impossible. It would become possible under #3806, in
2072 // which case we must detect `x < min_int`.
2073 unreachable;
2074 };
2075
2076 // Check if the operand is greater than `max_int` when truncated to an integer.
2077 big.setTwosCompIntLimit(.max, int_info.signedness, int_info.bits);
2078 const above_max_inst: Air.Inst.Index = if (big.positive or big.eqlZero()) bad: {
2079 // `max_int >= 0`, so check for `x >= max_int + 1`.
2080 big.addScalar(big.toConst(), 1);
2081 // For `>=`, we must round the RHS up, so that this value is the first `x` which returns `true`.
2082 const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .ceil);
2083 break :bad try main_block.addCmp(l, .gte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
2084 .vector = is_vector,
2085 .optimized = optimized,
2086 });
2087 } else {
2088 // `max_int < 0`, which is currently impossible. It would become possible under #3806, in
2089 // which case we must detect `x > max_int`.
2090 unreachable;
2091 };
2092
2093 // Combine the conditions.
2094 const out_of_bounds_inst: Air.Inst.Index = main_block.add(l, .{
2095 .tag = .bool_or,
2096 .data = .{ .bin_op = .{
2097 .lhs = below_min_inst.toRef(),
2098 .rhs = above_max_inst.toRef(),
2099 } },
2100 });
2101 const scalar_out_of_bounds_inst: Air.Inst.Index = if (is_vector) main_block.add(l, .{
2102 .tag = .reduce,
2103 .data = .{ .reduce = .{
2104 .operand = out_of_bounds_inst.toRef(),
2105 .operation = .Or,
2106 } },
2107 }) else out_of_bounds_inst;
2108
2109 // Now emit the actual condbr. "true" will be safety panic. "false" will be "ok", meaning we do
2110 // the `int_from_float` and `br` the result to `orig_inst`.
2111 var condbr: CondBr = .init(l, scalar_out_of_bounds_inst.toRef(), &main_block, .{ .true = .cold });
2112 condbr.then_block = .init(main_block.stealRemainingCapacity());
2113 try condbr.then_block.addPanic(l, .integer_part_out_of_bounds);
2114 condbr.else_block = .init(condbr.then_block.stealRemainingCapacity());
2115 const cast_inst = condbr.else_block.add(l, .{
2116 .tag = if (optimized) .int_from_float_optimized else .int_from_float,
2117 .data = .{ .ty_op = .{
2118 .ty = Air.internedToRef(dest_ty.toIntern()),
2119 .operand = operand_ref,
2120 } },
2121 });
2122 _ = condbr.else_block.add(l, .{
2123 .tag = .br,
2124 .data = .{ .br = .{
2125 .block_inst = orig_inst,
2126 .operand = cast_inst.toRef(),
2127 } },
2128 });
2129 _ = condbr.else_block.stealRemainingCapacity(); // we might not have used it all
2130 try condbr.finish(l);
2131
2132 return .{ .ty_pl = .{
2133 .ty = Air.internedToRef(dest_ty.toIntern()),
2134 .payload = try l.addBlockBody(main_block.body()),
2135 } };
2136}
1975fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data {2137fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data {
1976 const pt = l.pt;2138 const pt = l.pt;
1977 const zcu = pt.zcu;2139 const zcu = pt.zcu;
...@@ -2349,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro...@@ -2349,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro
2349 } };2511 } };
2350}2512}
23512513
2514/// Given a `std.math.big.int.Const`, converts it to a `Value` which is a float of type `float_ty`
2515/// representing the same numeric value. If the integer cannot be exactly represented, `round`
2516/// decides whether the value should be rounded up or down. If `is_vector`, then `float_ty` is
2517/// instead a vector of floats, and the result value is a vector containing the converted scalar
2518/// repeated N times.
2519fn floatFromBigIntVal(
2520 pt: Zcu.PerThread,
2521 is_vector: bool,
2522 float_ty: Type,
2523 x: std.math.big.int.Const,
2524 round: std.math.big.int.Round,
2525) Error!Value {
2526 const zcu = pt.zcu;
2527 const scalar_ty = switch (is_vector) {
2528 true => float_ty.childType(zcu),
2529 false => float_ty,
2530 };
2531 assert(scalar_ty.zigTypeTag(zcu) == .float);
2532 const scalar_val: Value = switch (scalar_ty.floatBits(zcu.getTarget())) {
2533 16 => try pt.floatValue(scalar_ty, x.toFloat(f16, round)[0]),
2534 32 => try pt.floatValue(scalar_ty, x.toFloat(f32, round)[0]),
2535 64 => try pt.floatValue(scalar_ty, x.toFloat(f64, round)[0]),
2536 80 => try pt.floatValue(scalar_ty, x.toFloat(f80, round)[0]),
2537 128 => try pt.floatValue(scalar_ty, x.toFloat(f128, round)[0]),
2538 else => unreachable,
2539 };
2540 if (is_vector) {
2541 return .fromInterned(try pt.intern(.{ .aggregate = .{
2542 .ty = float_ty.toIntern(),
2543 .storage = .{ .repeated_elem = scalar_val.toIntern() },
2544 } }));
2545 } else {
2546 return scalar_val;
2547 }
2548}
2549
2352const Block = struct {2550const Block = struct {
2353 instructions: []Air.Inst.Index,2551 instructions: []Air.Inst.Index,
2354 len: usize,2552 len: usize,
...@@ -2691,7 +2889,7 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 {...@@ -2691,7 +2889,7 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 {
2691}2889}
26922890
2693/// Returns `tag` to remind the caller to `continue :inst` the result.2891/// Returns `tag` to remind the caller to `continue :inst` the result.
2694/// This is inline to propagate the comptime-known `tag`.2892/// `inline` to propagate the comptime-known `tag` result.
2695inline fn replaceInst(l: *Legalize, inst: Air.Inst.Index, comptime tag: Air.Inst.Tag, data: Air.Inst.Data) Air.Inst.Tag {2893inline fn replaceInst(l: *Legalize, inst: Air.Inst.Index, comptime tag: Air.Inst.Tag, data: Air.Inst.Data) Air.Inst.Tag {
2696 const orig_ty = if (std.debug.runtime_safety) l.typeOfIndex(inst) else {};2894 const orig_ty = if (std.debug.runtime_safety) l.typeOfIndex(inst) else {};
2697 l.air_instructions.set(@intFromEnum(inst), .{ .tag = tag, .data = data });2895 l.air_instructions.set(@intFromEnum(inst), .{ .tag = tag, .data = data });
...@@ -2706,4 +2904,5 @@ const InternPool = @import("../InternPool.zig");...@@ -2706,4 +2904,5 @@ const InternPool = @import("../InternPool.zig");
2706const Legalize = @This();2904const Legalize = @This();
2707const std = @import("std");2905const std = @import("std");
2708const Type = @import("../Type.zig");2906const Type = @import("../Type.zig");
2907const Value = @import("../Value.zig");
2709const Zcu = @import("../Zcu.zig");2908const Zcu = @import("../Zcu.zig");
src/Air/Liveness.zig+4
...@@ -374,6 +374,8 @@ pub fn categorizeOperand(...@@ -374,6 +374,8 @@ pub fn categorizeOperand(
374 .array_to_slice,374 .array_to_slice,
375 .int_from_float,375 .int_from_float,
376 .int_from_float_optimized,376 .int_from_float_optimized,
377 .int_from_float_safe,
378 .int_from_float_optimized_safe,
377 .float_from_int,379 .float_from_int,
378 .get_union_tag,380 .get_union_tag,
379 .clz,381 .clz,
...@@ -1015,6 +1017,8 @@ fn analyzeInst(...@@ -1015,6 +1017,8 @@ fn analyzeInst(
1015 .array_to_slice,1017 .array_to_slice,
1016 .int_from_float,1018 .int_from_float,
1017 .int_from_float_optimized,1019 .int_from_float_optimized,
1020 .int_from_float_safe,
1021 .int_from_float_optimized_safe,
1018 .float_from_int,1022 .float_from_int,
1019 .get_union_tag,1023 .get_union_tag,
1020 .clz,1024 .clz,
src/Air/Liveness/Verify.zig+2
...@@ -107,6 +107,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -107,6 +107,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
107 .array_to_slice,107 .array_to_slice,
108 .int_from_float,108 .int_from_float,
109 .int_from_float_optimized,109 .int_from_float_optimized,
110 .int_from_float_safe,
111 .int_from_float_optimized_safe,
110 .float_from_int,112 .float_from_int,
111 .get_union_tag,113 .get_union_tag,
112 .clz,114 .clz,
src/Air/print.zig+2
...@@ -250,6 +250,8 @@ const Writer = struct {...@@ -250,6 +250,8 @@ const Writer = struct {
250 .splat,250 .splat,
251 .int_from_float,251 .int_from_float,
252 .int_from_float_optimized,252 .int_from_float_optimized,
253 .int_from_float_safe,
254 .int_from_float_optimized_safe,
253 .get_union_tag,255 .get_union_tag,
254 .clz,256 .clz,
255 .ctz,257 .ctz,
src/Air/types_resolved.zig+2
...@@ -130,6 +130,8 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {...@@ -130,6 +130,8 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
130 .array_to_slice,130 .array_to_slice,
131 .int_from_float,131 .int_from_float,
132 .int_from_float_optimized,132 .int_from_float_optimized,
133 .int_from_float_safe,
134 .int_from_float_optimized_safe,
133 .float_from_int,135 .float_from_int,
134 .splat,136 .splat,
135 .error_set_has_value,137 .error_set_has_value,
src/Sema.zig+74-106
...@@ -8934,9 +8934,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -8934,9 +8934,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
89348934
8935 try sema.requireRuntimeBlock(block, src, operand_src);8935 try sema.requireRuntimeBlock(block, src, operand_src);
8936 if (block.wantSafety()) {8936 if (block.wantSafety()) {
8937 if (zcu.backendSupportsFeature(.panic_fn)) {8937 try sema.preparePanicId(src, .invalid_enum_value);
8938 _ = try sema.preparePanicId(src, .invalid_enum_value);
8939 }
8940 return block.addTyOp(.intcast_safe, dest_ty, operand);8938 return block.addTyOp(.intcast_safe, dest_ty, operand);
8941 }8939 }
8942 return block.addTyOp(.intcast, dest_ty, operand);8940 return block.addTyOp(.intcast, dest_ty, operand);
...@@ -10340,9 +10338,7 @@ fn intCast(...@@ -10340,9 +10338,7 @@ fn intCast(
1034010338
10341 try sema.requireRuntimeBlock(block, src, operand_src);10339 try sema.requireRuntimeBlock(block, src, operand_src);
10342 if (block.wantSafety()) {10340 if (block.wantSafety()) {
10343 if (zcu.backendSupportsFeature(.panic_fn)) {10341 try sema.preparePanicId(src, .integer_out_of_bounds);
10344 _ = try sema.preparePanicId(src, .integer_out_of_bounds);
10345 }
10346 return block.addTyOp(.intcast_safe, dest_ty, operand);10342 return block.addTyOp(.intcast_safe, dest_ty, operand);
10347 }10343 }
10348 return block.addTyOp(.intcast, dest_ty, operand);10344 return block.addTyOp(.intcast, dest_ty, operand);
...@@ -16395,9 +16391,7 @@ fn analyzeArithmetic(...@@ -16395,9 +16391,7 @@ fn analyzeArithmetic(
16395 }16391 }
1639616392
16397 if (block.wantSafety() and want_safety and scalar_tag == .int) {16393 if (block.wantSafety() and want_safety and scalar_tag == .int) {
16398 if (air_tag != air_tag_safe and zcu.backendSupportsFeature(.panic_fn)) {16394 if (air_tag != air_tag_safe) try sema.preparePanicId(src, .integer_overflow);
16399 _ = try sema.preparePanicId(src, .integer_overflow);
16400 }
16401 return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);16395 return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
16402 }16396 }
16403 return block.addBinOp(air_tag, casted_lhs, casted_rhs);16397 return block.addBinOp(air_tag, casted_lhs, casted_rhs);
...@@ -22178,44 +22172,32 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro...@@ -22178,44 +22172,32 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
2217822172
22179 try sema.requireRuntimeBlock(block, src, operand_src);22173 try sema.requireRuntimeBlock(block, src, operand_src);
22180 if (dest_scalar_ty.intInfo(zcu).bits == 0) {22174 if (dest_scalar_ty.intInfo(zcu).bits == 0) {
22181 if (!is_vector) {
22182 if (block.wantSafety()) {
22183 const ok = try block.addBinOp(if (block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, operand, Air.internedToRef((try pt.floatValue(operand_ty, 0.0)).toIntern()));
22184 try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
22185 }
22186 return Air.internedToRef((try pt.intValue(dest_ty, 0)).toIntern());
22187 }
22188 if (block.wantSafety()) {22175 if (block.wantSafety()) {
22189 const len = dest_ty.vectorLen(zcu);22176 // Emit an explicit safety check. We can do this one like `abs(x) < 1`.
22190 for (0..len) |i| {22177 const abs_ref = try block.addTyOp(.abs, operand_ty, operand);
22191 const idx_ref = try pt.intRef(.usize, i);22178 const max_abs_ref = if (is_vector) try block.addReduce(abs_ref, .Max) else abs_ref;
22192 const elem_ref = try block.addBinOp(.array_elem_val, operand, idx_ref);22179 const one_ref = Air.internedToRef((try pt.floatValue(operand_scalar_ty, 1.0)).toIntern());
22193 const ok = try block.addBinOp(if (block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, elem_ref, Air.internedToRef((try pt.floatValue(operand_scalar_ty, 0.0)).toIntern()));22180 const ok_ref = try block.addBinOp(.cmp_lt, max_abs_ref, one_ref);
22194 try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);22181 try sema.addSafetyCheck(block, src, ok_ref, .integer_part_out_of_bounds);
22195 }22182 }
22196 }22183 const scalar_val = try pt.intValue(dest_scalar_ty, 0);
22184 if (!is_vector) return Air.internedToRef(scalar_val.toIntern());
22197 return Air.internedToRef(try pt.intern(.{ .aggregate = .{22185 return Air.internedToRef(try pt.intern(.{ .aggregate = .{
22198 .ty = dest_ty.toIntern(),22186 .ty = dest_ty.toIntern(),
22199 .storage = .{ .repeated_elem = (try pt.intValue(dest_scalar_ty, 0)).toIntern() },22187 .storage = .{ .repeated_elem = scalar_val.toIntern() },
22200 } }));22188 } }));
22201 }22189 }
22202 const result = try block.addTyOp(if (block.float_mode == .optimized) .int_from_float_optimized else .int_from_float, dest_ty, operand);
22203 if (block.wantSafety()) {22190 if (block.wantSafety()) {
22204 const back = try block.addTyOp(.float_from_int, operand_ty, result);22191 try sema.preparePanicId(src, .integer_part_out_of_bounds);
22205 const diff = try block.addBinOp(if (block.float_mode == .optimized) .sub_optimized else .sub, operand, back);22192 return block.addTyOp(switch (block.float_mode) {
22206 const ok = if (is_vector) ok: {22193 .optimized => .int_from_float_optimized_safe,
22207 const ok_pos = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, 1.0))).toIntern()), .lt);22194 .strict => .int_from_float_safe,
22208 const ok_neg = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, -1.0))).toIntern()), .gt);22195 }, dest_ty, operand);
22209 const ok = try block.addBinOp(.bit_and, ok_pos, ok_neg);
22210 break :ok try block.addReduce(ok, .And);
22211 } else ok: {
22212 const ok_pos = try block.addBinOp(if (block.float_mode == .optimized) .cmp_lt_optimized else .cmp_lt, diff, Air.internedToRef((try pt.floatValue(operand_ty, 1.0)).toIntern()));
22213 const ok_neg = try block.addBinOp(if (block.float_mode == .optimized) .cmp_gt_optimized else .cmp_gt, diff, Air.internedToRef((try pt.floatValue(operand_ty, -1.0)).toIntern()));
22214 break :ok try block.addBinOp(.bool_and, ok_pos, ok_neg);
22215 };
22216 try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
22217 }22196 }
22218 return result;22197 return block.addTyOp(switch (block.float_mode) {
22198 .optimized => .int_from_float_optimized,
22199 .strict => .int_from_float,
22200 }, dest_ty, operand);
22219}22201}
2222022202
22221fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {22203fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
...@@ -26871,7 +26853,15 @@ fn explainWhyTypeIsNotPacked(...@@ -26871,7 +26853,15 @@ fn explainWhyTypeIsNotPacked(
26871/// Backends depend on panic decls being available when lowering safety-checked26853/// Backends depend on panic decls being available when lowering safety-checked
26872/// instructions. This function ensures the panic function will be available to26854/// instructions. This function ensures the panic function will be available to
26873/// be called during that time.26855/// be called during that time.
26874fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index {26856fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !void {
26857 // If the backend doesn't support `.panic_fn`, it doesn't want us to lower the panic handlers.
26858 // The backend will transform panics into traps instead.
26859 if (sema.pt.zcu.backendSupportsFeature(.panic_fn)) {
26860 _ = try sema.getPanicIdFunc(src, panic_id);
26861 }
26862}
26863
26864fn getPanicIdFunc(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index {
26875 const zcu = sema.pt.zcu;26865 const zcu = sema.pt.zcu;
26876 try sema.ensureMemoizedStateResolved(src, .panic);26866 try sema.ensureMemoizedStateResolved(src, .panic);
26877 const panic_func = zcu.builtin_decl_values.get(panic_id.toBuiltin());26867 const panic_func = zcu.builtin_decl_values.get(panic_id.toBuiltin());
...@@ -27120,7 +27110,7 @@ fn safetyPanic(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.Simple...@@ -27120,7 +27110,7 @@ fn safetyPanic(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.Simple
27120 if (!sema.pt.zcu.backendSupportsFeature(.panic_fn)) {27110 if (!sema.pt.zcu.backendSupportsFeature(.panic_fn)) {
27121 _ = try block.addNoOp(.trap);27111 _ = try block.addNoOp(.trap);
27122 } else {27112 } else {
27123 const panic_fn = try sema.preparePanicId(src, panic_id);27113 const panic_fn = try sema.getPanicIdFunc(src, panic_id);
27124 try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{}, .@"safety check");27114 try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{}, .@"safety check");
27125 }27115 }
27126}27116}
...@@ -32843,24 +32833,21 @@ fn cmpNumeric(...@@ -32843,24 +32833,21 @@ fn cmpNumeric(
32843 }32833 }
32844 }32834 }
32845 if (lhs_is_float) {32835 if (lhs_is_float) {
32846 if (lhs_val.floatHasFraction(zcu)) {32836 const float = lhs_val.toFloat(f128, zcu);
32847 switch (op) {32837 var big_int: std.math.big.int.Mutable = .{
32838 .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
32839 .len = undefined,
32840 .positive = undefined,
32841 };
32842 switch (big_int.setFloat(float, .away)) {
32843 .inexact => switch (op) {
32848 .eq => return .bool_false,32844 .eq => return .bool_false,
32849 .neq => return .bool_true,32845 .neq => return .bool_true,
32850 else => {},32846 else => {},
32851 }32847 },
32852 }32848 .exact => {},
32853
32854 var bigint = try float128IntPartToBigInt(sema.gpa, lhs_val.toFloat(f128, zcu));
32855 defer bigint.deinit();
32856 if (lhs_val.floatHasFraction(zcu)) {
32857 if (lhs_is_signed) {
32858 try bigint.addScalar(&bigint, -1);
32859 } else {
32860 try bigint.addScalar(&bigint, 1);
32861 }
32862 }32849 }
32863 lhs_bits = bigint.toConst().bitCountTwosComp();32850 lhs_bits = big_int.toConst().bitCountTwosComp();
32864 } else {32851 } else {
32865 lhs_bits = lhs_val.intBitCountTwosComp(zcu);32852 lhs_bits = lhs_val.intBitCountTwosComp(zcu);
32866 }32853 }
...@@ -32890,24 +32877,21 @@ fn cmpNumeric(...@@ -32890,24 +32877,21 @@ fn cmpNumeric(
32890 }32877 }
32891 }32878 }
32892 if (rhs_is_float) {32879 if (rhs_is_float) {
32893 if (rhs_val.floatHasFraction(zcu)) {32880 const float = rhs_val.toFloat(f128, zcu);
32894 switch (op) {32881 var big_int: std.math.big.int.Mutable = .{
32882 .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
32883 .len = undefined,
32884 .positive = undefined,
32885 };
32886 switch (big_int.setFloat(float, .away)) {
32887 .inexact => switch (op) {
32895 .eq => return .bool_false,32888 .eq => return .bool_false,
32896 .neq => return .bool_true,32889 .neq => return .bool_true,
32897 else => {},32890 else => {},
32898 }32891 },
32899 }32892 .exact => {},
32900
32901 var bigint = try float128IntPartToBigInt(sema.gpa, rhs_val.toFloat(f128, zcu));
32902 defer bigint.deinit();
32903 if (rhs_val.floatHasFraction(zcu)) {
32904 if (rhs_is_signed) {
32905 try bigint.addScalar(&bigint, -1);
32906 } else {
32907 try bigint.addScalar(&bigint, 1);
32908 }
32909 }32893 }
32910 rhs_bits = bigint.toConst().bitCountTwosComp();32894 rhs_bits = big_int.toConst().bitCountTwosComp();
32911 } else {32895 } else {
32912 rhs_bits = rhs_val.intBitCountTwosComp(zcu);32896 rhs_bits = rhs_val.intBitCountTwosComp(zcu);
32913 }32897 }
...@@ -36955,31 +36939,6 @@ fn intFromFloat(...@@ -36955,31 +36939,6 @@ fn intFromFloat(
36955 return sema.intFromFloatScalar(block, src, val, int_ty, mode);36939 return sema.intFromFloatScalar(block, src, val, int_ty, mode);
36956}36940}
3695736941
36958// float is expected to be finite and non-NaN
36959fn float128IntPartToBigInt(
36960 arena: Allocator,
36961 float: f128,
36962) !std.math.big.int.Managed {
36963 const is_negative = std.math.signbit(float);
36964 const floored = @floor(@abs(float));
36965
36966 var rational = try std.math.big.Rational.init(arena);
36967 defer rational.q.deinit();
36968 rational.setFloat(f128, floored) catch |err| switch (err) {
36969 error.NonFiniteFloat => unreachable,
36970 error.OutOfMemory => return error.OutOfMemory,
36971 };
36972
36973 // The float is reduced in rational.setFloat, so we assert that denominator is equal to one
36974 const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true };
36975 assert(rational.q.toConst().eqlAbs(big_one));
36976
36977 if (is_negative) {
36978 rational.negate();
36979 }
36980 return rational.p;
36981}
36982
36983fn intFromFloatScalar(36942fn intFromFloatScalar(
36984 sema: *Sema,36943 sema: *Sema,
36985 block: *Block,36944 block: *Block,
...@@ -36993,13 +36952,6 @@ fn intFromFloatScalar(...@@ -36993,13 +36952,6 @@ fn intFromFloatScalar(
3699336952
36994 if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src);36953 if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src);
3699536954
36996 if (mode == .exact and val.floatHasFraction(zcu)) return sema.fail(
36997 block,
36998 src,
36999 "fractional component prevents float value '{}' from coercion to type '{}'",
37000 .{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },
37001 );
37002
37003 const float = val.toFloat(f128, zcu);36955 const float = val.toFloat(f128, zcu);
37004 if (std.math.isNan(float)) {36956 if (std.math.isNan(float)) {
37005 return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{36957 return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{
...@@ -37012,12 +36964,28 @@ fn intFromFloatScalar(...@@ -37012,12 +36964,28 @@ fn intFromFloatScalar(
37012 });36964 });
37013 }36965 }
3701436966
37015 var big_int = try float128IntPartToBigInt(sema.arena, float);36967 var big_int: std.math.big.int.Mutable = .{
37016 defer big_int.deinit();36968 .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
3701736969 .len = undefined,
36970 .positive = undefined,
36971 };
36972 switch (big_int.setFloat(float, .trunc)) {
36973 .inexact => switch (mode) {
36974 .exact => return sema.fail(
36975 block,
36976 src,
36977 "fractional component prevents float value '{}' from coercion to type '{}'",
36978 .{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },
36979 ),
36980 .truncate => {},
36981 },
36982 .exact => {},
36983 }
37018 const cti_result = try pt.intValue_big(.comptime_int, big_int.toConst());36984 const cti_result = try pt.intValue_big(.comptime_int, big_int.toConst());
36985 if (int_ty.toIntern() == .comptime_int_type) return cti_result;
3701936986
37020 if (!(try sema.intFitsInType(cti_result, int_ty, null))) {36987 const int_info = int_ty.intInfo(zcu);
36988 if (!big_int.toConst().fitsInTwosComp(int_info.signedness, int_info.bits)) {
37021 return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{36989 return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{
37022 val.fmtValueSema(pt, sema), int_ty.fmt(pt),36990 val.fmtValueSema(pt, sema), int_ty.fmt(pt),
37023 });36991 });
src/Sema/LowerZon.zig+12-19
...@@ -509,30 +509,23 @@ fn lowerInt(...@@ -509,30 +509,23 @@ fn lowerInt(
509 },509 },
510 },510 },
511 .float_literal => |val| {511 .float_literal => |val| {
512 // Check for fractional components512 var big_int: std.math.big.int.Mutable = .{
513 if (@rem(val, 1) != 0) {513 .limbs = try self.sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(val)),
514 return self.fail(514 .len = undefined,
515 .positive = undefined,
516 };
517 switch (big_int.setFloat(val, .trunc)) {
518 .inexact => return self.fail(
515 node,519 node,
516 "fractional component prevents float value '{}' from coercion to type '{}'",520 "fractional component prevents float value '{}' from coercion to type '{}'",
517 .{ val, res_ty.fmt(self.sema.pt) },521 .{ val, res_ty.fmt(self.sema.pt) },
518 );522 ),
523 .exact => {},
519 }524 }
520525
521 // Create a rational representation of the float
522 var rational = try std.math.big.Rational.init(self.sema.arena);
523 rational.setFloat(f128, val) catch |err| switch (err) {
524 error.NonFiniteFloat => unreachable,
525 error.OutOfMemory => return error.OutOfMemory,
526 };
527
528 // The float is reduced in rational.setFloat, so we assert that denominator is equal to
529 // one
530 const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true };
531 assert(rational.q.toConst().eqlAbs(big_one));
532
533 // Check that the result is in range of the result type526 // Check that the result is in range of the result type
534 const int_info = res_ty.intInfo(self.sema.pt.zcu);527 const int_info = res_ty.intInfo(self.sema.pt.zcu);
535 if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) {528 if (!big_int.toConst().fitsInTwosComp(int_info.signedness, int_info.bits)) {
536 return self.fail(529 return self.fail(
537 node,530 node,
538 "type '{}' cannot represent integer value '{}'",531 "type '{}' cannot represent integer value '{}'",
...@@ -543,7 +536,7 @@ fn lowerInt(...@@ -543,7 +536,7 @@ fn lowerInt(
543 return self.sema.pt.intern(.{536 return self.sema.pt.intern(.{
544 .int = .{537 .int = .{
545 .ty = res_ty.toIntern(),538 .ty = res_ty.toIntern(),
546 .storage = .{ .big_int = rational.p.toConst() },539 .storage = .{ .big_int = big_int.toConst() },
547 },540 },
548 });541 });
549 },542 },
...@@ -584,7 +577,7 @@ fn lowerFloat(...@@ -584,7 +577,7 @@ fn lowerFloat(
584 const value = switch (node.get(self.file.zoir.?)) {577 const value = switch (node.get(self.file.zoir.?)) {
585 .int_literal => |int| switch (int) {578 .int_literal => |int| switch (int) {
586 .small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),579 .small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
587 .big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128)),580 .big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128, .nearest_even)[0]),
588 },581 },
589 .float_literal => |val| try self.sema.pt.floatValue(res_ty, val),582 .float_literal => |val| try self.sema.pt.floatValue(res_ty, val),
590 .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),583 .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
src/Value.zig+5-19
...@@ -898,7 +898,7 @@ pub fn readFromPackedMemory(...@@ -898,7 +898,7 @@ pub fn readFromPackedMemory(
898pub fn toFloat(val: Value, comptime T: type, zcu: *const Zcu) T {898pub fn toFloat(val: Value, comptime T: type, zcu: *const Zcu) T {
899 return switch (zcu.intern_pool.indexToKey(val.toIntern())) {899 return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
900 .int => |int| switch (int.storage) {900 .int => |int| switch (int.storage) {
901 .big_int => |big_int| big_int.toFloat(T),901 .big_int => |big_int| big_int.toFloat(T, .nearest_even)[0],
902 inline .u64, .i64 => |x| {902 inline .u64, .i64 => |x| {
903 if (T == f80) {903 if (T == f80) {
904 @panic("TODO we can't lower this properly on non-x86 llvm backend yet");904 @panic("TODO we can't lower this properly on non-x86 llvm backend yet");
...@@ -997,16 +997,6 @@ pub fn floatCast(val: Value, dest_ty: Type, pt: Zcu.PerThread) !Value {...@@ -997,16 +997,6 @@ pub fn floatCast(val: Value, dest_ty: Type, pt: Zcu.PerThread) !Value {
997 } }));997 } }));
998}998}
999999
1000/// Asserts the value is a float
1001pub fn floatHasFraction(self: Value, zcu: *const Zcu) bool {
1002 return switch (zcu.intern_pool.indexToKey(self.toIntern())) {
1003 .float => |float| switch (float.storage) {
1004 inline else => |x| @rem(x, 1) != 0,
1005 },
1006 else => unreachable,
1007 };
1008}
1009
1010pub fn orderAgainstZero(lhs: Value, zcu: *Zcu) std.math.Order {1000pub fn orderAgainstZero(lhs: Value, zcu: *Zcu) std.math.Order {
1011 return orderAgainstZeroInner(lhs, .normal, zcu, {}) catch unreachable;1001 return orderAgainstZeroInner(lhs, .normal, zcu, {}) catch unreachable;
1012}1002}
...@@ -1557,17 +1547,13 @@ pub fn floatFromIntAdvanced(...@@ -1557,17 +1547,13 @@ pub fn floatFromIntAdvanced(
1557}1547}
15581548
1559pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptime strat: ResolveStrat) !Value {1549pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptime strat: ResolveStrat) !Value {
1560 const zcu = pt.zcu;1550 return switch (pt.zcu.intern_pool.indexToKey(val.toIntern())) {
1561 return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
1562 .undef => try pt.undefValue(float_ty),1551 .undef => try pt.undefValue(float_ty),
1563 .int => |int| switch (int.storage) {1552 .int => |int| switch (int.storage) {
1564 .big_int => |big_int| {1553 .big_int => |big_int| pt.floatValue(float_ty, big_int.toFloat(f128, .nearest_even)[0]),
1565 const float = big_int.toFloat(f128);
1566 return pt.floatValue(float_ty, float);
1567 },
1568 inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt),1554 inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt),
1569 .lazy_align => |ty| return floatFromIntInner((try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), pt.zcu, pt.tid)).scalar.toByteUnits() orelse 0, float_ty, pt),1555 .lazy_align => |ty| floatFromIntInner((try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), pt.zcu, pt.tid)).scalar.toByteUnits() orelse 0, float_ty, pt),
1570 .lazy_size => |ty| return floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, float_ty, pt),1556 .lazy_size => |ty| floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, float_ty, pt),
1571 },1557 },
1572 else => unreachable,1558 else => unreachable,
1573 };1559 };
src/arch/aarch64/CodeGen.zig+2
...@@ -861,6 +861,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -861,6 +861,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
861 .sub_safe,861 .sub_safe,
862 .mul_safe,862 .mul_safe,
863 .intcast_safe,863 .intcast_safe,
864 .int_from_float_safe,
865 .int_from_float_optimized_safe,
864 => return self.fail("TODO implement safety_checked_instructions", .{}),866 => return self.fail("TODO implement safety_checked_instructions", .{}),
865867
866 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),868 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
src/arch/arm/CodeGen.zig+2
...@@ -850,6 +850,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -850,6 +850,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
850 .sub_safe,850 .sub_safe,
851 .mul_safe,851 .mul_safe,
852 .intcast_safe,852 .intcast_safe,
853 .int_from_float_safe,
854 .int_from_float_optimized_safe,
853 => return self.fail("TODO implement safety_checked_instructions", .{}),855 => return self.fail("TODO implement safety_checked_instructions", .{}),
854856
855 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),857 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
src/arch/riscv64/CodeGen.zig+4
...@@ -54,6 +54,8 @@ const InnerError = CodeGenError || error{OutOfRegisters};...@@ -54,6 +54,8 @@ const InnerError = CodeGenError || error{OutOfRegisters};
54pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {54pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
55 return comptime &.initMany(&.{55 return comptime &.initMany(&.{
56 .expand_intcast_safe,56 .expand_intcast_safe,
57 .expand_int_from_float_safe,
58 .expand_int_from_float_optimized_safe,
57 .expand_add_safe,59 .expand_add_safe,
58 .expand_sub_safe,60 .expand_sub_safe,
59 .expand_mul_safe,61 .expand_mul_safe,
...@@ -1474,6 +1476,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {...@@ -1474,6 +1476,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
1474 .sub_safe,1476 .sub_safe,
1475 .mul_safe,1477 .mul_safe,
1476 .intcast_safe,1478 .intcast_safe,
1479 .int_from_float_safe,
1480 .int_from_float_optimized_safe,
1477 => return func.fail("TODO implement safety_checked_instructions", .{}),1481 => return func.fail("TODO implement safety_checked_instructions", .{}),
14781482
1479 .cmp_lt,1483 .cmp_lt,
src/arch/sparc64/CodeGen.zig+2
...@@ -696,6 +696,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -696,6 +696,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
696 .sub_safe,696 .sub_safe,
697 .mul_safe,697 .mul_safe,
698 .intcast_safe,698 .intcast_safe,
699 .int_from_float_safe,
700 .int_from_float_optimized_safe,
699 => @panic("TODO implement safety_checked_instructions"),701 => @panic("TODO implement safety_checked_instructions"),
700702
701 .is_named_enum_value => @panic("TODO implement is_named_enum_value"),703 .is_named_enum_value => @panic("TODO implement is_named_enum_value"),
src/arch/wasm/CodeGen.zig+4
...@@ -31,6 +31,8 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;...@@ -31,6 +31,8 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
31pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {31pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
32 return comptime &.initMany(&.{32 return comptime &.initMany(&.{
33 .expand_intcast_safe,33 .expand_intcast_safe,
34 .expand_int_from_float_safe,
35 .expand_int_from_float_optimized_safe,
34 .expand_add_safe,36 .expand_add_safe,
35 .expand_sub_safe,37 .expand_sub_safe,
36 .expand_mul_safe,38 .expand_mul_safe,
...@@ -2020,6 +2022,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -2020,6 +2022,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
2020 .sub_safe,2022 .sub_safe,
2021 .mul_safe,2023 .mul_safe,
2022 .intcast_safe,2024 .intcast_safe,
2025 .int_from_float_safe,
2026 .int_from_float_optimized_safe,
2023 => return cg.fail("TODO implement safety_checked_instructions", .{}),2027 => return cg.fail("TODO implement safety_checked_instructions", .{}),
20242028
2025 .work_item_id,2029 .work_item_id,
src/arch/x86_64/CodeGen.zig+4
...@@ -102,6 +102,8 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features...@@ -102,6 +102,8 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features
102 .reduce_one_elem_to_bitcast = true,102 .reduce_one_elem_to_bitcast = true,
103103
104 .expand_intcast_safe = true,104 .expand_intcast_safe = true,
105 .expand_int_from_float_safe = true,
106 .expand_int_from_float_optimized_safe = true,
105 .expand_add_safe = true,107 .expand_add_safe = true,
106 .expand_sub_safe = true,108 .expand_sub_safe = true,
107 .expand_mul_safe = true,109 .expand_mul_safe = true,
...@@ -107763,6 +107765,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {...@@ -107763,6 +107765,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
107763 };107765 };
107764 try res[0].finish(inst, &.{ty_op.operand}, &ops, cg);107766 try res[0].finish(inst, &.{ty_op.operand}, &ops, cg);
107765 },107767 },
107768 .int_from_float_safe => unreachable,
107769 .int_from_float_optimized_safe => unreachable,
107766 .float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else {107770 .float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else {
107767 const ty_op = air_datas[@intFromEnum(inst)].ty_op;107771 const ty_op = air_datas[@intFromEnum(inst)].ty_op;
107768 var ops = try cg.tempsFromOperands(inst, .{ty_op.operand});107772 var ops = try cg.tempsFromOperands(inst, .{ty_op.operand});
src/codegen/c.zig+17-6
...@@ -23,12 +23,21 @@ const BigIntLimb = std.math.big.Limb;...@@ -23,12 +23,21 @@ const BigIntLimb = std.math.big.Limb;
23const BigInt = std.math.big.int;23const BigInt = std.math.big.int;
2424
25pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {25pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
26 return if (dev.env.supports(.legalize)) comptime &.initMany(&.{26 return comptime switch (dev.env.supports(.legalize)) {
27 .expand_intcast_safe,27 inline false, true => |supports_legalize| &.init(.{
28 .expand_add_safe,28 // we don't currently ask zig1 to use safe optimization modes
29 .expand_sub_safe,29 .expand_intcast_safe = supports_legalize,
30 .expand_mul_safe,30 .expand_int_from_float_safe = supports_legalize,
31 }) else null; // we don't currently ask zig1 to use safe optimization modes31 .expand_int_from_float_optimized_safe = supports_legalize,
32 .expand_add_safe = supports_legalize,
33 .expand_sub_safe = supports_legalize,
34 .expand_mul_safe = supports_legalize,
35
36 .expand_packed_load = true,
37 .expand_packed_store = true,
38 .expand_packed_struct_field_val = true,
39 }),
40 };
32}41}
3342
34/// For most backends, MIR is basically a sequence of machine code instructions, perhaps with some43/// For most backends, MIR is basically a sequence of machine code instructions, perhaps with some
...@@ -3571,6 +3580,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,...@@ -3571,6 +3580,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
3571 .sub_safe,3580 .sub_safe,
3572 .mul_safe,3581 .mul_safe,
3573 .intcast_safe,3582 .intcast_safe,
3583 .int_from_float_safe,
3584 .int_from_float_optimized_safe,
3574 => return f.fail("TODO implement safety_checked_instructions", .{}),3585 => return f.fail("TODO implement safety_checked_instructions", .{}),
35753586
3576 .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),3587 .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
src/codegen/llvm.zig+6-1
...@@ -37,7 +37,10 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;...@@ -37,7 +37,10 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
37const Error = error{ OutOfMemory, CodegenFail };37const Error = error{ OutOfMemory, CodegenFail };
3838
39pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {39pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
40 return null;40 return comptime &.initMany(&.{
41 .expand_int_from_float_safe,
42 .expand_int_from_float_optimized_safe,
43 });
41}44}
4245
43fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 {46fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 {
...@@ -4987,6 +4990,8 @@ pub const FuncGen = struct {...@@ -4987,6 +4990,8 @@ pub const FuncGen = struct {
49874990
4988 .int_from_float => try self.airIntFromFloat(inst, .normal),4991 .int_from_float => try self.airIntFromFloat(inst, .normal),
4989 .int_from_float_optimized => try self.airIntFromFloat(inst, .fast),4992 .int_from_float_optimized => try self.airIntFromFloat(inst, .fast),
4993 .int_from_float_safe => unreachable, // handled by `legalizeFeatures`
4994 .int_from_float_optimized_safe => unreachable, // handled by `legalizeFeatures`
49904995
4991 .array_to_slice => try self.airArrayToSlice(inst),4996 .array_to_slice => try self.airArrayToSlice(inst),
4992 .float_from_int => try self.airFloatFromInt(inst),4997 .float_from_int => try self.airFloatFromInt(inst),
src/codegen/spirv.zig+2
...@@ -31,6 +31,8 @@ const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);...@@ -31,6 +31,8 @@ const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
31pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {31pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
32 return comptime &.initMany(&.{32 return comptime &.initMany(&.{
33 .expand_intcast_safe,33 .expand_intcast_safe,
34 .expand_int_from_float_safe,
35 .expand_int_from_float_optimized_safe,
34 .expand_add_safe,36 .expand_add_safe,
35 .expand_sub_safe,37 .expand_sub_safe,
36 .expand_mul_safe,38 .expand_mul_safe,
stage1/zig.h+9
...@@ -272,6 +272,15 @@...@@ -272,6 +272,15 @@
272#define zig_linksection_fn zig_linksection272#define zig_linksection_fn zig_linksection
273#endif273#endif
274274
275#if zig_has_attribute(visibility)
276#define zig_visibility(name) __attribute__((visibility(#name)))
277#else
278#define zig_visibility(name) zig_visibility_##name
279#define zig_visibility_default
280#define zig_visibility_hidden zig_visibility_hidden_unavailable
281#define zig_visibility_protected zig_visibility_protected_unavailable
282#endif
283
275#if zig_has_builtin(unreachable) || defined(zig_gcc) || defined(zig_tinyc)284#if zig_has_builtin(unreachable) || defined(zig_gcc) || defined(zig_tinyc)
276#define zig_unreachable() __builtin_unreachable()285#define zig_unreachable() __builtin_unreachable()
277#elif defined(zig_msvc)286#elif defined(zig_msvc)
stage1/zig1.wasm
Binary files a/stage1/zig1.wasm and b/stage1/zig1.wasm differ
test/behavior/cast.zig+64
...@@ -102,6 +102,7 @@ test "comptime_int @floatFromInt" {...@@ -102,6 +102,7 @@ test "comptime_int @floatFromInt" {
102test "@floatFromInt" {102test "@floatFromInt" {
103 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO103 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
104 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO104 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
105 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
105 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO106 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
106107
107 const S = struct {108 const S = struct {
...@@ -2737,3 +2738,66 @@ test "peer type resolution: slice of sentinel-terminated array" {...@@ -2737,3 +2738,66 @@ test "peer type resolution: slice of sentinel-terminated array" {
2737 try expect(result[0][0] == 10);2738 try expect(result[0][0] == 10);
2738 try expect(result[0][1] == 20);2739 try expect(result[0][1] == 20);
2739}2740}
2741
2742test "@intFromFloat boundary cases" {
2743 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
2744
2745 const S = struct {
2746 fn case(comptime I: type, x: f32, bump: enum { up, down }, expected: I) !void {
2747 const input: f32 = switch (bump) {
2748 .up => std.math.nextAfter(f32, x, std.math.inf(f32)),
2749 .down => std.math.nextAfter(f32, x, -std.math.inf(f32)),
2750 };
2751 const output: I = @intFromFloat(input);
2752 try expect(output == expected);
2753 }
2754 fn doTheTest() !void {
2755 try case(u8, 256.0, .down, 255);
2756 try case(u8, -1.0, .up, 0);
2757 try case(i8, 128.0, .down, 127);
2758 try case(i8, -129.0, .up, -128);
2759
2760 try case(u0, 1.0, .down, 0);
2761 try case(u0, -1.0, .up, 0);
2762 try case(i0, 1.0, .down, 0);
2763 try case(i0, -1.0, .up, 0);
2764
2765 try case(u10, 1024.0, .down, 1023);
2766 try case(u10, -1.0, .up, 0);
2767 try case(i10, 512.0, .down, 511);
2768 try case(i10, -513.0, .up, -512);
2769 }
2770 };
2771 try S.doTheTest();
2772 try comptime S.doTheTest();
2773}
2774
2775test "@intFromFloat vector boundary cases" {
2776 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
2777 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
2778 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
2779
2780 const S = struct {
2781 fn case(comptime I: type, unshifted_inputs: [2]f32, expected: [2]I) !void {
2782 const inputs: @Vector(2, f32) = .{
2783 std.math.nextAfter(f32, unshifted_inputs[0], std.math.inf(f32)),
2784 std.math.nextAfter(f32, unshifted_inputs[1], -std.math.inf(f32)),
2785 };
2786 const outputs: @Vector(2, I) = @intFromFloat(inputs);
2787 try expect(outputs[0] == expected[0]);
2788 try expect(outputs[1] == expected[1]);
2789 }
2790 fn doTheTest() !void {
2791 try case(u8, .{ -1.0, 256.0 }, .{ 0, 255 });
2792 try case(i8, .{ -129.0, 128.0 }, .{ -128, 127 });
2793
2794 try case(u0, .{ -1.0, 1.0 }, .{ 0, 0 });
2795 try case(i0, .{ -1.0, 1.0 }, .{ 0, 0 });
2796
2797 try case(u10, .{ -1.0, 1024.0 }, .{ 0, 1023 });
2798 try case(i10, .{ -513.0, 512.0 }, .{ -512, 511 });
2799 }
2800 };
2801 try S.doTheTest();
2802 try comptime S.doTheTest();
2803}
test/cases/safety/@intFromFloat cannot fit - boundary case - i0 max.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = 1.0;
10pub fn main() !void {
11 _ = @as(i0, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - i0 min.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = -1.0;
10pub fn main() !void {
11 _ = @as(i0, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - signed max.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = 128;
10pub fn main() !void {
11 _ = @as(i8, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - signed min.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = -129;
10pub fn main() !void {
11 _ = @as(i8, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - u0 max.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = 1.0;
10pub fn main() !void {
11 _ = @as(u0, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - u0 min.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = -1.0;
10pub fn main() !void {
11 _ = @as(u0, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - unsigned max.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = 256;
10pub fn main() !void {
11 _ = @as(u8, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - unsigned min.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = -1;
10pub fn main() !void {
11 _ = @as(u8, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - vector max.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: @Vector(2, f32) = .{ 100, 512 };
10pub fn main() !void {
11 _ = @as(@Vector(2, i10), @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - vector min.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: @Vector(2, f32) = .{ 100, -513 };
10pub fn main() !void {
11 _ = @as(@Vector(2, i10), @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native