| author | |
| committer | |
| log | 561fdd0ed3d93a373f126f4df01caf813ad32fec |
| tree | 285a0c41e4951ff6eb503b65dff92b6020538355 |
| parent | 080ee25ecf1991d85038716ca6199ae8dc31c8f5 |
| parent | e498d8da3c7a957fe3754db8f3517d71f7fc650e |
| signature |
Absorb std.math.big.rational logic into std.math.big.int; fix `@intFromFloat` safety check39 files changed, 1341 insertions(+), 1082 deletions(-)
lib/compiler/aro/aro/Value.zig+16-26| ... | @@ -148,35 +148,25 @@ pub fn floatToInt(v: *Value, dest_ty: Type, comp: *Compilation) !FloatToIntChang | ... | @@ -148,35 +148,25 @@ pub fn floatToInt(v: *Value, dest_ty: Type, comp: *Compilation) !FloatToIntChang |
| 148 | return .out_of_range; | 148 | return .out_of_range; |
| 149 | } | 149 | } |
| 150 | 150 | ||
| 151 | const had_fraction = @rem(float_val, 1) != 0; | ||
| 152 | const is_negative = std.math.signbit(float_val); | ||
| 153 | const floored = @floor(@abs(float_val)); | ||
| 154 | |||
| 155 | var rational = try std.math.big.Rational.init(comp.gpa); | ||
| 156 | defer rational.deinit(); | ||
| 157 | rational.setFloat(f128, floored) catch |err| switch (err) { | ||
| 158 | error.NonFiniteFloat => { | ||
| 159 | v.* = .{}; | ||
| 160 | return .overflow; | ||
| 161 | }, | ||
| 162 | error.OutOfMemory => return error.OutOfMemory, | ||
| 163 | }; | ||
| 164 | |||
| 165 | // The float is reduced in rational.setFloat, so we assert that denominator is equal to one | ||
| 166 | const big_one = BigIntConst{ .limbs = &.{1}, .positive = true }; | ||
| 167 | assert(rational.q.toConst().eqlAbs(big_one)); | ||
| 168 | |||
| 169 | if (is_negative) { | ||
| 170 | rational.negate(); | ||
| 171 | } | ||
| 172 | |||
| 173 | const signedness = dest_ty.signedness(comp); | 151 | const signedness = dest_ty.signedness(comp); |
| 174 | const bits: usize = @intCast(dest_ty.bitSizeof(comp).?); | 152 | const bits: usize = @intCast(dest_ty.bitSizeof(comp).?); |
| 175 | 153 | ||
| 176 | // rational.p.truncate(rational.p.toConst(), signedness: Signedness, bit_count: usize) | 154 | var big_int: std.math.big.int.Mutable = .{ |
| 177 | const fits = rational.p.fitsInTwosComp(signedness, bits); | 155 | .limbs = try comp.gpa.alloc(std.math.big.Limb, @max( |
| 178 | v.* = try intern(comp, .{ .int = .{ .big_int = rational.p.toConst() } }); | 156 | std.math.big.int.calcLimbLen(float_val), |
| 179 | try rational.p.truncate(&rational.p, signedness, bits); | 157 | std.math.big.int.calcTwosCompLimbCount(bits), |
| 158 | )), | ||
| 159 | .len = undefined, | ||
| 160 | .positive = undefined, | ||
| 161 | }; | ||
| 162 | const had_fraction = switch (big_int.setFloat(float_val, .trunc)) { | ||
| 163 | .inexact => true, | ||
| 164 | .exact => false, | ||
| 165 | }; | ||
| 166 | |||
| 167 | const fits = big_int.toConst().fitsInTwosComp(signedness, bits); | ||
| 168 | v.* = try intern(comp, .{ .int = .{ .big_int = big_int.toConst() } }); | ||
| 169 | big_int.truncate(big_int.toConst(), signedness, bits); | ||
| 180 | 170 | ||
| 181 | if (!was_zero and v.isZero(comp)) return .nonzero_to_zero; | 171 | if (!was_zero and v.isZero(comp)) return .nonzero_to_zero; |
| 182 | if (!fits) return .out_of_range; | 172 | if (!fits) return .out_of_range; |
lib/std/math.zig+1| ... | @@ -45,6 +45,7 @@ pub const rad_per_deg = 0.017453292519943295769236907684886127134428718885417254 | ... | @@ -45,6 +45,7 @@ pub const rad_per_deg = 0.017453292519943295769236907684886127134428718885417254 |
| 45 | /// 180.0/pi | 45 | /// 180.0/pi |
| 46 | pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861; | 46 | pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861; |
| 47 | 47 | ||
| 48 | pub const FloatRepr = float.FloatRepr; | ||
| 48 | pub const floatExponentBits = float.floatExponentBits; | 49 | pub const floatExponentBits = float.floatExponentBits; |
| 49 | pub const floatMantissaBits = float.floatMantissaBits; | 50 | pub const floatMantissaBits = float.floatMantissaBits; |
| 50 | pub const floatFractionalBits = float.floatFractionalBits; | 51 | pub const floatFractionalBits = float.floatFractionalBits; |
lib/std/math/big.zig-2| ... | @@ -1,7 +1,6 @@ | ... | @@ -1,7 +1,6 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| 2 | const assert = std.debug.assert; | 2 | const assert = std.debug.assert; |
| 3 | 3 | ||
| 4 | pub const Rational = @import("big/rational.zig").Rational; | ||
| 5 | pub const int = @import("big/int.zig"); | 4 | pub const int = @import("big/int.zig"); |
| 6 | pub const Limb = usize; | 5 | pub const Limb = usize; |
| 7 | const limb_info = @typeInfo(Limb).int; | 6 | const limb_info = @typeInfo(Limb).int; |
| ... | @@ -18,7 +17,6 @@ comptime { | ... | @@ -18,7 +17,6 @@ comptime { |
| 18 | 17 | ||
| 19 | test { | 18 | test { |
| 20 | _ = int; | 19 | _ = int; |
| 21 | _ = Rational; | ||
| 22 | _ = Limb; | 20 | _ = Limb; |
| 23 | _ = SignedLimb; | 21 | _ = SignedLimb; |
| 24 | _ = DoubleLimb; | 22 | _ = DoubleLimb; |
lib/std/math/big/int.zig+182-46| ... | @@ -18,17 +18,28 @@ const Signedness = std.builtin.Signedness; | ... | @@ -18,17 +18,28 @@ const Signedness = std.builtin.Signedness; |
| 18 | const native_endian = builtin.cpu.arch.endian(); | 18 | const native_endian = builtin.cpu.arch.endian(); |
| 19 | 19 | ||
| 20 | /// Returns the number of limbs needed to store `scalar`, which must be a | 20 | /// Returns the number of limbs needed to store `scalar`, which must be a |
| 21 | /// primitive integer value. | 21 | /// primitive integer or float value. |
| 22 | /// Note: A comptime-known upper bound of this value that may be used | 22 | /// Note: A comptime-known upper bound of this value that may be used |
| 23 | /// instead if `scalar` is not already comptime-known is | 23 | /// instead if `scalar` is not already comptime-known is |
| 24 | /// `calcTwosCompLimbCount(@typeInfo(@TypeOf(scalar)).int.bits)` | 24 | /// `calcTwosCompLimbCount(@typeInfo(@TypeOf(scalar)).int.bits)` |
| 25 | pub fn calcLimbLen(scalar: anytype) usize { | 25 | pub fn calcLimbLen(scalar: anytype) usize { |
| 26 | if (scalar == 0) { | 26 | switch (@typeInfo(@TypeOf(scalar))) { |
| 27 | return 1; | 27 | .int, .comptime_int => { |
| 28 | if (scalar == 0) return 1; | ||
| 29 | const w_value = @abs(scalar); | ||
| 30 | return @as(usize, @intCast(@divFloor(@as(Limb, @intCast(math.log2(w_value))), limb_bits) + 1)); | ||
| 31 | }, | ||
| 32 | .float => { | ||
| 33 | const repr: std.math.FloatRepr(@TypeOf(scalar)) = @bitCast(scalar); | ||
| 34 | return switch (repr.exponent) { | ||
| 35 | .denormal => 1, | ||
| 36 | else => return calcNonZeroTwosCompLimbCount(@as(usize, 2) + @max(repr.exponent.unbias(), 0)), | ||
| 37 | .infinite => 0, | ||
| 38 | }; | ||
| 39 | }, | ||
| 40 | .comptime_float => return calcLimbLen(@as(f128, scalar)), | ||
| 41 | else => @compileError("expected float or int, got " ++ @typeName(@TypeOf(scalar))), | ||
| 28 | } | 42 | } |
| 29 | |||
| 30 | const w_value = @abs(scalar); | ||
| 31 | return @as(usize, @intCast(@divFloor(@as(Limb, @intCast(math.log2(w_value))), limb_bits) + 1)); | ||
| 32 | } | 43 | } |
| 33 | 44 | ||
| 34 | pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize { | 45 | pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize { |
| ... | @@ -134,6 +145,22 @@ pub const TwosCompIntLimit = enum { | ... | @@ -134,6 +145,22 @@ pub const TwosCompIntLimit = enum { |
| 134 | max, | 145 | max, |
| 135 | }; | 146 | }; |
| 136 | 147 | ||
| 148 | pub 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 | |||
| 162 | pub const Exactness = enum { inexact, exact }; | ||
| 163 | |||
| 137 | /// A arbitrary-precision big integer, with a fixed set of mutable limbs. | 164 | /// A arbitrary-precision big integer, with a fixed set of mutable limbs. |
| 138 | pub const Mutable = struct { | 165 | pub const Mutable = struct { |
| 139 | /// Raw digits. These are: | 166 | /// Raw digits. These are: |
| ... | @@ -155,6 +182,20 @@ pub const Mutable = struct { | ... | @@ -155,6 +182,20 @@ pub const Mutable = struct { |
| 155 | }; | 182 | }; |
| 156 | } | 183 | } |
| 157 | 184 | ||
| 185 | pub const ConvertError = Const.ConvertError; | ||
| 186 | |||
| 187 | /// Convert `self` to `Int`. | ||
| 188 | /// | ||
| 189 | /// Returns an error if self cannot be narrowed into the requested type without truncation. | ||
| 190 | pub fn toInt(self: Mutable, comptime Int: type) ConvertError!Int { | ||
| 191 | return self.toConst().toInt(Int); | ||
| 192 | } | ||
| 193 | |||
| 194 | /// Convert `self` to `Float`. | ||
| 195 | pub fn toFloat(self: Mutable, comptime Float: type, round: Round) struct { Float, Exactness } { | ||
| 196 | return self.toConst().toFloat(Float, round); | ||
| 197 | } | ||
| 198 | |||
| 158 | /// Returns true if `a == 0`. | 199 | /// Returns true if `a == 0`. |
| 159 | pub fn eqlZero(self: Mutable) bool { | 200 | pub fn eqlZero(self: Mutable) bool { |
| 160 | return self.toConst().eqlZero(); | 201 | return self.toConst().eqlZero(); |
| ... | @@ -401,6 +442,65 @@ pub const Mutable = struct { | ... | @@ -401,6 +442,65 @@ pub const Mutable = struct { |
| 401 | } | 442 | } |
| 402 | } | 443 | } |
| 403 | 444 | ||
| 445 | /// Sets the Mutable to a float value rounded according to `round`. | ||
| 446 | /// Returns whether the conversion was exact (`round` had no effect on the result). | ||
| 447 | pub fn setFloat(self: *Mutable, value: anytype, round: Round) Exactness { | ||
| 448 | const Float = @TypeOf(value); | ||
| 449 | if (Float == comptime_float) return self.setFloat(@as(f128, value), round); | ||
| 450 | const abs_value = @abs(value); | ||
| 451 | if (abs_value < 1.0) { | ||
| 452 | if (abs_value == 0.0) { | ||
| 453 | self.set(0); | ||
| 454 | return .exact; | ||
| 455 | } | ||
| 456 | self.set(@as(i2, round: switch (round) { | ||
| 457 | .nearest_even => if (abs_value <= 0.5) 0 else continue :round .away, | ||
| 458 | .away => if (value < 0.0) -1 else 1, | ||
| 459 | .trunc => 0, | ||
| 460 | .floor => -@as(i2, @intFromBool(value < 0.0)), | ||
| 461 | .ceil => @intFromBool(value > 0.0), | ||
| 462 | })); | ||
| 463 | return .inexact; | ||
| 464 | } | ||
| 465 | const Repr = std.math.FloatRepr(Float); | ||
| 466 | const repr: Repr = @bitCast(value); | ||
| 467 | const exponent = repr.exponent.unbias(); | ||
| 468 | assert(exponent >= 0); | ||
| 469 | const int_bit: Repr.Mantissa = 1 << (@bitSizeOf(Repr.Mantissa) - 1); | ||
| 470 | const mantissa = int_bit | repr.mantissa; | ||
| 471 | if (exponent >= @bitSizeOf(Repr.Normalized.Fraction)) { | ||
| 472 | self.set(mantissa); | ||
| 473 | self.shiftLeft(self.toConst(), @intCast(exponent - @bitSizeOf(Repr.Normalized.Fraction))); | ||
| 474 | self.positive = repr.sign == .positive; | ||
| 475 | return .exact; | ||
| 476 | } | ||
| 477 | self.set(mantissa >> @intCast(@bitSizeOf(Repr.Normalized.Fraction) - exponent)); | ||
| 478 | const round_bits: Repr.Normalized.Fraction = @truncate(mantissa << @intCast(exponent)); | ||
| 479 | if (round_bits == 0) { | ||
| 480 | self.positive = repr.sign == .positive; | ||
| 481 | return .exact; | ||
| 482 | } | ||
| 483 | round: switch (round) { | ||
| 484 | .nearest_even => { | ||
| 485 | const half: Repr.Normalized.Fraction = 1 << (@bitSizeOf(Repr.Normalized.Fraction) - 1); | ||
| 486 | if (round_bits >= half) self.addScalar(self.toConst(), 1); | ||
| 487 | if (round_bits == half) self.limbs[0] &= ~@as(Limb, 1); | ||
| 488 | }, | ||
| 489 | .away => self.addScalar(self.toConst(), 1), | ||
| 490 | .trunc => {}, | ||
| 491 | .floor => switch (repr.sign) { | ||
| 492 | .positive => {}, | ||
| 493 | .negative => continue :round .away, | ||
| 494 | }, | ||
| 495 | .ceil => switch (repr.sign) { | ||
| 496 | .positive => continue :round .away, | ||
| 497 | .negative => {}, | ||
| 498 | }, | ||
| 499 | } | ||
| 500 | self.positive = repr.sign == .positive; | ||
| 501 | return .inexact; | ||
| 502 | } | ||
| 503 | |||
| 404 | /// r = a + scalar | 504 | /// r = a + scalar |
| 405 | /// | 505 | /// |
| 406 | /// r and a may be aliases. | 506 | /// r and a may be aliases. |
| ... | @@ -2117,25 +2217,25 @@ pub const Const = struct { | ... | @@ -2117,25 +2217,25 @@ pub const Const = struct { |
| 2117 | /// Deprecated; use `toInt`. | 2217 | /// Deprecated; use `toInt`. |
| 2118 | pub const to = toInt; | 2218 | pub const to = toInt; |
| 2119 | 2219 | ||
| 2120 | /// Convert self to integer type T. | 2220 | /// Convert `self` to `Int`. |
| 2121 | /// | 2221 | /// |
| 2122 | /// Returns an error if self cannot be narrowed into the requested type without truncation. | 2222 | /// Returns an error if self cannot be narrowed into the requested type without truncation. |
| 2123 | pub fn toInt(self: Const, comptime T: type) ConvertError!T { | 2223 | pub fn toInt(self: Const, comptime Int: type) ConvertError!Int { |
| 2124 | switch (@typeInfo(T)) { | 2224 | switch (@typeInfo(Int)) { |
| 2125 | .int => |info| { | 2225 | .int => |info| { |
| 2126 | // Make sure -0 is handled correctly. | 2226 | // Make sure -0 is handled correctly. |
| 2127 | if (self.eqlZero()) return 0; | 2227 | if (self.eqlZero()) return 0; |
| 2128 | 2228 | ||
| 2129 | const UT = std.meta.Int(.unsigned, info.bits); | 2229 | const Unsigned = std.meta.Int(.unsigned, info.bits); |
| 2130 | 2230 | ||
| 2131 | if (!self.fitsInTwosComp(info.signedness, info.bits)) { | 2231 | if (!self.fitsInTwosComp(info.signedness, info.bits)) { |
| 2132 | return error.TargetTooSmall; | 2232 | return error.TargetTooSmall; |
| 2133 | } | 2233 | } |
| 2134 | 2234 | ||
| 2135 | var r: UT = 0; | 2235 | var r: Unsigned = 0; |
| 2136 | 2236 | ||
| 2137 | if (@sizeOf(UT) <= @sizeOf(Limb)) { | 2237 | if (@sizeOf(Unsigned) <= @sizeOf(Limb)) { |
| 2138 | r = @as(UT, @intCast(self.limbs[0])); | 2238 | r = @intCast(self.limbs[0]); |
| 2139 | } else { | 2239 | } else { |
| 2140 | for (self.limbs[0..self.limbs.len], 0..) |_, ri| { | 2240 | for (self.limbs[0..self.limbs.len], 0..) |_, ri| { |
| 2141 | const limb = self.limbs[self.limbs.len - ri - 1]; | 2241 | const limb = self.limbs[self.limbs.len - ri - 1]; |
| ... | @@ -2145,40 +2245,76 @@ pub const Const = struct { | ... | @@ -2145,40 +2245,76 @@ pub const Const = struct { |
| 2145 | } | 2245 | } |
| 2146 | 2246 | ||
| 2147 | if (info.signedness == .unsigned) { | 2247 | if (info.signedness == .unsigned) { |
| 2148 | return if (self.positive) @as(T, @intCast(r)) else error.NegativeIntoUnsigned; | 2248 | return if (self.positive) @intCast(r) else error.NegativeIntoUnsigned; |
| 2149 | } else { | 2249 | } else { |
| 2150 | if (self.positive) { | 2250 | if (self.positive) { |
| 2151 | return @intCast(r); | 2251 | return @intCast(r); |
| 2152 | } else { | 2252 | } else { |
| 2153 | if (math.cast(T, r)) |ok| { | 2253 | if (math.cast(Int, r)) |ok| { |
| 2154 | return -ok; | 2254 | return -ok; |
| 2155 | } else { | 2255 | } else { |
| 2156 | return minInt(T); | 2256 | return minInt(Int); |
| 2157 | } | 2257 | } |
| 2158 | } | 2258 | } |
| 2159 | } | 2259 | } |
| 2160 | }, | 2260 | }, |
| 2161 | else => @compileError("expected int type, found '" ++ @typeName(T) ++ "'"), | 2261 | else => @compileError("expected int type, found '" ++ @typeName(Int) ++ "'"), |
| 2162 | } | 2262 | } |
| 2163 | } | 2263 | } |
| 2164 | 2264 | ||
| 2165 | /// Convert self to float type T. | 2265 | /// Convert self to `Float`. |
| 2166 | pub fn toFloat(self: Const, comptime T: type) T { | 2266 | pub fn toFloat(self: Const, comptime Float: type, round: Round) struct { Float, Exactness } { |
| 2167 | if (self.limbs.len == 0) return 0; | 2267 | if (Float == comptime_float) return self.toFloat(f128, round); |
| 2168 | 2268 | const normalized_abs: Const = .{ | |
| 2169 | const base = std.math.maxInt(std.math.big.Limb) + 1; | 2269 | .limbs = self.limbs[0..llnormalize(self.limbs)], |
| 2170 | var result: f128 = 0; | 2270 | .positive = true, |
| 2171 | var i: usize = self.limbs.len; | 2271 | }; |
| 2172 | while (i != 0) { | 2272 | if (normalized_abs.eqlZero()) return .{ if (self.positive) 0.0 else -0.0, .exact }; |
| 2173 | i -= 1; | 2273 | |
| 2174 | const limb: f128 = @floatFromInt(self.limbs[i]); | 2274 | const Repr = std.math.FloatRepr(Float); |
| 2175 | result = @mulAdd(f128, base, result, limb); | 2275 | var mantissa_limbs: [calcNonZeroTwosCompLimbCount(1 + @bitSizeOf(Repr.Mantissa))]Limb = undefined; |
| 2176 | } | 2276 | var mantissa: Mutable = .{ |
| 2177 | if (self.positive) { | 2277 | .limbs = &mantissa_limbs, |
| 2178 | return @floatCast(result); | 2278 | .positive = undefined, |
| 2179 | } else { | 2279 | .len = undefined, |
| 2180 | return @floatCast(-result); | 2280 | }; |
| 2181 | } | 2281 | var exponent = normalized_abs.bitCountAbs() - 1; |
| 2282 | const exactness: Exactness = exactness: { | ||
| 2283 | if (exponent <= @bitSizeOf(Repr.Normalized.Fraction)) { | ||
| 2284 | mantissa.shiftLeft(normalized_abs, @intCast(@bitSizeOf(Repr.Normalized.Fraction) - exponent)); | ||
| 2285 | break :exactness .exact; | ||
| 2286 | } | ||
| 2287 | const shift: usize = @intCast(exponent - @bitSizeOf(Repr.Normalized.Fraction)); | ||
| 2288 | mantissa.shiftRight(normalized_abs, shift); | ||
| 2289 | const final_limb_index = (shift - 1) / limb_bits; | ||
| 2290 | const round_bits = normalized_abs.limbs[final_limb_index] << @truncate(-%shift) | | ||
| 2291 | @intFromBool(!std.mem.allEqual(Limb, normalized_abs.limbs[0..final_limb_index], 0)); | ||
| 2292 | if (round_bits == 0) break :exactness .exact; | ||
| 2293 | round: switch (round) { | ||
| 2294 | .nearest_even => { | ||
| 2295 | const half: Limb = 1 << (limb_bits - 1); | ||
| 2296 | if (round_bits >= half) mantissa.addScalar(mantissa.toConst(), 1); | ||
| 2297 | if (round_bits == half) mantissa.limbs[0] &= ~@as(Limb, 1); | ||
| 2298 | }, | ||
| 2299 | .away => mantissa.addScalar(mantissa.toConst(), 1), | ||
| 2300 | .trunc => {}, | ||
| 2301 | .floor => if (!self.positive) continue :round .away, | ||
| 2302 | .ceil => if (self.positive) continue :round .away, | ||
| 2303 | } | ||
| 2304 | break :exactness .inexact; | ||
| 2305 | }; | ||
| 2306 | const normalized_res: Repr.Normalized = .{ | ||
| 2307 | .fraction = @truncate(mantissa.toInt(Repr.Mantissa) catch |err| switch (err) { | ||
| 2308 | error.NegativeIntoUnsigned => unreachable, | ||
| 2309 | error.TargetTooSmall => fraction: { | ||
| 2310 | assert(mantissa.toConst().orderAgainstScalar(1 << @bitSizeOf(Repr.Mantissa)).compare(.eq)); | ||
| 2311 | exponent += 1; | ||
| 2312 | break :fraction 1 << (@bitSizeOf(Repr.Mantissa) - 1); | ||
| 2313 | }, | ||
| 2314 | }), | ||
| 2315 | .exponent = std.math.lossyCast(Repr.Normalized.Exponent, exponent), | ||
| 2316 | }; | ||
| 2317 | return .{ normalized_res.reconstruct(if (self.positive) .positive else .negative), exactness }; | ||
| 2182 | } | 2318 | } |
| 2183 | 2319 | ||
| 2184 | /// To allow `std.fmt.format` to work with this type. | 2320 | /// To allow `std.fmt.format` to work with this type. |
| ... | @@ -2739,16 +2875,16 @@ pub const Managed = struct { | ... | @@ -2739,16 +2875,16 @@ pub const Managed = struct { |
| 2739 | /// Deprecated; use `toInt`. | 2875 | /// Deprecated; use `toInt`. |
| 2740 | pub const to = toInt; | 2876 | pub const to = toInt; |
| 2741 | 2877 | ||
| 2742 | /// Convert self to integer type T. | 2878 | /// Convert `self` to `Int`. |
| 2743 | /// | 2879 | /// |
| 2744 | /// Returns an error if self cannot be narrowed into the requested type without truncation. | 2880 | /// Returns an error if self cannot be narrowed into the requested type without truncation. |
| 2745 | pub fn toInt(self: Managed, comptime T: type) ConvertError!T { | 2881 | pub fn toInt(self: Managed, comptime Int: type) ConvertError!Int { |
| 2746 | return self.toConst().toInt(T); | 2882 | return self.toConst().toInt(Int); |
| 2747 | } | 2883 | } |
| 2748 | 2884 | ||
| 2749 | /// Convert self to float type T. | 2885 | /// Convert `self` to `Float`. |
| 2750 | pub fn toFloat(self: Managed, comptime T: type) T { | 2886 | pub fn toFloat(self: Managed, comptime Float: type, round: Round) struct { Float, Exactness } { |
| 2751 | return self.toConst().toFloat(T); | 2887 | return self.toConst().toFloat(Float, round); |
| 2752 | } | 2888 | } |
| 2753 | 2889 | ||
| 2754 | /// Set self from the string representation `value`. | 2890 | /// Set self from the string representation `value`. |
| ... | @@ -3807,7 +3943,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) usize { | ... | @@ -3807,7 +3943,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) usize { |
| 3807 | 3943 | ||
| 3808 | // if the most significant limb becomes 0 after the shift | 3944 | // if the most significant limb becomes 0 after the shift |
| 3809 | const shrink = a[a.len - 1] >> bit_shift == 0; | 3945 | const shrink = a[a.len - 1] >> bit_shift == 0; |
| 3810 | std.debug.assert(r.len >= a.len - @intFromBool(!shrink)); | 3946 | std.debug.assert(r.len >= a.len - @intFromBool(shrink)); |
| 3811 | 3947 | ||
| 3812 | var i: usize = 0; | 3948 | var i: usize = 0; |
| 3813 | while (i < a.len - 1) : (i += 1) { | 3949 | while (i < a.len - 1) : (i += 1) { |
| ... | @@ -4240,7 +4376,7 @@ test { | ... | @@ -4240,7 +4376,7 @@ test { |
| 4240 | 4376 | ||
| 4241 | const testing_allocator = std.testing.allocator; | 4377 | const testing_allocator = std.testing.allocator; |
| 4242 | test "llshl shift by whole number of limb" { | 4378 | test "llshl shift by whole number of limb" { |
| 4243 | const padding = std.math.maxInt(Limb); | 4379 | const padding = maxInt(Limb); |
| 4244 | 4380 | ||
| 4245 | var r: [10]Limb = @splat(padding); | 4381 | var r: [10]Limb = @splat(padding); |
| 4246 | 4382 | ||
| ... | @@ -4390,8 +4526,8 @@ test "llshr to 0" { | ... | @@ -4390,8 +4526,8 @@ test "llshr to 0" { |
| 4390 | try testOneShiftCase(.llshr, .{1, &.{0}, &.{1}}); | 4526 | try testOneShiftCase(.llshr, .{1, &.{0}, &.{1}}); |
| 4391 | try testOneShiftCase(.llshr, .{5, &.{0}, &.{1}}); | 4527 | try testOneShiftCase(.llshr, .{5, &.{0}, &.{1}}); |
| 4392 | try testOneShiftCase(.llshr, .{65, &.{0}, &.{0, 1}}); | 4528 | try testOneShiftCase(.llshr, .{65, &.{0}, &.{0, 1}}); |
| 4393 | try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, std.math.maxInt(Limb)}}); | 4529 | try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, maxInt(Limb)}}); |
| 4394 | try testOneShiftCase(.llshr, .{193, &.{0}, &.{std.math.maxInt(Limb), 1, std.math.maxInt(Limb)}}); | 4530 | try testOneShiftCase(.llshr, .{193, &.{0}, &.{maxInt(Limb), 1, maxInt(Limb)}}); |
| 4395 | try testOneShiftCase(.llshr, .{193, &.{0}, &.{0xdeadbeef, 0xabcdefab, 0x1234}}); | 4531 | try testOneShiftCase(.llshr, .{193, &.{0}, &.{0xdeadbeef, 0xabcdefab, 0x1234}}); |
| 4396 | // zig fmt: on | 4532 | // zig fmt: on |
| 4397 | } | 4533 | } |
| ... | @@ -4475,7 +4611,7 @@ fn testOneShiftCase(comptime function: enum { llshr, llshl }, case: Case) !void | ... | @@ -4475,7 +4611,7 @@ fn testOneShiftCase(comptime function: enum { llshr, llshl }, case: Case) !void |
| 4475 | } | 4611 | } |
| 4476 | 4612 | ||
| 4477 | fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case) !void { | 4613 | fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case) !void { |
| 4478 | const padding = std.math.maxInt(Limb); | 4614 | const padding = maxInt(Limb); |
| 4479 | var r: [20]Limb = @splat(padding); | 4615 | var r: [20]Limb = @splat(padding); |
| 4480 | 4616 | ||
| 4481 | const shift = case[0]; | 4617 | const shift = case[0]; |
| ... | @@ -4492,7 +4628,7 @@ fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case | ... | @@ -4492,7 +4628,7 @@ fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case |
| 4492 | } | 4628 | } |
| 4493 | 4629 | ||
| 4494 | fn testOneShiftCaseAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case, shift_direction: isize) !void { | 4630 | fn testOneShiftCaseAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case, shift_direction: isize) !void { |
| 4495 | const padding = std.math.maxInt(Limb); | 4631 | const padding = maxInt(Limb); |
| 4496 | var r: [60]Limb = @splat(padding); | 4632 | var r: [60]Limb = @splat(padding); |
| 4497 | const base = 20; | 4633 | const base = 20; |
| 4498 | 4634 |
lib/std/math/big/int_test.zig+408| ... | @@ -17,6 +17,12 @@ const minInt = std.math.minInt; | ... | @@ -17,6 +17,12 @@ const minInt = std.math.minInt; |
| 17 | // They will still run on larger than this and should pass, but the multi-limb code-paths | 17 | // They will still run on larger than this and should pass, but the multi-limb code-paths |
| 18 | // may be untested in some cases. | 18 | // may be untested in some cases. |
| 19 | 19 | ||
| 20 | fn 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 | |||
| 20 | test "comptime_int set" { | 26 | test "comptime_int set" { |
| 21 | comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab; | 27 | comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab; |
| 22 | var a = try Managed.initSet(testing.allocator, s); | 28 | var a = try Managed.initSet(testing.allocator, s); |
| ... | @@ -85,6 +91,408 @@ test "to target too small error" { | ... | @@ -85,6 +91,408 @@ test "to target too small error" { |
| 85 | try testing.expectError(error.TargetTooSmall, a.toInt(u8)); | 91 | try testing.expectError(error.TargetTooSmall, a.toInt(u8)); |
| 86 | } | 92 | } |
| 87 | 93 | ||
| 94 | fn 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 | } | ||
| 278 | test 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 | |||
| 290 | fn 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 | } | ||
| 486 | test 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 | |||
| 88 | test "normalize" { | 496 | test "normalize" { |
| 89 | var a = try Managed.init(testing.allocator); | 497 | var a = try Managed.init(testing.allocator); |
| 90 | defer a.deinit(); | 498 | defer a.deinit(); |
lib/std/math/big/rational.zig deleted-820| ... | @@ -1,820 +0,0 @@ | ||
| 1 | const std = @import("../../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const debug = std.debug; | ||
| 4 | const math = std.math; | ||
| 5 | const mem = std.mem; | ||
| 6 | const testing = std.testing; | ||
| 7 | const Allocator = mem.Allocator; | ||
| 8 | |||
| 9 | const Limb = std.math.big.Limb; | ||
| 10 | const DoubleLimb = std.math.big.DoubleLimb; | ||
| 11 | const Int = std.math.big.int.Managed; | ||
| 12 | const 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. | ||
| 24 | pub 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 | |||
| 475 | fn 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 | |||
| 496 | test 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 | |||
| 516 | test "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 | |||
| 545 | test "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 | |||
| 570 | test "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 | |||
| 581 | test "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 | |||
| 594 | test "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 | |||
| 607 | test "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 | |||
| 637 | test "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 | |||
| 654 | test "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 | |||
| 671 | test "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 | |||
| 695 | test "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 | |||
| 710 | test "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 | |||
| 722 | test "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 | |||
| 737 | test "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 | |||
| 753 | test "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 | |||
| 769 | test "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 | |||
| 785 | test "div" { | ||
| 786 | { | ||
| 787 | var a = try Rational.init(testing.allocator); | ||
| 788 | defer a.deinit(); | ||
| 789 | var b = try Rational.init(testing.allocator); | ||
| 790 | defer b.deinit(); | ||
| 791 | var r = try Rational.init(testing.allocator); | ||
| 792 | defer r.deinit(); | ||
| 793 | |||
| 794 | try a.setRatio(78923, 23341); | ||
| 795 | try b.setRatio(123097, 12441414); | ||
| 796 | try a.div(a, b); | ||
| 797 | |||
| 798 | try r.setRatio(75531824394, 221015929); | ||
| 799 | try testing.expect((try a.order(r)) == .eq); | ||
| 800 | } | ||
| 801 | |||
| 802 | { | ||
| 803 | var a = try Rational.init(testing.allocator); | ||
| 804 | defer a.deinit(); | ||
| 805 | var r = try Rational.init(testing.allocator); | ||
| 806 | defer r.deinit(); | ||
| 807 | |||
| 808 | try a.setRatio(78923, 23341); | ||
| 809 | a.invert(); | ||
| 810 | |||
| 811 | try r.setRatio(23341, 78923); | ||
| 812 | try testing.expect((try a.order(r)) == .eq); | ||
| 813 | |||
| 814 | try a.setRatio(-78923, 23341); | ||
| 815 | a.invert(); | ||
| 816 | |||
| 817 | try r.setRatio(-23341, 78923); | ||
| 818 | try testing.expect((try a.order(r)) == .eq); | ||
| 819 | } | ||
| 820 | } | ||
lib/std/math/float.zig+113| ... | @@ -4,6 +4,119 @@ const assert = std.debug.assert; | ... | @@ -4,6 +4,119 @@ const assert = std.debug.assert; |
| 4 | const expect = std.testing.expect; | 4 | const expect = std.testing.expect; |
| 5 | const expectEqual = std.testing.expectEqual; | 5 | const expectEqual = std.testing.expectEqual; |
| 6 | 6 | ||
| 7 | pub const Sign = enum(u1) { positive, negative }; | ||
| 8 | |||
| 9 | pub fn FloatRepr(comptime Float: type) type { | ||
| 10 | const fractional_bits = floatFractionalBits(Float); | ||
| 11 | const exponent_bits = floatExponentBits(Float); | ||
| 12 | return packed struct { | ||
| 13 | const Repr = @This(); | ||
| 14 | |||
| 15 | mantissa: StoredMantissa, | ||
| 16 | exponent: BiasedExponent, | ||
| 17 | sign: Sign, | ||
| 18 | |||
| 19 | pub const StoredMantissa = @Type(.{ .int = .{ | ||
| 20 | .signedness = .unsigned, | ||
| 21 | .bits = floatMantissaBits(Float), | ||
| 22 | } }); | ||
| 23 | pub const Mantissa = @Type(.{ .int = .{ | ||
| 24 | .signedness = .unsigned, | ||
| 25 | .bits = 1 + fractional_bits, | ||
| 26 | } }); | ||
| 27 | pub const Exponent = @Type(.{ .int = .{ | ||
| 28 | .signedness = .signed, | ||
| 29 | .bits = exponent_bits, | ||
| 30 | } }); | ||
| 31 | pub const BiasedExponent = enum(@Type(.{ .int = .{ | ||
| 32 | .signedness = .unsigned, | ||
| 33 | .bits = exponent_bits, | ||
| 34 | } })) { | ||
| 35 | denormal = 0, | ||
| 36 | min_normal = 1, | ||
| 37 | zero = (1 << (exponent_bits - 1)) - 1, | ||
| 38 | max_normal = (1 << exponent_bits) - 2, | ||
| 39 | infinite = (1 << exponent_bits) - 1, | ||
| 40 | _, | ||
| 41 | |||
| 42 | pub const Int = @typeInfo(BiasedExponent).@"enum".tag_type; | ||
| 43 | |||
| 44 | pub fn unbias(biased: BiasedExponent) Exponent { | ||
| 45 | switch (biased) { | ||
| 46 | .denormal => unreachable, | ||
| 47 | else => return @bitCast(@intFromEnum(biased) -% @intFromEnum(BiasedExponent.zero)), | ||
| 48 | .infinite => unreachable, | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | pub fn bias(unbiased: Exponent) BiasedExponent { | ||
| 53 | return @enumFromInt(@intFromEnum(BiasedExponent.zero) +% @as(Int, @bitCast(unbiased))); | ||
| 54 | } | ||
| 55 | }; | ||
| 56 | |||
| 57 | pub const Normalized = struct { | ||
| 58 | fraction: Fraction, | ||
| 59 | exponent: Normalized.Exponent, | ||
| 60 | |||
| 61 | pub const Fraction = @Type(.{ .int = .{ | ||
| 62 | .signedness = .unsigned, | ||
| 63 | .bits = fractional_bits, | ||
| 64 | } }); | ||
| 65 | pub const Exponent = @Type(.{ .int = .{ | ||
| 66 | .signedness = .signed, | ||
| 67 | .bits = 1 + exponent_bits, | ||
| 68 | } }); | ||
| 69 | |||
| 70 | /// This currently truncates denormal values, which needs to be fixed before this can be used to | ||
| 71 | /// produce a rounded value. | ||
| 72 | pub fn reconstruct(normalized: Normalized, sign: Sign) Float { | ||
| 73 | if (normalized.exponent > BiasedExponent.max_normal.unbias()) return @bitCast(Repr{ | ||
| 74 | .mantissa = 0, | ||
| 75 | .exponent = .infinite, | ||
| 76 | .sign = sign, | ||
| 77 | }); | ||
| 78 | const mantissa = @as(Mantissa, 1 << fractional_bits) | normalized.fraction; | ||
| 79 | if (normalized.exponent < BiasedExponent.min_normal.unbias()) return @bitCast(Repr{ | ||
| 80 | .mantissa = @truncate(std.math.shr( | ||
| 81 | Mantissa, | ||
| 82 | mantissa, | ||
| 83 | BiasedExponent.min_normal.unbias() - normalized.exponent, | ||
| 84 | )), | ||
| 85 | .exponent = .denormal, | ||
| 86 | .sign = sign, | ||
| 87 | }); | ||
| 88 | return @bitCast(Repr{ | ||
| 89 | .mantissa = @truncate(mantissa), | ||
| 90 | .exponent = .bias(@intCast(normalized.exponent)), | ||
| 91 | .sign = sign, | ||
| 92 | }); | ||
| 93 | } | ||
| 94 | }; | ||
| 95 | |||
| 96 | pub const Classified = union(enum) { normalized: Normalized, infinity, nan, invalid }; | ||
| 97 | fn classify(repr: Repr) Classified { | ||
| 98 | return switch (repr.exponent) { | ||
| 99 | .denormal => { | ||
| 100 | const mantissa: Mantissa = repr.mantissa; | ||
| 101 | const shift = @clz(mantissa); | ||
| 102 | return .{ .normalized = .{ | ||
| 103 | .fraction = @truncate(mantissa << shift), | ||
| 104 | .exponent = @as(Normalized.Exponent, comptime BiasedExponent.min_normal.unbias()) - shift, | ||
| 105 | } }; | ||
| 106 | }, | ||
| 107 | else => if (repr.mantissa <= std.math.maxInt(Normalized.Fraction)) .{ .normalized = .{ | ||
| 108 | .fraction = @intCast(repr.mantissa), | ||
| 109 | .exponent = repr.exponent.unbias(), | ||
| 110 | } } else .invalid, | ||
| 111 | .infinite => switch (repr.mantissa) { | ||
| 112 | 0 => .infinity, | ||
| 113 | else => .nan, | ||
| 114 | }, | ||
| 115 | }; | ||
| 116 | } | ||
| 117 | }; | ||
| 118 | } | ||
| 119 | |||
| 7 | /// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic. | 120 | /// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic. |
| 8 | inline fn mantissaOne(comptime T: type) comptime_int { | 121 | inline fn mantissaOne(comptime T: type) comptime_int { |
| 9 | return if (@typeInfo(T).float.bits == 80) 1 << floatFractionalBits(T) else 0; | 122 | return if (@typeInfo(T).float.bits == 80) 1 << floatFractionalBits(T) else 0; |
lib/std/zon/parse.zig+1-1| ... | @@ -593,7 +593,7 @@ const Parser = struct { | ... | @@ -593,7 +593,7 @@ const Parser = struct { |
| 593 | switch (node.get(self.zoir)) { | 593 | switch (node.get(self.zoir)) { |
| 594 | .int_literal => |int| switch (int) { | 594 | .int_literal => |int| switch (int) { |
| 595 | .small => |val| return @floatFromInt(val), | 595 | .small => |val| return @floatFromInt(val), |
| 596 | .big => |val| return val.toFloat(T), | 596 | .big => |val| return val.toFloat(T, .nearest_even)[0], |
| 597 | }, | 597 | }, |
| 598 | .float_literal => |val| return @floatCast(val), | 598 | .float_literal => |val| return @floatCast(val), |
| 599 | .pos_inf => return std.math.inf(T), | 599 | .pos_inf => return std.math.inf(T), |
src/Air.zig+8| ... | @@ -683,6 +683,10 @@ pub const Inst = struct { | ... | @@ -683,6 +683,10 @@ pub const Inst = struct { |
| 683 | int_from_float, | 683 | int_from_float, |
| 684 | /// Same as `int_from_float` with optimized float mode. | 684 | /// Same as `int_from_float` with optimized float mode. |
| 685 | int_from_float_optimized, | 685 | int_from_float_optimized, |
| 686 | /// Same as `int_from_float`, but with a safety check that the operand is in bounds. | ||
| 687 | int_from_float_safe, | ||
| 688 | /// Same as `int_from_float_optimized`, but with a safety check that the operand is in bounds. | ||
| 689 | int_from_float_optimized_safe, | ||
| 686 | /// Given an integer operand, return the float with the closest mathematical meaning. | 690 | /// Given an integer operand, return the float with the closest mathematical meaning. |
| 687 | /// Uses the `ty_op` field. | 691 | /// Uses the `ty_op` field. |
| 688 | float_from_int, | 692 | float_from_int, |
| ... | @@ -1612,6 +1616,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool) | ... | @@ -1612,6 +1616,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool) |
| 1612 | .array_to_slice, | 1616 | .array_to_slice, |
| 1613 | .int_from_float, | 1617 | .int_from_float, |
| 1614 | .int_from_float_optimized, | 1618 | .int_from_float_optimized, |
| 1619 | .int_from_float_safe, | ||
| 1620 | .int_from_float_optimized_safe, | ||
| 1615 | .float_from_int, | 1621 | .float_from_int, |
| 1616 | .splat, | 1622 | .splat, |
| 1617 | .get_union_tag, | 1623 | .get_union_tag, |
| ... | @@ -1842,6 +1848,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool { | ... | @@ -1842,6 +1848,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool { |
| 1842 | .sub_safe, | 1848 | .sub_safe, |
| 1843 | .mul_safe, | 1849 | .mul_safe, |
| 1844 | .intcast_safe, | 1850 | .intcast_safe, |
| 1851 | .int_from_float_safe, | ||
| 1852 | .int_from_float_optimized_safe, | ||
| 1845 | => true, | 1853 | => true, |
| 1846 | 1854 | ||
| 1847 | .add, | 1855 | .add, |
src/Air/Legalize.zig+235-36| ... | @@ -1,7 +1,36 @@ | ... | @@ -1,7 +1,36 @@ |
| 1 | pt: Zcu.PerThread, | 1 | pt: Zcu.PerThread, |
| 2 | air_instructions: std.MultiArrayList(Air.Inst), | 2 | air_instructions: std.MultiArrayList(Air.Inst), |
| 3 | air_extra: std.ArrayListUnmanaged(u32), | 3 | air_extra: std.ArrayListUnmanaged(u32), |
| 4 | features: *const Features, | 4 | features: 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 | }, | ||
| 5 | 34 | ||
| 6 | pub const Feature = enum { | 35 | pub const Feature = enum { |
| 7 | scalarize_add, | 36 | scalarize_add, |
| ... | @@ -83,6 +112,8 @@ pub const Feature = enum { | ... | @@ -83,6 +112,8 @@ pub const Feature = enum { |
| 83 | scalarize_trunc, | 112 | scalarize_trunc, |
| 84 | scalarize_int_from_float, | 113 | scalarize_int_from_float, |
| 85 | scalarize_int_from_float_optimized, | 114 | scalarize_int_from_float_optimized, |
| 115 | scalarize_int_from_float_safe, | ||
| 116 | scalarize_int_from_float_optimized_safe, | ||
| 86 | scalarize_float_from_int, | 117 | scalarize_float_from_int, |
| 87 | scalarize_shuffle_one, | 118 | scalarize_shuffle_one, |
| 88 | scalarize_shuffle_two, | 119 | scalarize_shuffle_two, |
| ... | @@ -97,6 +128,12 @@ pub const Feature = enum { | ... | @@ -97,6 +128,12 @@ pub const Feature = enum { |
| 97 | /// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure. | 128 | /// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure. |
| 98 | /// Not compatible with `scalarize_intcast_safe`. | 129 | /// Not compatible with `scalarize_intcast_safe`. |
| 99 | expand_intcast_safe, | 130 | expand_intcast_safe, |
| 131 | /// Replace `int_from_float_safe` with an explicit safety check which `call`s the panic function on failure. | ||
| 132 | /// Not compatible with `scalarize_int_from_float_safe`. | ||
| 133 | expand_int_from_float_safe, | ||
| 134 | /// Replace `int_from_float_optimized_safe` with an explicit safety check which `call`s the panic function on failure. | ||
| 135 | /// Not compatible with `scalarize_int_from_float_optimized_safe`. | ||
| 136 | expand_int_from_float_optimized_safe, | ||
| 100 | /// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure. | 137 | /// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure. |
| 101 | /// Not compatible with `scalarize_add_safe`. | 138 | /// Not compatible with `scalarize_add_safe`. |
| 102 | expand_add_safe, | 139 | expand_add_safe, |
| ... | @@ -196,10 +233,12 @@ pub const Feature = enum { | ... | @@ -196,10 +233,12 @@ pub const Feature = enum { |
| 196 | .trunc => .scalarize_trunc, | 233 | .trunc => .scalarize_trunc, |
| 197 | .int_from_float => .scalarize_int_from_float, | 234 | .int_from_float => .scalarize_int_from_float, |
| 198 | .int_from_float_optimized => .scalarize_int_from_float_optimized, | 235 | .int_from_float_optimized => .scalarize_int_from_float_optimized, |
| 236 | .int_from_float_safe => .scalarize_int_from_float_safe, | ||
| 237 | .int_from_float_optimized_safe => .scalarize_int_from_float_optimized_safe, | ||
| 199 | .float_from_int => .scalarize_float_from_int, | 238 | .float_from_int => .scalarize_float_from_int, |
| 200 | .shuffle_one => .scalarize_shuffle_one, | 239 | .shuffle_one => .scalarize_shuffle_one, |
| 201 | .shuffle_two => .scalarize_shuffle_two, | 240 | .shuffle_two => .scalarize_shuffle_two, |
| 202 | .select => .scalarize_selects, | 241 | .select => .scalarize_select, |
| 203 | .mul_add => .scalarize_mul_add, | 242 | .mul_add => .scalarize_mul_add, |
| 204 | }; | 243 | }; |
| 205 | } | 244 | } |
| ... | @@ -210,13 +249,12 @@ pub const Features = std.enums.EnumSet(Feature); | ... | @@ -210,13 +249,12 @@ pub const Features = std.enums.EnumSet(Feature); |
| 210 | pub const Error = std.mem.Allocator.Error; | 249 | pub const Error = std.mem.Allocator.Error; |
| 211 | 250 | ||
| 212 | pub fn legalize(air: *Air, pt: Zcu.PerThread, features: *const Features) Error!void { | 251 | pub fn legalize(air: *Air, pt: Zcu.PerThread, features: *const Features) Error!void { |
| 213 | dev.check(.legalize); | ||
| 214 | assert(!features.eql(comptime .initEmpty())); // backend asked to run legalize, but no features were enabled | 252 | assert(!features.eql(comptime .initEmpty())); // backend asked to run legalize, but no features were enabled |
| 215 | var l: Legalize = .{ | 253 | var l: Legalize = .{ |
| 216 | .pt = pt, | 254 | .pt = pt, |
| 217 | .air_instructions = air.instructions.toMultiArrayList(), | 255 | .air_instructions = air.instructions.toMultiArrayList(), |
| 218 | .air_extra = air.extra, | 256 | .air_extra = air.extra, |
| 219 | .features = features, | 257 | .features = .init(features), |
| 220 | }; | 258 | }; |
| 221 | defer air.* = l.getTmpAir(); | 259 | defer air.* = l.getTmpAir(); |
| 222 | const main_extra = l.extraData(Air.Block, l.air_extra.items[@intFromEnum(Air.ExtraIndex.main_block)]); | 260 | const main_extra = l.extraData(Air.Block, l.air_extra.items[@intFromEnum(Air.ExtraIndex.main_block)]); |
| ... | @@ -278,28 +316,28 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -278,28 +316,28 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 278 | .bit_and, | 316 | .bit_and, |
| 279 | .bit_or, | 317 | .bit_or, |
| 280 | .xor, | 318 | .xor, |
| 281 | => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { | 319 | => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { |
| 282 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; | 320 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 283 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); | 321 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); |
| 284 | }, | 322 | }, |
| 285 | .add_safe => if (l.features.contains(.expand_add_safe)) { | 323 | .add_safe => if (l.features.has(.expand_add_safe)) { |
| 286 | assert(!l.features.contains(.scalarize_add_safe)); // it doesn't make sense to do both | 324 | assert(!l.features.has(.scalarize_add_safe)); // it doesn't make sense to do both |
| 287 | continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .add_with_overflow)); | 325 | continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .add_with_overflow)); |
| 288 | } else if (l.features.contains(.scalarize_add_safe)) { | 326 | } else if (l.features.has(.scalarize_add_safe)) { |
| 289 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; | 327 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 290 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); | 328 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); |
| 291 | }, | 329 | }, |
| 292 | .sub_safe => if (l.features.contains(.expand_sub_safe)) { | 330 | .sub_safe => if (l.features.has(.expand_sub_safe)) { |
| 293 | assert(!l.features.contains(.scalarize_sub_safe)); // it doesn't make sense to do both | 331 | assert(!l.features.has(.scalarize_sub_safe)); // it doesn't make sense to do both |
| 294 | continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .sub_with_overflow)); | 332 | continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .sub_with_overflow)); |
| 295 | } else if (l.features.contains(.scalarize_sub_safe)) { | 333 | } else if (l.features.has(.scalarize_sub_safe)) { |
| 296 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; | 334 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 297 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); | 335 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); |
| 298 | }, | 336 | }, |
| 299 | .mul_safe => if (l.features.contains(.expand_mul_safe)) { | 337 | .mul_safe => if (l.features.has(.expand_mul_safe)) { |
| 300 | assert(!l.features.contains(.scalarize_mul_safe)); // it doesn't make sense to do both | 338 | assert(!l.features.has(.scalarize_mul_safe)); // it doesn't make sense to do both |
| 301 | continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .mul_with_overflow)); | 339 | continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .mul_with_overflow)); |
| 302 | } else if (l.features.contains(.scalarize_mul_safe)) { | 340 | } else if (l.features.has(.scalarize_mul_safe)) { |
| 303 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; | 341 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 304 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); | 342 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); |
| 305 | }, | 343 | }, |
| ... | @@ -308,7 +346,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -308,7 +346,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 308 | .sub_with_overflow, | 346 | .sub_with_overflow, |
| 309 | .mul_with_overflow, | 347 | .mul_with_overflow, |
| 310 | .shl_with_overflow, | 348 | .shl_with_overflow, |
| 311 | => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { | 349 | => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { |
| 312 | const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; | 350 | const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 313 | if (ty_pl.ty.toType().fieldType(0, zcu).isVector(zcu)) continue :inst l.replaceInst(inst, .block, try l.scalarizeOverflowBlockPayload(inst)); | 351 | if (ty_pl.ty.toType().fieldType(0, zcu).isVector(zcu)) continue :inst l.replaceInst(inst, .block, try l.scalarizeOverflowBlockPayload(inst)); |
| 314 | }, | 352 | }, |
| ... | @@ -320,13 +358,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -320,13 +358,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 320 | .shl, | 358 | .shl, |
| 321 | .shl_exact, | 359 | .shl_exact, |
| 322 | .shl_sat, | 360 | .shl_sat, |
| 323 | => |air_tag| if (!l.features.intersectWith(comptime .initMany(&.{ | 361 | => |air_tag| if (l.features.hasAny(&.{ |
| 324 | .unsplat_shift_rhs, | 362 | .unsplat_shift_rhs, |
| 325 | .scalarize(air_tag), | 363 | .scalarize(air_tag), |
| 326 | })).eql(comptime .initEmpty())) { | 364 | })) { |
| 327 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; | 365 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 328 | if (l.typeOf(bin_op.rhs).isVector(zcu)) { | 366 | if (l.typeOf(bin_op.rhs).isVector(zcu)) { |
| 329 | if (l.features.contains(.unsplat_shift_rhs)) { | 367 | if (l.features.has(.unsplat_shift_rhs)) { |
| 330 | if (bin_op.rhs.toInterned()) |rhs_ip_index| switch (ip.indexToKey(rhs_ip_index)) { | 368 | if (bin_op.rhs.toInterned()) |rhs_ip_index| switch (ip.indexToKey(rhs_ip_index)) { |
| 331 | else => {}, | 369 | else => {}, |
| 332 | .aggregate => |aggregate| switch (aggregate.storage) { | 370 | .aggregate => |aggregate| switch (aggregate.storage) { |
| ... | @@ -347,7 +385,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -347,7 +385,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 347 | } | 385 | } |
| 348 | } | 386 | } |
| 349 | } | 387 | } |
| 350 | if (l.features.contains(comptime .scalarize(air_tag))) continue :inst try l.scalarize(inst, .bin_op); | 388 | if (l.features.has(comptime .scalarize(air_tag))) continue :inst try l.scalarize(inst, .bin_op); |
| 351 | } | 389 | } |
| 352 | }, | 390 | }, |
| 353 | inline .not, | 391 | inline .not, |
| ... | @@ -364,11 +402,11 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -364,11 +402,11 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 364 | .int_from_float, | 402 | .int_from_float, |
| 365 | .int_from_float_optimized, | 403 | .int_from_float_optimized, |
| 366 | .float_from_int, | 404 | .float_from_int, |
| 367 | => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { | 405 | => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { |
| 368 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; | 406 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 369 | if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op); | 407 | if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op); |
| 370 | }, | 408 | }, |
| 371 | .bitcast => if (l.features.contains(.scalarize_bitcast)) { | 409 | .bitcast => if (l.features.has(.scalarize_bitcast)) { |
| 372 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; | 410 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 373 | 411 | ||
| 374 | const to_ty = ty_op.ty.toType(); | 412 | const to_ty = ty_op.ty.toType(); |
| ... | @@ -404,10 +442,24 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -404,10 +442,24 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 404 | }; | 442 | }; |
| 405 | if (!from_ty_legal) continue :inst l.replaceInst(inst, .block, try l.scalarizeBitcastOperandBlockPayload(inst)); | 443 | if (!from_ty_legal) continue :inst l.replaceInst(inst, .block, try l.scalarizeBitcastOperandBlockPayload(inst)); |
| 406 | }, | 444 | }, |
| 407 | .intcast_safe => if (l.features.contains(.expand_intcast_safe)) { | 445 | .intcast_safe => if (l.features.has(.expand_intcast_safe)) { |
| 408 | assert(!l.features.contains(.scalarize_intcast_safe)); // it doesn't make sense to do both | 446 | assert(!l.features.has(.scalarize_intcast_safe)); // it doesn't make sense to do both |
| 409 | continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst)); | 447 | continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst)); |
| 410 | } else if (l.features.contains(.scalarize_intcast_safe)) { | 448 | } else if (l.features.has(.scalarize_intcast_safe)) { |
| 449 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; | ||
| 450 | if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op); | ||
| 451 | }, | ||
| 452 | .int_from_float_safe => if (l.features.has(.expand_int_from_float_safe)) { | ||
| 453 | assert(!l.features.has(.scalarize_int_from_float_safe)); | ||
| 454 | continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, false)); | ||
| 455 | } else if (l.features.has(.scalarize_int_from_float_safe)) { | ||
| 456 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; | ||
| 457 | if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op); | ||
| 458 | }, | ||
| 459 | .int_from_float_optimized_safe => if (l.features.has(.expand_int_from_float_optimized_safe)) { | ||
| 460 | assert(!l.features.has(.scalarize_int_from_float_optimized_safe)); | ||
| 461 | continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, true)); | ||
| 462 | } else if (l.features.has(.scalarize_int_from_float_optimized_safe)) { | ||
| 411 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; | 463 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 412 | if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op); | 464 | if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op); |
| 413 | }, | 465 | }, |
| ... | @@ -442,7 +494,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -442,7 +494,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 442 | .trunc_float, | 494 | .trunc_float, |
| 443 | .neg, | 495 | .neg, |
| 444 | .neg_optimized, | 496 | .neg_optimized, |
| 445 | => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { | 497 | => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { |
| 446 | const un_op = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op; | 498 | const un_op = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op; |
| 447 | if (l.typeOf(un_op).isVector(zcu)) continue :inst try l.scalarize(inst, .un_op); | 499 | if (l.typeOf(un_op).isVector(zcu)) continue :inst try l.scalarize(inst, .un_op); |
| 448 | }, | 500 | }, |
| ... | @@ -459,7 +511,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -459,7 +511,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 459 | .cmp_neq, | 511 | .cmp_neq, |
| 460 | .cmp_neq_optimized, | 512 | .cmp_neq_optimized, |
| 461 | => {}, | 513 | => {}, |
| 462 | inline .cmp_vector, .cmp_vector_optimized => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { | 514 | inline .cmp_vector, .cmp_vector_optimized => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { |
| 463 | const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; | 515 | const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 464 | if (ty_pl.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .cmp_vector); | 516 | if (ty_pl.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .cmp_vector); |
| 465 | }, | 517 | }, |
| ... | @@ -513,13 +565,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -513,13 +565,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 513 | .bool_and, | 565 | .bool_and, |
| 514 | .bool_or, | 566 | .bool_or, |
| 515 | => {}, | 567 | => {}, |
| 516 | .load => if (l.features.contains(.expand_packed_load)) { | 568 | .load => if (l.features.has(.expand_packed_load)) { |
| 517 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; | 569 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 518 | const ptr_info = l.typeOf(ty_op.operand).ptrInfo(zcu); | 570 | const ptr_info = l.typeOf(ty_op.operand).ptrInfo(zcu); |
| 519 | if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedLoadBlockPayload(inst)); | 571 | if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedLoadBlockPayload(inst)); |
| 520 | }, | 572 | }, |
| 521 | .ret, .ret_safe, .ret_load => {}, | 573 | .ret, .ret_safe, .ret_load => {}, |
| 522 | .store, .store_safe => if (l.features.contains(.expand_packed_store)) { | 574 | .store, .store_safe => if (l.features.has(.expand_packed_store)) { |
| 523 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; | 575 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 524 | const ptr_info = l.typeOf(bin_op.lhs).ptrInfo(zcu); | 576 | const ptr_info = l.typeOf(bin_op.lhs).ptrInfo(zcu); |
| 525 | if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedStoreBlockPayload(inst)); | 577 | if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedStoreBlockPayload(inst)); |
| ... | @@ -542,7 +594,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -542,7 +594,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 542 | .struct_field_ptr_index_2, | 594 | .struct_field_ptr_index_2, |
| 543 | .struct_field_ptr_index_3, | 595 | .struct_field_ptr_index_3, |
| 544 | => {}, | 596 | => {}, |
| 545 | .struct_field_val => if (l.features.contains(.expand_packed_struct_field_val)) { | 597 | .struct_field_val => if (l.features.has(.expand_packed_struct_field_val)) { |
| 546 | const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; | 598 | const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 547 | const extra = l.extraData(Air.StructField, ty_pl.payload).data; | 599 | const extra = l.extraData(Air.StructField, ty_pl.payload).data; |
| 548 | switch (l.typeOf(extra.struct_operand).containerLayout(zcu)) { | 600 | switch (l.typeOf(extra.struct_operand).containerLayout(zcu)) { |
| ... | @@ -564,7 +616,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -564,7 +616,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 564 | .ptr_elem_ptr, | 616 | .ptr_elem_ptr, |
| 565 | .array_to_slice, | 617 | .array_to_slice, |
| 566 | => {}, | 618 | => {}, |
| 567 | .reduce, .reduce_optimized => if (l.features.contains(.reduce_one_elem_to_bitcast)) { | 619 | .reduce, .reduce_optimized => if (l.features.has(.reduce_one_elem_to_bitcast)) { |
| 568 | const reduce = l.air_instructions.items(.data)[@intFromEnum(inst)].reduce; | 620 | const reduce = l.air_instructions.items(.data)[@intFromEnum(inst)].reduce; |
| 569 | const vector_ty = l.typeOf(reduce.operand); | 621 | const vector_ty = l.typeOf(reduce.operand); |
| 570 | switch (vector_ty.vectorLen(zcu)) { | 622 | switch (vector_ty.vectorLen(zcu)) { |
| ... | @@ -577,9 +629,9 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -577,9 +629,9 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 577 | } | 629 | } |
| 578 | }, | 630 | }, |
| 579 | .splat => {}, | 631 | .splat => {}, |
| 580 | .shuffle_one => if (l.features.contains(.scalarize_shuffle_one)) continue :inst try l.scalarize(inst, .shuffle_one), | 632 | .shuffle_one => if (l.features.has(.scalarize_shuffle_one)) continue :inst try l.scalarize(inst, .shuffle_one), |
| 581 | .shuffle_two => if (l.features.contains(.scalarize_shuffle_two)) continue :inst try l.scalarize(inst, .shuffle_two), | 633 | .shuffle_two => if (l.features.has(.scalarize_shuffle_two)) continue :inst try l.scalarize(inst, .shuffle_two), |
| 582 | .select => if (l.features.contains(.scalarize_select)) continue :inst try l.scalarize(inst, .select), | 634 | .select => if (l.features.has(.scalarize_select)) continue :inst try l.scalarize(inst, .select), |
| 583 | .memset, | 635 | .memset, |
| 584 | .memset_safe, | 636 | .memset_safe, |
| 585 | .memcpy, | 637 | .memcpy, |
| ... | @@ -597,7 +649,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -597,7 +649,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 597 | .error_name, | 649 | .error_name, |
| 598 | .error_set_has_value, | 650 | .error_set_has_value, |
| 599 | => {}, | 651 | => {}, |
| 600 | .aggregate_init => if (l.features.contains(.expand_packed_aggregate_init)) { | 652 | .aggregate_init => if (l.features.has(.expand_packed_aggregate_init)) { |
| 601 | const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; | 653 | const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 602 | const agg_ty = ty_pl.ty.toType(); | 654 | const agg_ty = ty_pl.ty.toType(); |
| 603 | switch (agg_ty.zigTypeTag(zcu)) { | 655 | switch (agg_ty.zigTypeTag(zcu)) { |
| ... | @@ -609,7 +661,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -609,7 +661,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 609 | } | 661 | } |
| 610 | }, | 662 | }, |
| 611 | .union_init, .prefetch => {}, | 663 | .union_init, .prefetch => {}, |
| 612 | .mul_add => if (l.features.contains(.scalarize_mul_add)) { | 664 | .mul_add => if (l.features.has(.scalarize_mul_add)) { |
| 613 | const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op; | 665 | const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op; |
| 614 | if (l.typeOf(pl_op.operand).isVector(zcu)) continue :inst try l.scalarize(inst, .pl_op_bin); | 666 | if (l.typeOf(pl_op.operand).isVector(zcu)) continue :inst try l.scalarize(inst, .pl_op_bin); |
| 615 | }, | 667 | }, |
| ... | @@ -636,6 +688,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { | ... | @@ -636,6 +688,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 636 | } | 688 | } |
| 637 | 689 | ||
| 638 | const ScalarizeForm = enum { un_op, ty_op, bin_op, pl_op_bin, bitcast, cmp_vector, shuffle_one, shuffle_two, select }; | 690 | const 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. | ||
| 639 | inline fn scalarize(l: *Legalize, orig_inst: Air.Inst.Index, comptime form: ScalarizeForm) Error!Air.Inst.Tag { | 692 | inline fn scalarize(l: *Legalize, orig_inst: Air.Inst.Index, comptime form: ScalarizeForm) Error!Air.Inst.Tag { |
| 640 | return l.replaceInst(orig_inst, .block, try l.scalarizeBlockPayload(orig_inst, form)); | 693 | return l.replaceInst(orig_inst, .block, try l.scalarizeBlockPayload(orig_inst, form)); |
| 641 | } | 694 | } |
| ... | @@ -1972,6 +2025,115 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In | ... | @@ -1972,6 +2025,115 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In |
| 1972 | .payload = try l.addBlockBody(main_block.body()), | 2025 | .payload = try l.addBlockBody(main_block.body()), |
| 1973 | } }; | 2026 | } }; |
| 1974 | } | 2027 | } |
| 2028 | fn 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 | } | ||
| 1975 | fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data { | 2137 | fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data { |
| 1976 | const pt = l.pt; | 2138 | const pt = l.pt; |
| 1977 | const zcu = pt.zcu; | 2139 | const zcu = pt.zcu; |
| ... | @@ -2349,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro | ... | @@ -2349,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro |
| 2349 | } }; | 2511 | } }; |
| 2350 | } | 2512 | } |
| 2351 | 2513 | ||
| 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. | ||
| 2519 | fn 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 | |||
| 2352 | const Block = struct { | 2550 | const Block = struct { |
| 2353 | instructions: []Air.Inst.Index, | 2551 | instructions: []Air.Inst.Index, |
| 2354 | len: usize, | 2552 | len: usize, |
| ... | @@ -2691,7 +2889,7 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 { | ... | @@ -2691,7 +2889,7 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 { |
| 2691 | } | 2889 | } |
| 2692 | 2890 | ||
| 2693 | /// Returns `tag` to remind the caller to `continue :inst` the result. | 2891 | /// Returns `tag` to remind the caller to `continue :inst` the result. |
| 2694 | /// This is inline to propagate the comptime-known `tag`. | 2892 | /// `inline` to propagate the comptime-known `tag` result. |
| 2695 | inline fn replaceInst(l: *Legalize, inst: Air.Inst.Index, comptime tag: Air.Inst.Tag, data: Air.Inst.Data) Air.Inst.Tag { | 2893 | inline fn replaceInst(l: *Legalize, inst: Air.Inst.Index, comptime tag: Air.Inst.Tag, data: Air.Inst.Data) Air.Inst.Tag { |
| 2696 | const orig_ty = if (std.debug.runtime_safety) l.typeOfIndex(inst) else {}; | 2894 | const orig_ty = if (std.debug.runtime_safety) l.typeOfIndex(inst) else {}; |
| 2697 | l.air_instructions.set(@intFromEnum(inst), .{ .tag = tag, .data = data }); | 2895 | l.air_instructions.set(@intFromEnum(inst), .{ .tag = tag, .data = data }); |
| ... | @@ -2706,4 +2904,5 @@ const InternPool = @import("../InternPool.zig"); | ... | @@ -2706,4 +2904,5 @@ const InternPool = @import("../InternPool.zig"); |
| 2706 | const Legalize = @This(); | 2904 | const Legalize = @This(); |
| 2707 | const std = @import("std"); | 2905 | const std = @import("std"); |
| 2708 | const Type = @import("../Type.zig"); | 2906 | const Type = @import("../Type.zig"); |
| 2907 | const Value = @import("../Value.zig"); | ||
| 2709 | const Zcu = @import("../Zcu.zig"); | 2908 | const Zcu = @import("../Zcu.zig"); |
src/Air/Liveness.zig+4| ... | @@ -374,6 +374,8 @@ pub fn categorizeOperand( | ... | @@ -374,6 +374,8 @@ pub fn categorizeOperand( |
| 374 | .array_to_slice, | 374 | .array_to_slice, |
| 375 | .int_from_float, | 375 | .int_from_float, |
| 376 | .int_from_float_optimized, | 376 | .int_from_float_optimized, |
| 377 | .int_from_float_safe, | ||
| 378 | .int_from_float_optimized_safe, | ||
| 377 | .float_from_int, | 379 | .float_from_int, |
| 378 | .get_union_tag, | 380 | .get_union_tag, |
| 379 | .clz, | 381 | .clz, |
| ... | @@ -1015,6 +1017,8 @@ fn analyzeInst( | ... | @@ -1015,6 +1017,8 @@ fn analyzeInst( |
| 1015 | .array_to_slice, | 1017 | .array_to_slice, |
| 1016 | .int_from_float, | 1018 | .int_from_float, |
| 1017 | .int_from_float_optimized, | 1019 | .int_from_float_optimized, |
| 1020 | .int_from_float_safe, | ||
| 1021 | .int_from_float_optimized_safe, | ||
| 1018 | .float_from_int, | 1022 | .float_from_int, |
| 1019 | .get_union_tag, | 1023 | .get_union_tag, |
| 1020 | .clz, | 1024 | .clz, |
src/Air/Liveness/Verify.zig+2| ... | @@ -107,6 +107,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { | ... | @@ -107,6 +107,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { |
| 107 | .array_to_slice, | 107 | .array_to_slice, |
| 108 | .int_from_float, | 108 | .int_from_float, |
| 109 | .int_from_float_optimized, | 109 | .int_from_float_optimized, |
| 110 | .int_from_float_safe, | ||
| 111 | .int_from_float_optimized_safe, | ||
| 110 | .float_from_int, | 112 | .float_from_int, |
| 111 | .get_union_tag, | 113 | .get_union_tag, |
| 112 | .clz, | 114 | .clz, |
src/Air/print.zig+2| ... | @@ -250,6 +250,8 @@ const Writer = struct { | ... | @@ -250,6 +250,8 @@ const Writer = struct { |
| 250 | .splat, | 250 | .splat, |
| 251 | .int_from_float, | 251 | .int_from_float, |
| 252 | .int_from_float_optimized, | 252 | .int_from_float_optimized, |
| 253 | .int_from_float_safe, | ||
| 254 | .int_from_float_optimized_safe, | ||
| 253 | .get_union_tag, | 255 | .get_union_tag, |
| 254 | .clz, | 256 | .clz, |
| 255 | .ctz, | 257 | .ctz, |
src/Air/types_resolved.zig+2| ... | @@ -130,6 +130,8 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { | ... | @@ -130,6 +130,8 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { |
| 130 | .array_to_slice, | 130 | .array_to_slice, |
| 131 | .int_from_float, | 131 | .int_from_float, |
| 132 | .int_from_float_optimized, | 132 | .int_from_float_optimized, |
| 133 | .int_from_float_safe, | ||
| 134 | .int_from_float_optimized_safe, | ||
| 133 | .float_from_int, | 135 | .float_from_int, |
| 134 | .splat, | 136 | .splat, |
| 135 | .error_set_has_value, | 137 | .error_set_has_value, |
src/Sema.zig+74-106| ... | @@ -8934,9 +8934,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError | ... | @@ -8934,9 +8934,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 8934 | 8934 | ||
| 8935 | try sema.requireRuntimeBlock(block, src, operand_src); | 8935 | try sema.requireRuntimeBlock(block, src, operand_src); |
| 8936 | if (block.wantSafety()) { | 8936 | if (block.wantSafety()) { |
| 8937 | if (zcu.backendSupportsFeature(.panic_fn)) { | 8937 | try sema.preparePanicId(src, .invalid_enum_value); |
| 8938 | _ = try sema.preparePanicId(src, .invalid_enum_value); | ||
| 8939 | } | ||
| 8940 | return block.addTyOp(.intcast_safe, dest_ty, operand); | 8938 | return block.addTyOp(.intcast_safe, dest_ty, operand); |
| 8941 | } | 8939 | } |
| 8942 | return block.addTyOp(.intcast, dest_ty, operand); | 8940 | return block.addTyOp(.intcast, dest_ty, operand); |
| ... | @@ -10340,9 +10338,7 @@ fn intCast( | ... | @@ -10340,9 +10338,7 @@ fn intCast( |
| 10340 | 10338 | ||
| 10341 | try sema.requireRuntimeBlock(block, src, operand_src); | 10339 | try sema.requireRuntimeBlock(block, src, operand_src); |
| 10342 | if (block.wantSafety()) { | 10340 | if (block.wantSafety()) { |
| 10343 | if (zcu.backendSupportsFeature(.panic_fn)) { | 10341 | try sema.preparePanicId(src, .integer_out_of_bounds); |
| 10344 | _ = try sema.preparePanicId(src, .integer_out_of_bounds); | ||
| 10345 | } | ||
| 10346 | return block.addTyOp(.intcast_safe, dest_ty, operand); | 10342 | return block.addTyOp(.intcast_safe, dest_ty, operand); |
| 10347 | } | 10343 | } |
| 10348 | return block.addTyOp(.intcast, dest_ty, operand); | 10344 | return block.addTyOp(.intcast, dest_ty, operand); |
| ... | @@ -16395,9 +16391,7 @@ fn analyzeArithmetic( | ... | @@ -16395,9 +16391,7 @@ fn analyzeArithmetic( |
| 16395 | } | 16391 | } |
| 16396 | 16392 | ||
| 16397 | if (block.wantSafety() and want_safety and scalar_tag == .int) { | 16393 | if (block.wantSafety() and want_safety and scalar_tag == .int) { |
| 16398 | if (air_tag != air_tag_safe and zcu.backendSupportsFeature(.panic_fn)) { | 16394 | if (air_tag != air_tag_safe) try sema.preparePanicId(src, .integer_overflow); |
| 16399 | _ = try sema.preparePanicId(src, .integer_overflow); | ||
| 16400 | } | ||
| 16401 | return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs); | 16395 | return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs); |
| 16402 | } | 16396 | } |
| 16403 | return block.addBinOp(air_tag, casted_lhs, casted_rhs); | 16397 | return block.addBinOp(air_tag, casted_lhs, casted_rhs); |
| ... | @@ -22178,44 +22172,32 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro | ... | @@ -22178,44 +22172,32 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro |
| 22178 | 22172 | ||
| 22179 | try sema.requireRuntimeBlock(block, src, operand_src); | 22173 | try sema.requireRuntimeBlock(block, src, operand_src); |
| 22180 | if (dest_scalar_ty.intInfo(zcu).bits == 0) { | 22174 | if (dest_scalar_ty.intInfo(zcu).bits == 0) { |
| 22181 | if (!is_vector) { | ||
| 22182 | if (block.wantSafety()) { | ||
| 22183 | const ok = try block.addBinOp(if (block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, operand, Air.internedToRef((try pt.floatValue(operand_ty, 0.0)).toIntern())); | ||
| 22184 | try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds); | ||
| 22185 | } | ||
| 22186 | return Air.internedToRef((try pt.intValue(dest_ty, 0)).toIntern()); | ||
| 22187 | } | ||
| 22188 | if (block.wantSafety()) { | 22175 | if (block.wantSafety()) { |
| 22189 | const len = dest_ty.vectorLen(zcu); | 22176 | // Emit an explicit safety check. We can do this one like `abs(x) < 1`. |
| 22190 | for (0..len) |i| { | 22177 | const abs_ref = try block.addTyOp(.abs, operand_ty, operand); |
| 22191 | const idx_ref = try pt.intRef(.usize, i); | 22178 | const max_abs_ref = if (is_vector) try block.addReduce(abs_ref, .Max) else abs_ref; |
| 22192 | const elem_ref = try block.addBinOp(.array_elem_val, operand, idx_ref); | 22179 | const one_ref = Air.internedToRef((try pt.floatValue(operand_scalar_ty, 1.0)).toIntern()); |
| 22193 | const ok = try block.addBinOp(if (block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, elem_ref, Air.internedToRef((try pt.floatValue(operand_scalar_ty, 0.0)).toIntern())); | 22180 | const ok_ref = try block.addBinOp(.cmp_lt, max_abs_ref, one_ref); |
| 22194 | try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds); | 22181 | try sema.addSafetyCheck(block, src, ok_ref, .integer_part_out_of_bounds); |
| 22195 | } | 22182 | } |
| 22196 | } | 22183 | const scalar_val = try pt.intValue(dest_scalar_ty, 0); |
| 22184 | if (!is_vector) return Air.internedToRef(scalar_val.toIntern()); | ||
| 22197 | return Air.internedToRef(try pt.intern(.{ .aggregate = .{ | 22185 | return Air.internedToRef(try pt.intern(.{ .aggregate = .{ |
| 22198 | .ty = dest_ty.toIntern(), | 22186 | .ty = dest_ty.toIntern(), |
| 22199 | .storage = .{ .repeated_elem = (try pt.intValue(dest_scalar_ty, 0)).toIntern() }, | 22187 | .storage = .{ .repeated_elem = scalar_val.toIntern() }, |
| 22200 | } })); | 22188 | } })); |
| 22201 | } | 22189 | } |
| 22202 | const result = try block.addTyOp(if (block.float_mode == .optimized) .int_from_float_optimized else .int_from_float, dest_ty, operand); | ||
| 22203 | if (block.wantSafety()) { | 22190 | if (block.wantSafety()) { |
| 22204 | const back = try block.addTyOp(.float_from_int, operand_ty, result); | 22191 | try sema.preparePanicId(src, .integer_part_out_of_bounds); |
| 22205 | const diff = try block.addBinOp(if (block.float_mode == .optimized) .sub_optimized else .sub, operand, back); | 22192 | return block.addTyOp(switch (block.float_mode) { |
| 22206 | const ok = if (is_vector) ok: { | 22193 | .optimized => .int_from_float_optimized_safe, |
| 22207 | const ok_pos = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, 1.0))).toIntern()), .lt); | 22194 | .strict => .int_from_float_safe, |
| 22208 | const ok_neg = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, -1.0))).toIntern()), .gt); | 22195 | }, dest_ty, operand); |
| 22209 | const ok = try block.addBinOp(.bit_and, ok_pos, ok_neg); | ||
| 22210 | break :ok try block.addReduce(ok, .And); | ||
| 22211 | } else ok: { | ||
| 22212 | const ok_pos = try block.addBinOp(if (block.float_mode == .optimized) .cmp_lt_optimized else .cmp_lt, diff, Air.internedToRef((try pt.floatValue(operand_ty, 1.0)).toIntern())); | ||
| 22213 | const ok_neg = try block.addBinOp(if (block.float_mode == .optimized) .cmp_gt_optimized else .cmp_gt, diff, Air.internedToRef((try pt.floatValue(operand_ty, -1.0)).toIntern())); | ||
| 22214 | break :ok try block.addBinOp(.bool_and, ok_pos, ok_neg); | ||
| 22215 | }; | ||
| 22216 | try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds); | ||
| 22217 | } | 22196 | } |
| 22218 | return result; | 22197 | return block.addTyOp(switch (block.float_mode) { |
| 22198 | .optimized => .int_from_float_optimized, | ||
| 22199 | .strict => .int_from_float, | ||
| 22200 | }, dest_ty, operand); | ||
| 22219 | } | 22201 | } |
| 22220 | 22202 | ||
| 22221 | fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { | 22203 | fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| ... | @@ -26871,7 +26853,15 @@ fn explainWhyTypeIsNotPacked( | ... | @@ -26871,7 +26853,15 @@ fn explainWhyTypeIsNotPacked( |
| 26871 | /// Backends depend on panic decls being available when lowering safety-checked | 26853 | /// Backends depend on panic decls being available when lowering safety-checked |
| 26872 | /// instructions. This function ensures the panic function will be available to | 26854 | /// instructions. This function ensures the panic function will be available to |
| 26873 | /// be called during that time. | 26855 | /// be called during that time. |
| 26874 | fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index { | 26856 | fn 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 | |||
| 26864 | fn getPanicIdFunc(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index { | ||
| 26875 | const zcu = sema.pt.zcu; | 26865 | const zcu = sema.pt.zcu; |
| 26876 | try sema.ensureMemoizedStateResolved(src, .panic); | 26866 | try sema.ensureMemoizedStateResolved(src, .panic); |
| 26877 | const panic_func = zcu.builtin_decl_values.get(panic_id.toBuiltin()); | 26867 | const panic_func = zcu.builtin_decl_values.get(panic_id.toBuiltin()); |
| ... | @@ -27120,7 +27110,7 @@ fn safetyPanic(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.Simple | ... | @@ -27120,7 +27110,7 @@ fn safetyPanic(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.Simple |
| 27120 | if (!sema.pt.zcu.backendSupportsFeature(.panic_fn)) { | 27110 | if (!sema.pt.zcu.backendSupportsFeature(.panic_fn)) { |
| 27121 | _ = try block.addNoOp(.trap); | 27111 | _ = try block.addNoOp(.trap); |
| 27122 | } else { | 27112 | } else { |
| 27123 | const panic_fn = try sema.preparePanicId(src, panic_id); | 27113 | const panic_fn = try sema.getPanicIdFunc(src, panic_id); |
| 27124 | try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{}, .@"safety check"); | 27114 | try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{}, .@"safety check"); |
| 27125 | } | 27115 | } |
| 27126 | } | 27116 | } |
| ... | @@ -32843,24 +32833,21 @@ fn cmpNumeric( | ... | @@ -32843,24 +32833,21 @@ fn cmpNumeric( |
| 32843 | } | 32833 | } |
| 32844 | } | 32834 | } |
| 32845 | if (lhs_is_float) { | 32835 | if (lhs_is_float) { |
| 32846 | if (lhs_val.floatHasFraction(zcu)) { | 32836 | const float = lhs_val.toFloat(f128, zcu); |
| 32847 | switch (op) { | 32837 | var big_int: std.math.big.int.Mutable = .{ |
| 32838 | .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)), | ||
| 32839 | .len = undefined, | ||
| 32840 | .positive = undefined, | ||
| 32841 | }; | ||
| 32842 | switch (big_int.setFloat(float, .away)) { | ||
| 32843 | .inexact => switch (op) { | ||
| 32848 | .eq => return .bool_false, | 32844 | .eq => return .bool_false, |
| 32849 | .neq => return .bool_true, | 32845 | .neq => return .bool_true, |
| 32850 | else => {}, | 32846 | else => {}, |
| 32851 | } | 32847 | }, |
| 32852 | } | 32848 | .exact => {}, |
| 32853 | |||
| 32854 | var bigint = try float128IntPartToBigInt(sema.gpa, lhs_val.toFloat(f128, zcu)); | ||
| 32855 | defer bigint.deinit(); | ||
| 32856 | if (lhs_val.floatHasFraction(zcu)) { | ||
| 32857 | if (lhs_is_signed) { | ||
| 32858 | try bigint.addScalar(&bigint, -1); | ||
| 32859 | } else { | ||
| 32860 | try bigint.addScalar(&bigint, 1); | ||
| 32861 | } | ||
| 32862 | } | 32849 | } |
| 32863 | lhs_bits = bigint.toConst().bitCountTwosComp(); | 32850 | lhs_bits = big_int.toConst().bitCountTwosComp(); |
| 32864 | } else { | 32851 | } else { |
| 32865 | lhs_bits = lhs_val.intBitCountTwosComp(zcu); | 32852 | lhs_bits = lhs_val.intBitCountTwosComp(zcu); |
| 32866 | } | 32853 | } |
| ... | @@ -32890,24 +32877,21 @@ fn cmpNumeric( | ... | @@ -32890,24 +32877,21 @@ fn cmpNumeric( |
| 32890 | } | 32877 | } |
| 32891 | } | 32878 | } |
| 32892 | if (rhs_is_float) { | 32879 | if (rhs_is_float) { |
| 32893 | if (rhs_val.floatHasFraction(zcu)) { | 32880 | const float = rhs_val.toFloat(f128, zcu); |
| 32894 | switch (op) { | 32881 | var big_int: std.math.big.int.Mutable = .{ |
| 32882 | .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)), | ||
| 32883 | .len = undefined, | ||
| 32884 | .positive = undefined, | ||
| 32885 | }; | ||
| 32886 | switch (big_int.setFloat(float, .away)) { | ||
| 32887 | .inexact => switch (op) { | ||
| 32895 | .eq => return .bool_false, | 32888 | .eq => return .bool_false, |
| 32896 | .neq => return .bool_true, | 32889 | .neq => return .bool_true, |
| 32897 | else => {}, | 32890 | else => {}, |
| 32898 | } | 32891 | }, |
| 32899 | } | 32892 | .exact => {}, |
| 32900 | |||
| 32901 | var bigint = try float128IntPartToBigInt(sema.gpa, rhs_val.toFloat(f128, zcu)); | ||
| 32902 | defer bigint.deinit(); | ||
| 32903 | if (rhs_val.floatHasFraction(zcu)) { | ||
| 32904 | if (rhs_is_signed) { | ||
| 32905 | try bigint.addScalar(&bigint, -1); | ||
| 32906 | } else { | ||
| 32907 | try bigint.addScalar(&bigint, 1); | ||
| 32908 | } | ||
| 32909 | } | 32893 | } |
| 32910 | rhs_bits = bigint.toConst().bitCountTwosComp(); | 32894 | rhs_bits = big_int.toConst().bitCountTwosComp(); |
| 32911 | } else { | 32895 | } else { |
| 32912 | rhs_bits = rhs_val.intBitCountTwosComp(zcu); | 32896 | rhs_bits = rhs_val.intBitCountTwosComp(zcu); |
| 32913 | } | 32897 | } |
| ... | @@ -36955,31 +36939,6 @@ fn intFromFloat( | ... | @@ -36955,31 +36939,6 @@ fn intFromFloat( |
| 36955 | return sema.intFromFloatScalar(block, src, val, int_ty, mode); | 36939 | return sema.intFromFloatScalar(block, src, val, int_ty, mode); |
| 36956 | } | 36940 | } |
| 36957 | 36941 | ||
| 36958 | // float is expected to be finite and non-NaN | ||
| 36959 | fn 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 | |||
| 36983 | fn intFromFloatScalar( | 36942 | fn intFromFloatScalar( |
| 36984 | sema: *Sema, | 36943 | sema: *Sema, |
| 36985 | block: *Block, | 36944 | block: *Block, |
| ... | @@ -36993,13 +36952,6 @@ fn intFromFloatScalar( | ... | @@ -36993,13 +36952,6 @@ fn intFromFloatScalar( |
| 36993 | 36952 | ||
| 36994 | if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src); | 36953 | if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src); |
| 36995 | 36954 | ||
| 36996 | if (mode == .exact and val.floatHasFraction(zcu)) return sema.fail( | ||
| 36997 | block, | ||
| 36998 | src, | ||
| 36999 | "fractional component prevents float value '{}' from coercion to type '{}'", | ||
| 37000 | .{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) }, | ||
| 37001 | ); | ||
| 37002 | |||
| 37003 | const float = val.toFloat(f128, zcu); | 36955 | const float = val.toFloat(f128, zcu); |
| 37004 | if (std.math.isNan(float)) { | 36956 | if (std.math.isNan(float)) { |
| 37005 | return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{ | 36957 | return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{ |
| ... | @@ -37012,12 +36964,28 @@ fn intFromFloatScalar( | ... | @@ -37012,12 +36964,28 @@ fn intFromFloatScalar( |
| 37012 | }); | 36964 | }); |
| 37013 | } | 36965 | } |
| 37014 | 36966 | ||
| 37015 | var big_int = try float128IntPartToBigInt(sema.arena, float); | 36967 | var big_int: std.math.big.int.Mutable = .{ |
| 37016 | defer big_int.deinit(); | 36968 | .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)), |
| 37017 | 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 | } | ||
| 37018 | const cti_result = try pt.intValue_big(.comptime_int, big_int.toConst()); | 36984 | const cti_result = try pt.intValue_big(.comptime_int, big_int.toConst()); |
| 36985 | if (int_ty.toIntern() == .comptime_int_type) return cti_result; | ||
| 37019 | 36986 | ||
| 37020 | if (!(try sema.intFitsInType(cti_result, int_ty, null))) { | 36987 | const int_info = int_ty.intInfo(zcu); |
| 36988 | if (!big_int.toConst().fitsInTwosComp(int_info.signedness, int_info.bits)) { | ||
| 37021 | return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{ | 36989 | return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{ |
| 37022 | val.fmtValueSema(pt, sema), int_ty.fmt(pt), | 36990 | val.fmtValueSema(pt, sema), int_ty.fmt(pt), |
| 37023 | }); | 36991 | }); |
src/Sema/LowerZon.zig+12-19| ... | @@ -509,30 +509,23 @@ fn lowerInt( | ... | @@ -509,30 +509,23 @@ fn lowerInt( |
| 509 | }, | 509 | }, |
| 510 | }, | 510 | }, |
| 511 | .float_literal => |val| { | 511 | .float_literal => |val| { |
| 512 | // Check for fractional components | 512 | var big_int: std.math.big.int.Mutable = .{ |
| 513 | if (@rem(val, 1) != 0) { | 513 | .limbs = try self.sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(val)), |
| 514 | return self.fail( | 514 | .len = undefined, |
| 515 | .positive = undefined, | ||
| 516 | }; | ||
| 517 | switch (big_int.setFloat(val, .trunc)) { | ||
| 518 | .inexact => return self.fail( | ||
| 515 | node, | 519 | node, |
| 516 | "fractional component prevents float value '{}' from coercion to type '{}'", | 520 | "fractional component prevents float value '{}' from coercion to type '{}'", |
| 517 | .{ val, res_ty.fmt(self.sema.pt) }, | 521 | .{ val, res_ty.fmt(self.sema.pt) }, |
| 518 | ); | 522 | ), |
| 523 | .exact => {}, | ||
| 519 | } | 524 | } |
| 520 | 525 | ||
| 521 | // Create a rational representation of the float | ||
| 522 | var rational = try std.math.big.Rational.init(self.sema.arena); | ||
| 523 | rational.setFloat(f128, val) catch |err| switch (err) { | ||
| 524 | error.NonFiniteFloat => unreachable, | ||
| 525 | error.OutOfMemory => return error.OutOfMemory, | ||
| 526 | }; | ||
| 527 | |||
| 528 | // The float is reduced in rational.setFloat, so we assert that denominator is equal to | ||
| 529 | // one | ||
| 530 | const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true }; | ||
| 531 | assert(rational.q.toConst().eqlAbs(big_one)); | ||
| 532 | |||
| 533 | // Check that the result is in range of the result type | 526 | // Check that the result is in range of the result type |
| 534 | const int_info = res_ty.intInfo(self.sema.pt.zcu); | 527 | const int_info = res_ty.intInfo(self.sema.pt.zcu); |
| 535 | if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) { | 528 | if (!big_int.toConst().fitsInTwosComp(int_info.signedness, int_info.bits)) { |
| 536 | return self.fail( | 529 | return self.fail( |
| 537 | node, | 530 | node, |
| 538 | "type '{}' cannot represent integer value '{}'", | 531 | "type '{}' cannot represent integer value '{}'", |
| ... | @@ -543,7 +536,7 @@ fn lowerInt( | ... | @@ -543,7 +536,7 @@ fn lowerInt( |
| 543 | return self.sema.pt.intern(.{ | 536 | return self.sema.pt.intern(.{ |
| 544 | .int = .{ | 537 | .int = .{ |
| 545 | .ty = res_ty.toIntern(), | 538 | .ty = res_ty.toIntern(), |
| 546 | .storage = .{ .big_int = rational.p.toConst() }, | 539 | .storage = .{ .big_int = big_int.toConst() }, |
| 547 | }, | 540 | }, |
| 548 | }); | 541 | }); |
| 549 | }, | 542 | }, |
| ... | @@ -584,7 +577,7 @@ fn lowerFloat( | ... | @@ -584,7 +577,7 @@ fn lowerFloat( |
| 584 | const value = switch (node.get(self.file.zoir.?)) { | 577 | const value = switch (node.get(self.file.zoir.?)) { |
| 585 | .int_literal => |int| switch (int) { | 578 | .int_literal => |int| switch (int) { |
| 586 | .small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), | 579 | .small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), |
| 587 | .big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128)), | 580 | .big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128, .nearest_even)[0]), |
| 588 | }, | 581 | }, |
| 589 | .float_literal => |val| try self.sema.pt.floatValue(res_ty, val), | 582 | .float_literal => |val| try self.sema.pt.floatValue(res_ty, val), |
| 590 | .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), | 583 | .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), |
src/Value.zig+5-19| ... | @@ -898,7 +898,7 @@ pub fn readFromPackedMemory( | ... | @@ -898,7 +898,7 @@ pub fn readFromPackedMemory( |
| 898 | pub fn toFloat(val: Value, comptime T: type, zcu: *const Zcu) T { | 898 | pub fn toFloat(val: Value, comptime T: type, zcu: *const Zcu) T { |
| 899 | return switch (zcu.intern_pool.indexToKey(val.toIntern())) { | 899 | return switch (zcu.intern_pool.indexToKey(val.toIntern())) { |
| 900 | .int => |int| switch (int.storage) { | 900 | .int => |int| switch (int.storage) { |
| 901 | .big_int => |big_int| big_int.toFloat(T), | 901 | .big_int => |big_int| big_int.toFloat(T, .nearest_even)[0], |
| 902 | inline .u64, .i64 => |x| { | 902 | inline .u64, .i64 => |x| { |
| 903 | if (T == f80) { | 903 | if (T == f80) { |
| 904 | @panic("TODO we can't lower this properly on non-x86 llvm backend yet"); | 904 | @panic("TODO we can't lower this properly on non-x86 llvm backend yet"); |
| ... | @@ -997,16 +997,6 @@ pub fn floatCast(val: Value, dest_ty: Type, pt: Zcu.PerThread) !Value { | ... | @@ -997,16 +997,6 @@ pub fn floatCast(val: Value, dest_ty: Type, pt: Zcu.PerThread) !Value { |
| 997 | } })); | 997 | } })); |
| 998 | } | 998 | } |
| 999 | 999 | ||
| 1000 | /// Asserts the value is a float | ||
| 1001 | pub 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 | |||
| 1010 | pub fn orderAgainstZero(lhs: Value, zcu: *Zcu) std.math.Order { | 1000 | pub fn orderAgainstZero(lhs: Value, zcu: *Zcu) std.math.Order { |
| 1011 | return orderAgainstZeroInner(lhs, .normal, zcu, {}) catch unreachable; | 1001 | return orderAgainstZeroInner(lhs, .normal, zcu, {}) catch unreachable; |
| 1012 | } | 1002 | } |
| ... | @@ -1557,17 +1547,13 @@ pub fn floatFromIntAdvanced( | ... | @@ -1557,17 +1547,13 @@ pub fn floatFromIntAdvanced( |
| 1557 | } | 1547 | } |
| 1558 | 1548 | ||
| 1559 | pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptime strat: ResolveStrat) !Value { | 1549 | pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptime strat: ResolveStrat) !Value { |
| 1560 | const zcu = pt.zcu; | 1550 | return switch (pt.zcu.intern_pool.indexToKey(val.toIntern())) { |
| 1561 | return switch (zcu.intern_pool.indexToKey(val.toIntern())) { | ||
| 1562 | .undef => try pt.undefValue(float_ty), | 1551 | .undef => try pt.undefValue(float_ty), |
| 1563 | .int => |int| switch (int.storage) { | 1552 | .int => |int| switch (int.storage) { |
| 1564 | .big_int => |big_int| { | 1553 | .big_int => |big_int| pt.floatValue(float_ty, big_int.toFloat(f128, .nearest_even)[0]), |
| 1565 | const float = big_int.toFloat(f128); | ||
| 1566 | return pt.floatValue(float_ty, float); | ||
| 1567 | }, | ||
| 1568 | inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt), | 1554 | inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt), |
| 1569 | .lazy_align => |ty| return floatFromIntInner((try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), pt.zcu, pt.tid)).scalar.toByteUnits() orelse 0, float_ty, pt), | 1555 | .lazy_align => |ty| floatFromIntInner((try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), pt.zcu, pt.tid)).scalar.toByteUnits() orelse 0, float_ty, pt), |
| 1570 | .lazy_size => |ty| return floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, float_ty, pt), | 1556 | .lazy_size => |ty| floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, float_ty, pt), |
| 1571 | }, | 1557 | }, |
| 1572 | else => unreachable, | 1558 | else => unreachable, |
| 1573 | }; | 1559 | }; |
src/arch/aarch64/CodeGen.zig+2| ... | @@ -861,6 +861,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -861,6 +861,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 861 | .sub_safe, | 861 | .sub_safe, |
| 862 | .mul_safe, | 862 | .mul_safe, |
| 863 | .intcast_safe, | 863 | .intcast_safe, |
| 864 | .int_from_float_safe, | ||
| 865 | .int_from_float_optimized_safe, | ||
| 864 | => return self.fail("TODO implement safety_checked_instructions", .{}), | 866 | => return self.fail("TODO implement safety_checked_instructions", .{}), |
| 865 | 867 | ||
| 866 | .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), | 868 | .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), |
src/arch/arm/CodeGen.zig+2| ... | @@ -850,6 +850,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -850,6 +850,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 850 | .sub_safe, | 850 | .sub_safe, |
| 851 | .mul_safe, | 851 | .mul_safe, |
| 852 | .intcast_safe, | 852 | .intcast_safe, |
| 853 | .int_from_float_safe, | ||
| 854 | .int_from_float_optimized_safe, | ||
| 853 | => return self.fail("TODO implement safety_checked_instructions", .{}), | 855 | => return self.fail("TODO implement safety_checked_instructions", .{}), |
| 854 | 856 | ||
| 855 | .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), | 857 | .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), |
src/arch/riscv64/CodeGen.zig+4| ... | @@ -54,6 +54,8 @@ const InnerError = CodeGenError || error{OutOfRegisters}; | ... | @@ -54,6 +54,8 @@ const InnerError = CodeGenError || error{OutOfRegisters}; |
| 54 | pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { | 54 | pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { |
| 55 | return comptime &.initMany(&.{ | 55 | return comptime &.initMany(&.{ |
| 56 | .expand_intcast_safe, | 56 | .expand_intcast_safe, |
| 57 | .expand_int_from_float_safe, | ||
| 58 | .expand_int_from_float_optimized_safe, | ||
| 57 | .expand_add_safe, | 59 | .expand_add_safe, |
| 58 | .expand_sub_safe, | 60 | .expand_sub_safe, |
| 59 | .expand_mul_safe, | 61 | .expand_mul_safe, |
| ... | @@ -1474,6 +1476,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -1474,6 +1476,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { |
| 1474 | .sub_safe, | 1476 | .sub_safe, |
| 1475 | .mul_safe, | 1477 | .mul_safe, |
| 1476 | .intcast_safe, | 1478 | .intcast_safe, |
| 1479 | .int_from_float_safe, | ||
| 1480 | .int_from_float_optimized_safe, | ||
| 1477 | => return func.fail("TODO implement safety_checked_instructions", .{}), | 1481 | => return func.fail("TODO implement safety_checked_instructions", .{}), |
| 1478 | 1482 | ||
| 1479 | .cmp_lt, | 1483 | .cmp_lt, |
src/arch/sparc64/CodeGen.zig+2| ... | @@ -696,6 +696,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -696,6 +696,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 696 | .sub_safe, | 696 | .sub_safe, |
| 697 | .mul_safe, | 697 | .mul_safe, |
| 698 | .intcast_safe, | 698 | .intcast_safe, |
| 699 | .int_from_float_safe, | ||
| 700 | .int_from_float_optimized_safe, | ||
| 699 | => @panic("TODO implement safety_checked_instructions"), | 701 | => @panic("TODO implement safety_checked_instructions"), |
| 700 | 702 | ||
| 701 | .is_named_enum_value => @panic("TODO implement is_named_enum_value"), | 703 | .is_named_enum_value => @panic("TODO implement is_named_enum_value"), |
src/arch/wasm/CodeGen.zig+4| ... | @@ -31,6 +31,8 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev; | ... | @@ -31,6 +31,8 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev; |
| 31 | pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { | 31 | pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { |
| 32 | return comptime &.initMany(&.{ | 32 | return comptime &.initMany(&.{ |
| 33 | .expand_intcast_safe, | 33 | .expand_intcast_safe, |
| 34 | .expand_int_from_float_safe, | ||
| 35 | .expand_int_from_float_optimized_safe, | ||
| 34 | .expand_add_safe, | 36 | .expand_add_safe, |
| 35 | .expand_sub_safe, | 37 | .expand_sub_safe, |
| 36 | .expand_mul_safe, | 38 | .expand_mul_safe, |
| ... | @@ -2020,6 +2022,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -2020,6 +2022,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2020 | .sub_safe, | 2022 | .sub_safe, |
| 2021 | .mul_safe, | 2023 | .mul_safe, |
| 2022 | .intcast_safe, | 2024 | .intcast_safe, |
| 2025 | .int_from_float_safe, | ||
| 2026 | .int_from_float_optimized_safe, | ||
| 2023 | => return cg.fail("TODO implement safety_checked_instructions", .{}), | 2027 | => return cg.fail("TODO implement safety_checked_instructions", .{}), |
| 2024 | 2028 | ||
| 2025 | .work_item_id, | 2029 | .work_item_id, |
src/arch/x86_64/CodeGen.zig+4| ... | @@ -102,6 +102,8 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features | ... | @@ -102,6 +102,8 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features |
| 102 | .reduce_one_elem_to_bitcast = true, | 102 | .reduce_one_elem_to_bitcast = true, |
| 103 | 103 | ||
| 104 | .expand_intcast_safe = true, | 104 | .expand_intcast_safe = true, |
| 105 | .expand_int_from_float_safe = true, | ||
| 106 | .expand_int_from_float_optimized_safe = true, | ||
| 105 | .expand_add_safe = true, | 107 | .expand_add_safe = true, |
| 106 | .expand_sub_safe = true, | 108 | .expand_sub_safe = true, |
| 107 | .expand_mul_safe = true, | 109 | .expand_mul_safe = true, |
| ... | @@ -107763,6 +107765,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -107763,6 +107765,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { |
| 107763 | }; | 107765 | }; |
| 107764 | try res[0].finish(inst, &.{ty_op.operand}, &ops, cg); | 107766 | try res[0].finish(inst, &.{ty_op.operand}, &ops, cg); |
| 107765 | }, | 107767 | }, |
| 107768 | .int_from_float_safe => unreachable, | ||
| 107769 | .int_from_float_optimized_safe => unreachable, | ||
| 107766 | .float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else { | 107770 | .float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else { |
| 107767 | const ty_op = air_datas[@intFromEnum(inst)].ty_op; | 107771 | const ty_op = air_datas[@intFromEnum(inst)].ty_op; |
| 107768 | var ops = try cg.tempsFromOperands(inst, .{ty_op.operand}); | 107772 | var ops = try cg.tempsFromOperands(inst, .{ty_op.operand}); |
src/codegen/c.zig+17-6| ... | @@ -23,12 +23,21 @@ const BigIntLimb = std.math.big.Limb; | ... | @@ -23,12 +23,21 @@ const BigIntLimb = std.math.big.Limb; |
| 23 | const BigInt = std.math.big.int; | 23 | const BigInt = std.math.big.int; |
| 24 | 24 | ||
| 25 | pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features { | 25 | pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features { |
| 26 | return if (dev.env.supports(.legalize)) comptime &.initMany(&.{ | 26 | return comptime switch (dev.env.supports(.legalize)) { |
| 27 | .expand_intcast_safe, | 27 | inline false, true => |supports_legalize| &.init(.{ |
| 28 | .expand_add_safe, | 28 | // we don't currently ask zig1 to use safe optimization modes |
| 29 | .expand_sub_safe, | 29 | .expand_intcast_safe = supports_legalize, |
| 30 | .expand_mul_safe, | 30 | .expand_int_from_float_safe = supports_legalize, |
| 31 | }) else null; // we don't currently ask zig1 to use safe optimization modes | 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 | }; | ||
| 32 | } | 41 | } |
| 33 | 42 | ||
| 34 | /// For most backends, MIR is basically a sequence of machine code instructions, perhaps with some | 43 | /// For most backends, MIR is basically a sequence of machine code instructions, perhaps with some |
| ... | @@ -3571,6 +3580,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, | ... | @@ -3571,6 +3580,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, |
| 3571 | .sub_safe, | 3580 | .sub_safe, |
| 3572 | .mul_safe, | 3581 | .mul_safe, |
| 3573 | .intcast_safe, | 3582 | .intcast_safe, |
| 3583 | .int_from_float_safe, | ||
| 3584 | .int_from_float_optimized_safe, | ||
| 3574 | => return f.fail("TODO implement safety_checked_instructions", .{}), | 3585 | => return f.fail("TODO implement safety_checked_instructions", .{}), |
| 3575 | 3586 | ||
| 3576 | .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}), | 3587 | .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}), |
src/codegen/llvm.zig+6-1| ... | @@ -37,7 +37,10 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev; | ... | @@ -37,7 +37,10 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev; |
| 37 | const Error = error{ OutOfMemory, CodegenFail }; | 37 | const Error = error{ OutOfMemory, CodegenFail }; |
| 38 | 38 | ||
| 39 | pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features { | 39 | pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features { |
| 40 | return null; | 40 | return comptime &.initMany(&.{ |
| 41 | .expand_int_from_float_safe, | ||
| 42 | .expand_int_from_float_optimized_safe, | ||
| 43 | }); | ||
| 41 | } | 44 | } |
| 42 | 45 | ||
| 43 | fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 { | 46 | fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 { |
| ... | @@ -4987,6 +4990,8 @@ pub const FuncGen = struct { | ... | @@ -4987,6 +4990,8 @@ pub const FuncGen = struct { |
| 4987 | 4990 | ||
| 4988 | .int_from_float => try self.airIntFromFloat(inst, .normal), | 4991 | .int_from_float => try self.airIntFromFloat(inst, .normal), |
| 4989 | .int_from_float_optimized => try self.airIntFromFloat(inst, .fast), | 4992 | .int_from_float_optimized => try self.airIntFromFloat(inst, .fast), |
| 4993 | .int_from_float_safe => unreachable, // handled by `legalizeFeatures` | ||
| 4994 | .int_from_float_optimized_safe => unreachable, // handled by `legalizeFeatures` | ||
| 4990 | 4995 | ||
| 4991 | .array_to_slice => try self.airArrayToSlice(inst), | 4996 | .array_to_slice => try self.airArrayToSlice(inst), |
| 4992 | .float_from_int => try self.airFloatFromInt(inst), | 4997 | .float_from_int => try self.airFloatFromInt(inst), |
src/codegen/spirv.zig+2| ... | @@ -31,6 +31,8 @@ const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef); | ... | @@ -31,6 +31,8 @@ const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef); |
| 31 | pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { | 31 | pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { |
| 32 | return comptime &.initMany(&.{ | 32 | return comptime &.initMany(&.{ |
| 33 | .expand_intcast_safe, | 33 | .expand_intcast_safe, |
| 34 | .expand_int_from_float_safe, | ||
| 35 | .expand_int_from_float_optimized_safe, | ||
| 34 | .expand_add_safe, | 36 | .expand_add_safe, |
| 35 | .expand_sub_safe, | 37 | .expand_sub_safe, |
| 36 | .expand_mul_safe, | 38 | .expand_mul_safe, |
stage1/zig.h+9| ... | @@ -272,6 +272,15 @@ | ... | @@ -272,6 +272,15 @@ |
| 272 | #define zig_linksection_fn zig_linksection | 272 | #define zig_linksection_fn zig_linksection |
| 273 | #endif | 273 | #endif |
| 274 | 274 | ||
| 275 | #if zig_has_attribute(visibility) | ||
| 276 | #define zig_visibility(name) __attribute__((visibility(#name))) | ||
| 277 | #else | ||
| 278 | #define zig_visibility(name) zig_visibility_##name | ||
| 279 | #define zig_visibility_default | ||
| 280 | #define zig_visibility_hidden zig_visibility_hidden_unavailable | ||
| 281 | #define zig_visibility_protected zig_visibility_protected_unavailable | ||
| 282 | #endif | ||
| 283 | |||
| 275 | #if zig_has_builtin(unreachable) || defined(zig_gcc) || defined(zig_tinyc) | 284 | #if zig_has_builtin(unreachable) || defined(zig_gcc) || defined(zig_tinyc) |
| 276 | #define zig_unreachable() __builtin_unreachable() | 285 | #define zig_unreachable() __builtin_unreachable() |
| 277 | #elif defined(zig_msvc) | 286 | #elif defined(zig_msvc) |
stage1/zig1.wasm| Binary files a/stage1/zig1.wasm and b/stage1/zig1.wasm differ | |||
test/behavior/cast.zig+64| ... | @@ -102,6 +102,7 @@ test "comptime_int @floatFromInt" { | ... | @@ -102,6 +102,7 @@ test "comptime_int @floatFromInt" { |
| 102 | test "@floatFromInt" { | 102 | test "@floatFromInt" { |
| 103 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO | 103 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO |
| 104 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | 104 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 105 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | ||
| 105 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | 106 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 106 | 107 | ||
| 107 | const S = struct { | 108 | const S = struct { |
| ... | @@ -2737,3 +2738,66 @@ test "peer type resolution: slice of sentinel-terminated array" { | ... | @@ -2737,3 +2738,66 @@ test "peer type resolution: slice of sentinel-terminated array" { |
| 2737 | try expect(result[0][0] == 10); | 2738 | try expect(result[0][0] == 10); |
| 2738 | try expect(result[0][1] == 20); | 2739 | try expect(result[0][1] == 20); |
| 2739 | } | 2740 | } |
| 2741 | |||
| 2742 | test "@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 | |||
| 2775 | test "@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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | pub 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 | } | ||
| 9 | var x: f32 = 1.0; | ||
| 10 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | pub 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 | } | ||
| 9 | var x: f32 = -1.0; | ||
| 10 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | pub 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 | } | ||
| 9 | var x: f32 = 128; | ||
| 10 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | pub 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 | } | ||
| 9 | var x: f32 = -129; | ||
| 10 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | pub 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 | } | ||
| 9 | var x: f32 = 1.0; | ||
| 10 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | pub 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 | } | ||
| 9 | var x: f32 = -1.0; | ||
| 10 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | pub 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 | } | ||
| 9 | var x: f32 = 256; | ||
| 10 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | pub 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 | } | ||
| 9 | var x: f32 = -1; | ||
| 10 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | pub 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 | } | ||
| 9 | var x: @Vector(2, f32) = .{ 100, 512 }; | ||
| 10 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | pub 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 | } | ||
| 9 | var x: @Vector(2, f32) = .{ 100, -513 }; | ||
| 10 | pub fn main() !void { | ||
| 11 | _ = @as(@Vector(2, i10), @intFromFloat(x)); | ||
| 12 | return error.TestFailed; | ||
| 13 | } | ||
| 14 | // run | ||
| 15 | // backend=stage2,llvm | ||
| 16 | // target=native | ||