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
148148 return .out_of_range;
149149 }
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
173151 const signedness = dest_ty.signedness(comp);
174152 const bits: usize = @intCast(dest_ty.bitSizeof(comp).?);
175153
176 // rational.p.truncate(rational.p.toConst(), signedness: Signedness, bit_count: usize)
177 const fits = rational.p.fitsInTwosComp(signedness, bits);
178 v.* = try intern(comp, .{ .int = .{ .big_int = rational.p.toConst() } });
179 try rational.p.truncate(&rational.p, signedness, bits);
154 var big_int: std.math.big.int.Mutable = .{
155 .limbs = try comp.gpa.alloc(std.math.big.Limb, @max(
156 std.math.big.int.calcLimbLen(float_val),
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
181171 if (!was_zero and v.isZero(comp)) return .nonzero_to_zero;
182172 if (!fits) return .out_of_range;
lib/std/math.zig+1
......@@ -45,6 +45,7 @@ pub const rad_per_deg = 0.017453292519943295769236907684886127134428718885417254
4545/// 180.0/pi
4646pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861;
4747
48pub const FloatRepr = float.FloatRepr;
4849pub const floatExponentBits = float.floatExponentBits;
4950pub const floatMantissaBits = float.floatMantissaBits;
5051pub const floatFractionalBits = float.floatFractionalBits;
lib/std/math/big.zig-2
......@@ -1,7 +1,6 @@
11const std = @import("../std.zig");
22const assert = std.debug.assert;
33
4pub const Rational = @import("big/rational.zig").Rational;
54pub const int = @import("big/int.zig");
65pub const Limb = usize;
76const limb_info = @typeInfo(Limb).int;
......@@ -18,7 +17,6 @@ comptime {
1817
1918test {
2019 _ = int;
21 _ = Rational;
2220 _ = Limb;
2321 _ = SignedLimb;
2422 _ = DoubleLimb;
lib/std/math/big/int.zig+182-46
......@@ -18,17 +18,28 @@ const Signedness = std.builtin.Signedness;
1818const native_endian = builtin.cpu.arch.endian();
1919
2020/// Returns the number of limbs needed to store `scalar`, which must be a
21/// primitive integer value.
21/// primitive integer or float value.
2222/// Note: A comptime-known upper bound of this value that may be used
2323/// instead if `scalar` is not already comptime-known is
2424/// `calcTwosCompLimbCount(@typeInfo(@TypeOf(scalar)).int.bits)`
2525pub fn calcLimbLen(scalar: anytype) usize {
26 if (scalar == 0) {
27 return 1;
26 switch (@typeInfo(@TypeOf(scalar))) {
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))),
2842 }
29
30 const w_value = @abs(scalar);
31 return @as(usize, @intCast(@divFloor(@as(Limb, @intCast(math.log2(w_value))), limb_bits) + 1));
3243}
3344
3445pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize {
......@@ -134,6 +145,22 @@ pub const TwosCompIntLimit = enum {
134145 max,
135146};
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
137164/// A arbitrary-precision big integer, with a fixed set of mutable limbs.
138165pub const Mutable = struct {
139166 /// Raw digits. These are:
......@@ -155,6 +182,20 @@ pub const Mutable = struct {
155182 };
156183 }
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
158199 /// Returns true if `a == 0`.
159200 pub fn eqlZero(self: Mutable) bool {
160201 return self.toConst().eqlZero();
......@@ -401,6 +442,65 @@ pub const Mutable = struct {
401442 }
402443 }
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
404504 /// r = a + scalar
405505 ///
406506 /// r and a may be aliases.
......@@ -2117,25 +2217,25 @@ pub const Const = struct {
21172217 /// Deprecated; use `toInt`.
21182218 pub const to = toInt;
21192219
2120 /// Convert self to integer type T.
2220 /// Convert `self` to `Int`.
21212221 ///
21222222 /// 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 {
2124 switch (@typeInfo(T)) {
2223 pub fn toInt(self: Const, comptime Int: type) ConvertError!Int {
2224 switch (@typeInfo(Int)) {
21252225 .int => |info| {
21262226 // Make sure -0 is handled correctly.
21272227 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
21312231 if (!self.fitsInTwosComp(info.signedness, info.bits)) {
21322232 return error.TargetTooSmall;
21332233 }
21342234
2135 var r: UT = 0;
2235 var r: Unsigned = 0;
21362236
2137 if (@sizeOf(UT) <= @sizeOf(Limb)) {
2138 r = @as(UT, @intCast(self.limbs[0]));
2237 if (@sizeOf(Unsigned) <= @sizeOf(Limb)) {
2238 r = @intCast(self.limbs[0]);
21392239 } else {
21402240 for (self.limbs[0..self.limbs.len], 0..) |_, ri| {
21412241 const limb = self.limbs[self.limbs.len - ri - 1];
......@@ -2145,40 +2245,76 @@ pub const Const = struct {
21452245 }
21462246
21472247 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;
21492249 } else {
21502250 if (self.positive) {
21512251 return @intCast(r);
21522252 } else {
2153 if (math.cast(T, r)) |ok| {
2253 if (math.cast(Int, r)) |ok| {
21542254 return -ok;
21552255 } else {
2156 return minInt(T);
2256 return minInt(Int);
21572257 }
21582258 }
21592259 }
21602260 },
2161 else => @compileError("expected int type, found '" ++ @typeName(T) ++ "'"),
2261 else => @compileError("expected int type, found '" ++ @typeName(Int) ++ "'"),
21622262 }
21632263 }
21642264
2165 /// Convert self to float type T.
2166 pub fn toFloat(self: Const, comptime T: type) T {
2167 if (self.limbs.len == 0) return 0;
2168
2169 const base = std.math.maxInt(std.math.big.Limb) + 1;
2170 var result: f128 = 0;
2171 var i: usize = self.limbs.len;
2172 while (i != 0) {
2173 i -= 1;
2174 const limb: f128 = @floatFromInt(self.limbs[i]);
2175 result = @mulAdd(f128, base, result, limb);
2176 }
2177 if (self.positive) {
2178 return @floatCast(result);
2179 } else {
2180 return @floatCast(-result);
2181 }
2265 /// Convert self to `Float`.
2266 pub fn toFloat(self: Const, comptime Float: type, round: Round) struct { Float, Exactness } {
2267 if (Float == comptime_float) return self.toFloat(f128, round);
2268 const normalized_abs: Const = .{
2269 .limbs = self.limbs[0..llnormalize(self.limbs)],
2270 .positive = true,
2271 };
2272 if (normalized_abs.eqlZero()) return .{ if (self.positive) 0.0 else -0.0, .exact };
2273
2274 const Repr = std.math.FloatRepr(Float);
2275 var mantissa_limbs: [calcNonZeroTwosCompLimbCount(1 + @bitSizeOf(Repr.Mantissa))]Limb = undefined;
2276 var mantissa: Mutable = .{
2277 .limbs = &mantissa_limbs,
2278 .positive = undefined,
2279 .len = undefined,
2280 };
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 };
21822318 }
21832319
21842320 /// To allow `std.fmt.format` to work with this type.
......@@ -2739,16 +2875,16 @@ pub const Managed = struct {
27392875 /// Deprecated; use `toInt`.
27402876 pub const to = toInt;
27412877
2742 /// Convert self to integer type T.
2878 /// Convert `self` to `Int`.
27432879 ///
27442880 /// 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 {
2746 return self.toConst().toInt(T);
2881 pub fn toInt(self: Managed, comptime Int: type) ConvertError!Int {
2882 return self.toConst().toInt(Int);
27472883 }
27482884
2749 /// Convert self to float type T.
2750 pub fn toFloat(self: Managed, comptime T: type) T {
2751 return self.toConst().toFloat(T);
2885 /// Convert `self` to `Float`.
2886 pub fn toFloat(self: Managed, comptime Float: type, round: Round) struct { Float, Exactness } {
2887 return self.toConst().toFloat(Float, round);
27522888 }
27532889
27542890 /// Set self from the string representation `value`.
......@@ -3807,7 +3943,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) usize {
38073943
38083944 // if the most significant limb becomes 0 after the shift
38093945 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
38123948 var i: usize = 0;
38133949 while (i < a.len - 1) : (i += 1) {
......@@ -4240,7 +4376,7 @@ test {
42404376
42414377const testing_allocator = std.testing.allocator;
42424378test "llshl shift by whole number of limb" {
4243 const padding = std.math.maxInt(Limb);
4379 const padding = maxInt(Limb);
42444380
42454381 var r: [10]Limb = @splat(padding);
42464382
......@@ -4390,8 +4526,8 @@ test "llshr to 0" {
43904526 try testOneShiftCase(.llshr, .{1, &.{0}, &.{1}});
43914527 try testOneShiftCase(.llshr, .{5, &.{0}, &.{1}});
43924528 try testOneShiftCase(.llshr, .{65, &.{0}, &.{0, 1}});
4393 try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, std.math.maxInt(Limb)}});
4394 try testOneShiftCase(.llshr, .{193, &.{0}, &.{std.math.maxInt(Limb), 1, std.math.maxInt(Limb)}});
4529 try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, maxInt(Limb)}});
4530 try testOneShiftCase(.llshr, .{193, &.{0}, &.{maxInt(Limb), 1, maxInt(Limb)}});
43954531 try testOneShiftCase(.llshr, .{193, &.{0}, &.{0xdeadbeef, 0xabcdefab, 0x1234}});
43964532 // zig fmt: on
43974533}
......@@ -4475,7 +4611,7 @@ fn testOneShiftCase(comptime function: enum { llshr, llshl }, case: Case) !void
44754611}
44764612
44774613fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case) !void {
4478 const padding = std.math.maxInt(Limb);
4614 const padding = maxInt(Limb);
44794615 var r: [20]Limb = @splat(padding);
44804616
44814617 const shift = case[0];
......@@ -4492,7 +4628,7 @@ fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case
44924628}
44934629
44944630fn 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);
44964632 var r: [60]Limb = @splat(padding);
44974633 const base = 20;
44984634
lib/std/math/big/int_test.zig+408
......@@ -17,6 +17,12 @@ const minInt = std.math.minInt;
1717// They will still run on larger than this and should pass, but the multi-limb code-paths
1818// 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
2026test "comptime_int set" {
2127 comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab;
2228 var a = try Managed.initSet(testing.allocator, s);
......@@ -85,6 +91,408 @@ test "to target too small error" {
8591 try testing.expectError(error.TargetTooSmall, a.toInt(u8));
8692}
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
88496test "normalize" {
89497 var a = try Managed.init(testing.allocator);
90498 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;
44const expect = std.testing.expect;
55const 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
7120/// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic.
8121inline fn mantissaOne(comptime T: type) comptime_int {
9122 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 {
593593 switch (node.get(self.zoir)) {
594594 .int_literal => |int| switch (int) {
595595 .small => |val| return @floatFromInt(val),
596 .big => |val| return val.toFloat(T),
596 .big => |val| return val.toFloat(T, .nearest_even)[0],
597597 },
598598 .float_literal => |val| return @floatCast(val),
599599 .pos_inf => return std.math.inf(T),
src/Air.zig+8
......@@ -683,6 +683,10 @@ pub const Inst = struct {
683683 int_from_float,
684684 /// Same as `int_from_float` with optimized float mode.
685685 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,
686690 /// Given an integer operand, return the float with the closest mathematical meaning.
687691 /// Uses the `ty_op` field.
688692 float_from_int,
......@@ -1612,6 +1616,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
16121616 .array_to_slice,
16131617 .int_from_float,
16141618 .int_from_float_optimized,
1619 .int_from_float_safe,
1620 .int_from_float_optimized_safe,
16151621 .float_from_int,
16161622 .splat,
16171623 .get_union_tag,
......@@ -1842,6 +1848,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
18421848 .sub_safe,
18431849 .mul_safe,
18441850 .intcast_safe,
1851 .int_from_float_safe,
1852 .int_from_float_optimized_safe,
18451853 => true,
18461854
18471855 .add,
src/Air/Legalize.zig+235-36
......@@ -1,7 +1,36 @@
11pt: Zcu.PerThread,
22air_instructions: std.MultiArrayList(Air.Inst),
33air_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
635pub const Feature = enum {
736 scalarize_add,
......@@ -83,6 +112,8 @@ pub const Feature = enum {
83112 scalarize_trunc,
84113 scalarize_int_from_float,
85114 scalarize_int_from_float_optimized,
115 scalarize_int_from_float_safe,
116 scalarize_int_from_float_optimized_safe,
86117 scalarize_float_from_int,
87118 scalarize_shuffle_one,
88119 scalarize_shuffle_two,
......@@ -97,6 +128,12 @@ pub const Feature = enum {
97128 /// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure.
98129 /// Not compatible with `scalarize_intcast_safe`.
99130 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,
100137 /// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure.
101138 /// Not compatible with `scalarize_add_safe`.
102139 expand_add_safe,
......@@ -196,10 +233,12 @@ pub const Feature = enum {
196233 .trunc => .scalarize_trunc,
197234 .int_from_float => .scalarize_int_from_float,
198235 .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,
199238 .float_from_int => .scalarize_float_from_int,
200239 .shuffle_one => .scalarize_shuffle_one,
201240 .shuffle_two => .scalarize_shuffle_two,
202 .select => .scalarize_selects,
241 .select => .scalarize_select,
203242 .mul_add => .scalarize_mul_add,
204243 };
205244 }
......@@ -210,13 +249,12 @@ pub const Features = std.enums.EnumSet(Feature);
210249pub const Error = std.mem.Allocator.Error;
211250
212251pub fn legalize(air: *Air, pt: Zcu.PerThread, features: *const Features) Error!void {
213 dev.check(.legalize);
214252 assert(!features.eql(comptime .initEmpty())); // backend asked to run legalize, but no features were enabled
215253 var l: Legalize = .{
216254 .pt = pt,
217255 .air_instructions = air.instructions.toMultiArrayList(),
218256 .air_extra = air.extra,
219 .features = features,
257 .features = .init(features),
220258 };
221259 defer air.* = l.getTmpAir();
222260 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 {
278316 .bit_and,
279317 .bit_or,
280318 .xor,
281 => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {
319 => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
282320 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
283321 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
284322 },
285 .add_safe => if (l.features.contains(.expand_add_safe)) {
286 assert(!l.features.contains(.scalarize_add_safe)); // it doesn't make sense to do both
323 .add_safe => if (l.features.has(.expand_add_safe)) {
324 assert(!l.features.has(.scalarize_add_safe)); // it doesn't make sense to do both
287325 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)) {
289327 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
290328 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
291329 },
292 .sub_safe => if (l.features.contains(.expand_sub_safe)) {
293 assert(!l.features.contains(.scalarize_sub_safe)); // it doesn't make sense to do both
330 .sub_safe => if (l.features.has(.expand_sub_safe)) {
331 assert(!l.features.has(.scalarize_sub_safe)); // it doesn't make sense to do both
294332 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)) {
296334 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
297335 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
298336 },
299 .mul_safe => if (l.features.contains(.expand_mul_safe)) {
300 assert(!l.features.contains(.scalarize_mul_safe)); // it doesn't make sense to do both
337 .mul_safe => if (l.features.has(.expand_mul_safe)) {
338 assert(!l.features.has(.scalarize_mul_safe)); // it doesn't make sense to do both
301339 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)) {
303341 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
304342 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
305343 },
......@@ -308,7 +346,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
308346 .sub_with_overflow,
309347 .mul_with_overflow,
310348 .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))) {
312350 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
313351 if (ty_pl.ty.toType().fieldType(0, zcu).isVector(zcu)) continue :inst l.replaceInst(inst, .block, try l.scalarizeOverflowBlockPayload(inst));
314352 },
......@@ -320,13 +358,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
320358 .shl,
321359 .shl_exact,
322360 .shl_sat,
323 => |air_tag| if (!l.features.intersectWith(comptime .initMany(&.{
361 => |air_tag| if (l.features.hasAny(&.{
324362 .unsplat_shift_rhs,
325363 .scalarize(air_tag),
326 })).eql(comptime .initEmpty())) {
364 })) {
327365 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
328366 if (l.typeOf(bin_op.rhs).isVector(zcu)) {
329 if (l.features.contains(.unsplat_shift_rhs)) {
367 if (l.features.has(.unsplat_shift_rhs)) {
330368 if (bin_op.rhs.toInterned()) |rhs_ip_index| switch (ip.indexToKey(rhs_ip_index)) {
331369 else => {},
332370 .aggregate => |aggregate| switch (aggregate.storage) {
......@@ -347,7 +385,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
347385 }
348386 }
349387 }
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);
351389 }
352390 },
353391 inline .not,
......@@ -364,11 +402,11 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
364402 .int_from_float,
365403 .int_from_float_optimized,
366404 .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))) {
368406 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
369407 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
370408 },
371 .bitcast => if (l.features.contains(.scalarize_bitcast)) {
409 .bitcast => if (l.features.has(.scalarize_bitcast)) {
372410 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
373411
374412 const to_ty = ty_op.ty.toType();
......@@ -404,10 +442,24 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
404442 };
405443 if (!from_ty_legal) continue :inst l.replaceInst(inst, .block, try l.scalarizeBitcastOperandBlockPayload(inst));
406444 },
407 .intcast_safe => if (l.features.contains(.expand_intcast_safe)) {
408 assert(!l.features.contains(.scalarize_intcast_safe)); // it doesn't make sense to do both
445 .intcast_safe => if (l.features.has(.expand_intcast_safe)) {
446 assert(!l.features.has(.scalarize_intcast_safe)); // it doesn't make sense to do both
409447 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)) {
411463 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
412464 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
413465 },
......@@ -442,7 +494,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
442494 .trunc_float,
443495 .neg,
444496 .neg_optimized,
445 => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {
497 => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
446498 const un_op = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op;
447499 if (l.typeOf(un_op).isVector(zcu)) continue :inst try l.scalarize(inst, .un_op);
448500 },
......@@ -459,7 +511,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
459511 .cmp_neq,
460512 .cmp_neq_optimized,
461513 => {},
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))) {
463515 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
464516 if (ty_pl.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .cmp_vector);
465517 },
......@@ -513,13 +565,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
513565 .bool_and,
514566 .bool_or,
515567 => {},
516 .load => if (l.features.contains(.expand_packed_load)) {
568 .load => if (l.features.has(.expand_packed_load)) {
517569 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
518570 const ptr_info = l.typeOf(ty_op.operand).ptrInfo(zcu);
519571 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));
520572 },
521573 .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)) {
523575 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
524576 const ptr_info = l.typeOf(bin_op.lhs).ptrInfo(zcu);
525577 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 {
542594 .struct_field_ptr_index_2,
543595 .struct_field_ptr_index_3,
544596 => {},
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)) {
546598 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
547599 const extra = l.extraData(Air.StructField, ty_pl.payload).data;
548600 switch (l.typeOf(extra.struct_operand).containerLayout(zcu)) {
......@@ -564,7 +616,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
564616 .ptr_elem_ptr,
565617 .array_to_slice,
566618 => {},
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)) {
568620 const reduce = l.air_instructions.items(.data)[@intFromEnum(inst)].reduce;
569621 const vector_ty = l.typeOf(reduce.operand);
570622 switch (vector_ty.vectorLen(zcu)) {
......@@ -577,9 +629,9 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
577629 }
578630 },
579631 .splat => {},
580 .shuffle_one => if (l.features.contains(.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),
582 .select => if (l.features.contains(.scalarize_select)) continue :inst try l.scalarize(inst, .select),
632 .shuffle_one => if (l.features.has(.scalarize_shuffle_one)) continue :inst try l.scalarize(inst, .shuffle_one),
633 .shuffle_two => if (l.features.has(.scalarize_shuffle_two)) continue :inst try l.scalarize(inst, .shuffle_two),
634 .select => if (l.features.has(.scalarize_select)) continue :inst try l.scalarize(inst, .select),
583635 .memset,
584636 .memset_safe,
585637 .memcpy,
......@@ -597,7 +649,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
597649 .error_name,
598650 .error_set_has_value,
599651 => {},
600 .aggregate_init => if (l.features.contains(.expand_packed_aggregate_init)) {
652 .aggregate_init => if (l.features.has(.expand_packed_aggregate_init)) {
601653 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
602654 const agg_ty = ty_pl.ty.toType();
603655 switch (agg_ty.zigTypeTag(zcu)) {
......@@ -609,7 +661,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
609661 }
610662 },
611663 .union_init, .prefetch => {},
612 .mul_add => if (l.features.contains(.scalarize_mul_add)) {
664 .mul_add => if (l.features.has(.scalarize_mul_add)) {
613665 const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op;
614666 if (l.typeOf(pl_op.operand).isVector(zcu)) continue :inst try l.scalarize(inst, .pl_op_bin);
615667 },
......@@ -636,6 +688,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
636688}
637689
638690const 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.
639692inline fn scalarize(l: *Legalize, orig_inst: Air.Inst.Index, comptime form: ScalarizeForm) Error!Air.Inst.Tag {
640693 return l.replaceInst(orig_inst, .block, try l.scalarizeBlockPayload(orig_inst, form));
641694}
......@@ -1972,6 +2025,115 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
19722025 .payload = try l.addBlockBody(main_block.body()),
19732026 } };
19742027}
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}
19752137fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data {
19762138 const pt = l.pt;
19772139 const zcu = pt.zcu;
......@@ -2349,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro
23492511 } };
23502512}
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
23522550const Block = struct {
23532551 instructions: []Air.Inst.Index,
23542552 len: usize,
......@@ -2691,7 +2889,7 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 {
26912889}
26922890
26932891/// 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.
26952893inline fn replaceInst(l: *Legalize, inst: Air.Inst.Index, comptime tag: Air.Inst.Tag, data: Air.Inst.Data) Air.Inst.Tag {
26962894 const orig_ty = if (std.debug.runtime_safety) l.typeOfIndex(inst) else {};
26972895 l.air_instructions.set(@intFromEnum(inst), .{ .tag = tag, .data = data });
......@@ -2706,4 +2904,5 @@ const InternPool = @import("../InternPool.zig");
27062904const Legalize = @This();
27072905const std = @import("std");
27082906const Type = @import("../Type.zig");
2907const Value = @import("../Value.zig");
27092908const Zcu = @import("../Zcu.zig");
src/Air/Liveness.zig+4
......@@ -374,6 +374,8 @@ pub fn categorizeOperand(
374374 .array_to_slice,
375375 .int_from_float,
376376 .int_from_float_optimized,
377 .int_from_float_safe,
378 .int_from_float_optimized_safe,
377379 .float_from_int,
378380 .get_union_tag,
379381 .clz,
......@@ -1015,6 +1017,8 @@ fn analyzeInst(
10151017 .array_to_slice,
10161018 .int_from_float,
10171019 .int_from_float_optimized,
1020 .int_from_float_safe,
1021 .int_from_float_optimized_safe,
10181022 .float_from_int,
10191023 .get_union_tag,
10201024 .clz,
src/Air/Liveness/Verify.zig+2
......@@ -107,6 +107,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
107107 .array_to_slice,
108108 .int_from_float,
109109 .int_from_float_optimized,
110 .int_from_float_safe,
111 .int_from_float_optimized_safe,
110112 .float_from_int,
111113 .get_union_tag,
112114 .clz,
src/Air/print.zig+2
......@@ -250,6 +250,8 @@ const Writer = struct {
250250 .splat,
251251 .int_from_float,
252252 .int_from_float_optimized,
253 .int_from_float_safe,
254 .int_from_float_optimized_safe,
253255 .get_union_tag,
254256 .clz,
255257 .ctz,
src/Air/types_resolved.zig+2
......@@ -130,6 +130,8 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
130130 .array_to_slice,
131131 .int_from_float,
132132 .int_from_float_optimized,
133 .int_from_float_safe,
134 .int_from_float_optimized_safe,
133135 .float_from_int,
134136 .splat,
135137 .error_set_has_value,
src/Sema.zig+74-106
......@@ -8934,9 +8934,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
89348934
89358935 try sema.requireRuntimeBlock(block, src, operand_src);
89368936 if (block.wantSafety()) {
8937 if (zcu.backendSupportsFeature(.panic_fn)) {
8938 _ = try sema.preparePanicId(src, .invalid_enum_value);
8939 }
8937 try sema.preparePanicId(src, .invalid_enum_value);
89408938 return block.addTyOp(.intcast_safe, dest_ty, operand);
89418939 }
89428940 return block.addTyOp(.intcast, dest_ty, operand);
......@@ -10340,9 +10338,7 @@ fn intCast(
1034010338
1034110339 try sema.requireRuntimeBlock(block, src, operand_src);
1034210340 if (block.wantSafety()) {
10343 if (zcu.backendSupportsFeature(.panic_fn)) {
10344 _ = try sema.preparePanicId(src, .integer_out_of_bounds);
10345 }
10341 try sema.preparePanicId(src, .integer_out_of_bounds);
1034610342 return block.addTyOp(.intcast_safe, dest_ty, operand);
1034710343 }
1034810344 return block.addTyOp(.intcast, dest_ty, operand);
......@@ -16395,9 +16391,7 @@ fn analyzeArithmetic(
1639516391 }
1639616392
1639716393 if (block.wantSafety() and want_safety and scalar_tag == .int) {
16398 if (air_tag != air_tag_safe and zcu.backendSupportsFeature(.panic_fn)) {
16399 _ = try sema.preparePanicId(src, .integer_overflow);
16400 }
16394 if (air_tag != air_tag_safe) try sema.preparePanicId(src, .integer_overflow);
1640116395 return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
1640216396 }
1640316397 return block.addBinOp(air_tag, casted_lhs, casted_rhs);
......@@ -22178,44 +22172,32 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
2217822172
2217922173 try sema.requireRuntimeBlock(block, src, operand_src);
2218022174 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 }
2218822175 if (block.wantSafety()) {
22189 const len = dest_ty.vectorLen(zcu);
22190 for (0..len) |i| {
22191 const idx_ref = try pt.intRef(.usize, i);
22192 const elem_ref = try block.addBinOp(.array_elem_val, operand, idx_ref);
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()));
22194 try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
22195 }
22196 }
22176 // Emit an explicit safety check. We can do this one like `abs(x) < 1`.
22177 const abs_ref = try block.addTyOp(.abs, operand_ty, operand);
22178 const max_abs_ref = if (is_vector) try block.addReduce(abs_ref, .Max) else abs_ref;
22179 const one_ref = Air.internedToRef((try pt.floatValue(operand_scalar_ty, 1.0)).toIntern());
22180 const ok_ref = try block.addBinOp(.cmp_lt, max_abs_ref, one_ref);
22181 try sema.addSafetyCheck(block, src, ok_ref, .integer_part_out_of_bounds);
22182 }
22183 const scalar_val = try pt.intValue(dest_scalar_ty, 0);
22184 if (!is_vector) return Air.internedToRef(scalar_val.toIntern());
2219722185 return Air.internedToRef(try pt.intern(.{ .aggregate = .{
2219822186 .ty = dest_ty.toIntern(),
22199 .storage = .{ .repeated_elem = (try pt.intValue(dest_scalar_ty, 0)).toIntern() },
22187 .storage = .{ .repeated_elem = scalar_val.toIntern() },
2220022188 } }));
2220122189 }
22202 const result = try block.addTyOp(if (block.float_mode == .optimized) .int_from_float_optimized else .int_from_float, dest_ty, operand);
2220322190 if (block.wantSafety()) {
22204 const back = try block.addTyOp(.float_from_int, operand_ty, result);
22205 const diff = try block.addBinOp(if (block.float_mode == .optimized) .sub_optimized else .sub, operand, back);
22206 const ok = if (is_vector) ok: {
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);
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);
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);
22191 try sema.preparePanicId(src, .integer_part_out_of_bounds);
22192 return block.addTyOp(switch (block.float_mode) {
22193 .optimized => .int_from_float_optimized_safe,
22194 .strict => .int_from_float_safe,
22195 }, dest_ty, operand);
2221722196 }
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);
2221922201}
2222022202
2222122203fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
......@@ -26871,7 +26853,15 @@ fn explainWhyTypeIsNotPacked(
2687126853/// Backends depend on panic decls being available when lowering safety-checked
2687226854/// instructions. This function ensures the panic function will be available to
2687326855/// 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 {
2687526865 const zcu = sema.pt.zcu;
2687626866 try sema.ensureMemoizedStateResolved(src, .panic);
2687726867 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
2712027110 if (!sema.pt.zcu.backendSupportsFeature(.panic_fn)) {
2712127111 _ = try block.addNoOp(.trap);
2712227112 } else {
27123 const panic_fn = try sema.preparePanicId(src, panic_id);
27113 const panic_fn = try sema.getPanicIdFunc(src, panic_id);
2712427114 try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{}, .@"safety check");
2712527115 }
2712627116}
......@@ -32843,24 +32833,21 @@ fn cmpNumeric(
3284332833 }
3284432834 }
3284532835 if (lhs_is_float) {
32846 if (lhs_val.floatHasFraction(zcu)) {
32847 switch (op) {
32836 const float = lhs_val.toFloat(f128, zcu);
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) {
3284832844 .eq => return .bool_false,
3284932845 .neq => return .bool_true,
3285032846 else => {},
32851 }
32852 }
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 }
32847 },
32848 .exact => {},
3286232849 }
32863 lhs_bits = bigint.toConst().bitCountTwosComp();
32850 lhs_bits = big_int.toConst().bitCountTwosComp();
3286432851 } else {
3286532852 lhs_bits = lhs_val.intBitCountTwosComp(zcu);
3286632853 }
......@@ -32890,24 +32877,21 @@ fn cmpNumeric(
3289032877 }
3289132878 }
3289232879 if (rhs_is_float) {
32893 if (rhs_val.floatHasFraction(zcu)) {
32894 switch (op) {
32880 const float = rhs_val.toFloat(f128, zcu);
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) {
3289532888 .eq => return .bool_false,
3289632889 .neq => return .bool_true,
3289732890 else => {},
32898 }
32899 }
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 }
32891 },
32892 .exact => {},
3290932893 }
32910 rhs_bits = bigint.toConst().bitCountTwosComp();
32894 rhs_bits = big_int.toConst().bitCountTwosComp();
3291132895 } else {
3291232896 rhs_bits = rhs_val.intBitCountTwosComp(zcu);
3291332897 }
......@@ -36955,31 +36939,6 @@ fn intFromFloat(
3695536939 return sema.intFromFloatScalar(block, src, val, int_ty, mode);
3695636940}
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
3698336942fn intFromFloatScalar(
3698436943 sema: *Sema,
3698536944 block: *Block,
......@@ -36993,13 +36952,6 @@ fn intFromFloatScalar(
3699336952
3699436953 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
3700336955 const float = val.toFloat(f128, zcu);
3700436956 if (std.math.isNan(float)) {
3700536957 return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{
......@@ -37012,12 +36964,28 @@ fn intFromFloatScalar(
3701236964 });
3701336965 }
3701436966
37015 var big_int = try float128IntPartToBigInt(sema.arena, float);
37016 defer big_int.deinit();
37017
36967 var big_int: std.math.big.int.Mutable = .{
36968 .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
36969 .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 }
3701836984 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)) {
3702136989 return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{
3702236990 val.fmtValueSema(pt, sema), int_ty.fmt(pt),
3702336991 });
src/Sema/LowerZon.zig+12-19
......@@ -509,30 +509,23 @@ fn lowerInt(
509509 },
510510 },
511511 .float_literal => |val| {
512 // Check for fractional components
513 if (@rem(val, 1) != 0) {
514 return self.fail(
512 var big_int: std.math.big.int.Mutable = .{
513 .limbs = try self.sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(val)),
514 .len = undefined,
515 .positive = undefined,
516 };
517 switch (big_int.setFloat(val, .trunc)) {
518 .inexact => return self.fail(
515519 node,
516520 "fractional component prevents float value '{}' from coercion to type '{}'",
517521 .{ val, res_ty.fmt(self.sema.pt) },
518 );
522 ),
523 .exact => {},
519524 }
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
533526 // Check that the result is in range of the result type
534527 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)) {
536529 return self.fail(
537530 node,
538531 "type '{}' cannot represent integer value '{}'",
......@@ -543,7 +536,7 @@ fn lowerInt(
543536 return self.sema.pt.intern(.{
544537 .int = .{
545538 .ty = res_ty.toIntern(),
546 .storage = .{ .big_int = rational.p.toConst() },
539 .storage = .{ .big_int = big_int.toConst() },
547540 },
548541 });
549542 },
......@@ -584,7 +577,7 @@ fn lowerFloat(
584577 const value = switch (node.get(self.file.zoir.?)) {
585578 .int_literal => |int| switch (int) {
586579 .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]),
588581 },
589582 .float_literal => |val| try self.sema.pt.floatValue(res_ty, val),
590583 .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(
898898pub fn toFloat(val: Value, comptime T: type, zcu: *const Zcu) T {
899899 return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
900900 .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],
902902 inline .u64, .i64 => |x| {
903903 if (T == f80) {
904904 @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 {
997997 } }));
998998}
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
10101000pub fn orderAgainstZero(lhs: Value, zcu: *Zcu) std.math.Order {
10111001 return orderAgainstZeroInner(lhs, .normal, zcu, {}) catch unreachable;
10121002}
......@@ -1557,17 +1547,13 @@ pub fn floatFromIntAdvanced(
15571547}
15581548
15591549pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptime strat: ResolveStrat) !Value {
1560 const zcu = pt.zcu;
1561 return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
1550 return switch (pt.zcu.intern_pool.indexToKey(val.toIntern())) {
15621551 .undef => try pt.undefValue(float_ty),
15631552 .int => |int| switch (int.storage) {
1564 .big_int => |big_int| {
1565 const float = big_int.toFloat(f128);
1566 return pt.floatValue(float_ty, float);
1567 },
1553 .big_int => |big_int| pt.floatValue(float_ty, big_int.toFloat(f128, .nearest_even)[0]),
15681554 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),
1570 .lazy_size => |ty| return floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, 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),
1556 .lazy_size => |ty| floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, float_ty, pt),
15711557 },
15721558 else => unreachable,
15731559 };
src/arch/aarch64/CodeGen.zig+2
......@@ -861,6 +861,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
861861 .sub_safe,
862862 .mul_safe,
863863 .intcast_safe,
864 .int_from_float_safe,
865 .int_from_float_optimized_safe,
864866 => return self.fail("TODO implement safety_checked_instructions", .{}),
865867
866868 .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 {
850850 .sub_safe,
851851 .mul_safe,
852852 .intcast_safe,
853 .int_from_float_safe,
854 .int_from_float_optimized_safe,
853855 => return self.fail("TODO implement safety_checked_instructions", .{}),
854856
855857 .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};
5454pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
5555 return comptime &.initMany(&.{
5656 .expand_intcast_safe,
57 .expand_int_from_float_safe,
58 .expand_int_from_float_optimized_safe,
5759 .expand_add_safe,
5860 .expand_sub_safe,
5961 .expand_mul_safe,
......@@ -1474,6 +1476,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
14741476 .sub_safe,
14751477 .mul_safe,
14761478 .intcast_safe,
1479 .int_from_float_safe,
1480 .int_from_float_optimized_safe,
14771481 => return func.fail("TODO implement safety_checked_instructions", .{}),
14781482
14791483 .cmp_lt,
src/arch/sparc64/CodeGen.zig+2
......@@ -696,6 +696,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
696696 .sub_safe,
697697 .mul_safe,
698698 .intcast_safe,
699 .int_from_float_safe,
700 .int_from_float_optimized_safe,
699701 => @panic("TODO implement safety_checked_instructions"),
700702
701703 .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;
3131pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
3232 return comptime &.initMany(&.{
3333 .expand_intcast_safe,
34 .expand_int_from_float_safe,
35 .expand_int_from_float_optimized_safe,
3436 .expand_add_safe,
3537 .expand_sub_safe,
3638 .expand_mul_safe,
......@@ -2020,6 +2022,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
20202022 .sub_safe,
20212023 .mul_safe,
20222024 .intcast_safe,
2025 .int_from_float_safe,
2026 .int_from_float_optimized_safe,
20232027 => return cg.fail("TODO implement safety_checked_instructions", .{}),
20242028
20252029 .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
102102 .reduce_one_elem_to_bitcast = true,
103103
104104 .expand_intcast_safe = true,
105 .expand_int_from_float_safe = true,
106 .expand_int_from_float_optimized_safe = true,
105107 .expand_add_safe = true,
106108 .expand_sub_safe = true,
107109 .expand_mul_safe = true,
......@@ -107763,6 +107765,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
107763107765 };
107764107766 try res[0].finish(inst, &.{ty_op.operand}, &ops, cg);
107765107767 },
107768 .int_from_float_safe => unreachable,
107769 .int_from_float_optimized_safe => unreachable,
107766107770 .float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else {
107767107771 const ty_op = air_datas[@intFromEnum(inst)].ty_op;
107768107772 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;
2323const BigInt = std.math.big.int;
2424
2525pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
26 return if (dev.env.supports(.legalize)) comptime &.initMany(&.{
27 .expand_intcast_safe,
28 .expand_add_safe,
29 .expand_sub_safe,
30 .expand_mul_safe,
31 }) else null; // we don't currently ask zig1 to use safe optimization modes
26 return comptime switch (dev.env.supports(.legalize)) {
27 inline false, true => |supports_legalize| &.init(.{
28 // we don't currently ask zig1 to use safe optimization modes
29 .expand_intcast_safe = supports_legalize,
30 .expand_int_from_float_safe = supports_legalize,
31 .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 };
3241}
3342
3443/// 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,
35713580 .sub_safe,
35723581 .mul_safe,
35733582 .intcast_safe,
3583 .int_from_float_safe,
3584 .int_from_float_optimized_safe,
35743585 => return f.fail("TODO implement safety_checked_instructions", .{}),
35753586
35763587 .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;
3737const Error = error{ OutOfMemory, CodegenFail };
3838
3939pub 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 });
4144}
4245
4346fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 {
......@@ -4987,6 +4990,8 @@ pub const FuncGen = struct {
49874990
49884991 .int_from_float => try self.airIntFromFloat(inst, .normal),
49894992 .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
49914996 .array_to_slice => try self.airArrayToSlice(inst),
49924997 .float_from_int => try self.airFloatFromInt(inst),
src/codegen/spirv.zig+2
......@@ -31,6 +31,8 @@ const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
3131pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
3232 return comptime &.initMany(&.{
3333 .expand_intcast_safe,
34 .expand_int_from_float_safe,
35 .expand_int_from_float_optimized_safe,
3436 .expand_add_safe,
3537 .expand_sub_safe,
3638 .expand_mul_safe,
stage1/zig.h+9
......@@ -272,6 +272,15 @@
272272#define zig_linksection_fn zig_linksection
273273#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
275284#if zig_has_builtin(unreachable) || defined(zig_gcc) || defined(zig_tinyc)
276285#define zig_unreachable() __builtin_unreachable()
277286#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" {
102102test "@floatFromInt" {
103103 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
104104 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
105 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
105106 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
106107
107108 const S = struct {
......@@ -2737,3 +2738,66 @@ test "peer type resolution: slice of sentinel-terminated array" {
27372738 try expect(result[0][0] == 10);
27382739 try expect(result[0][1] == 20);
27392740}
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