mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
Merge pull request #24188 from mlugg/intfromfloat-safety
Absorb std.math.big.rational logic into std.math.big.int; fix `@intFromFloat` safety check
This commit is contained in:
commit
561fdd0ed3
42
lib/compiler/aro/aro/Value.zig
vendored
42
lib/compiler/aro/aro/Value.zig
vendored
@ -148,35 +148,25 @@ pub fn floatToInt(v: *Value, dest_ty: Type, comp: *Compilation) !FloatToIntChang
|
|||||||
return .out_of_range;
|
return .out_of_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
const had_fraction = @rem(float_val, 1) != 0;
|
|
||||||
const is_negative = std.math.signbit(float_val);
|
|
||||||
const floored = @floor(@abs(float_val));
|
|
||||||
|
|
||||||
var rational = try std.math.big.Rational.init(comp.gpa);
|
|
||||||
defer rational.deinit();
|
|
||||||
rational.setFloat(f128, floored) catch |err| switch (err) {
|
|
||||||
error.NonFiniteFloat => {
|
|
||||||
v.* = .{};
|
|
||||||
return .overflow;
|
|
||||||
},
|
|
||||||
error.OutOfMemory => return error.OutOfMemory,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The float is reduced in rational.setFloat, so we assert that denominator is equal to one
|
|
||||||
const big_one = BigIntConst{ .limbs = &.{1}, .positive = true };
|
|
||||||
assert(rational.q.toConst().eqlAbs(big_one));
|
|
||||||
|
|
||||||
if (is_negative) {
|
|
||||||
rational.negate();
|
|
||||||
}
|
|
||||||
|
|
||||||
const signedness = dest_ty.signedness(comp);
|
const signedness = dest_ty.signedness(comp);
|
||||||
const bits: usize = @intCast(dest_ty.bitSizeof(comp).?);
|
const bits: usize = @intCast(dest_ty.bitSizeof(comp).?);
|
||||||
|
|
||||||
// rational.p.truncate(rational.p.toConst(), signedness: Signedness, bit_count: usize)
|
var big_int: std.math.big.int.Mutable = .{
|
||||||
const fits = rational.p.fitsInTwosComp(signedness, bits);
|
.limbs = try comp.gpa.alloc(std.math.big.Limb, @max(
|
||||||
v.* = try intern(comp, .{ .int = .{ .big_int = rational.p.toConst() } });
|
std.math.big.int.calcLimbLen(float_val),
|
||||||
try rational.p.truncate(&rational.p, signedness, bits);
|
std.math.big.int.calcTwosCompLimbCount(bits),
|
||||||
|
)),
|
||||||
|
.len = undefined,
|
||||||
|
.positive = undefined,
|
||||||
|
};
|
||||||
|
const had_fraction = switch (big_int.setFloat(float_val, .trunc)) {
|
||||||
|
.inexact => true,
|
||||||
|
.exact => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fits = big_int.toConst().fitsInTwosComp(signedness, bits);
|
||||||
|
v.* = try intern(comp, .{ .int = .{ .big_int = big_int.toConst() } });
|
||||||
|
big_int.truncate(big_int.toConst(), signedness, bits);
|
||||||
|
|
||||||
if (!was_zero and v.isZero(comp)) return .nonzero_to_zero;
|
if (!was_zero and v.isZero(comp)) return .nonzero_to_zero;
|
||||||
if (!fits) return .out_of_range;
|
if (!fits) return .out_of_range;
|
||||||
|
|||||||
@ -45,6 +45,7 @@ pub const rad_per_deg = 0.017453292519943295769236907684886127134428718885417254
|
|||||||
/// 180.0/pi
|
/// 180.0/pi
|
||||||
pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861;
|
pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861;
|
||||||
|
|
||||||
|
pub const FloatRepr = float.FloatRepr;
|
||||||
pub const floatExponentBits = float.floatExponentBits;
|
pub const floatExponentBits = float.floatExponentBits;
|
||||||
pub const floatMantissaBits = float.floatMantissaBits;
|
pub const floatMantissaBits = float.floatMantissaBits;
|
||||||
pub const floatFractionalBits = float.floatFractionalBits;
|
pub const floatFractionalBits = float.floatFractionalBits;
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
const std = @import("../std.zig");
|
const std = @import("../std.zig");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
pub const Rational = @import("big/rational.zig").Rational;
|
|
||||||
pub const int = @import("big/int.zig");
|
pub const int = @import("big/int.zig");
|
||||||
pub const Limb = usize;
|
pub const Limb = usize;
|
||||||
const limb_info = @typeInfo(Limb).int;
|
const limb_info = @typeInfo(Limb).int;
|
||||||
@ -18,7 +17,6 @@ comptime {
|
|||||||
|
|
||||||
test {
|
test {
|
||||||
_ = int;
|
_ = int;
|
||||||
_ = Rational;
|
|
||||||
_ = Limb;
|
_ = Limb;
|
||||||
_ = SignedLimb;
|
_ = SignedLimb;
|
||||||
_ = DoubleLimb;
|
_ = DoubleLimb;
|
||||||
|
|||||||
@ -18,17 +18,28 @@ const Signedness = std.builtin.Signedness;
|
|||||||
const native_endian = builtin.cpu.arch.endian();
|
const native_endian = builtin.cpu.arch.endian();
|
||||||
|
|
||||||
/// Returns the number of limbs needed to store `scalar`, which must be a
|
/// Returns the number of limbs needed to store `scalar`, which must be a
|
||||||
/// primitive integer value.
|
/// primitive integer or float value.
|
||||||
/// Note: A comptime-known upper bound of this value that may be used
|
/// Note: A comptime-known upper bound of this value that may be used
|
||||||
/// instead if `scalar` is not already comptime-known is
|
/// instead if `scalar` is not already comptime-known is
|
||||||
/// `calcTwosCompLimbCount(@typeInfo(@TypeOf(scalar)).int.bits)`
|
/// `calcTwosCompLimbCount(@typeInfo(@TypeOf(scalar)).int.bits)`
|
||||||
pub fn calcLimbLen(scalar: anytype) usize {
|
pub fn calcLimbLen(scalar: anytype) usize {
|
||||||
if (scalar == 0) {
|
switch (@typeInfo(@TypeOf(scalar))) {
|
||||||
return 1;
|
.int, .comptime_int => {
|
||||||
}
|
if (scalar == 0) return 1;
|
||||||
|
|
||||||
const w_value = @abs(scalar);
|
const w_value = @abs(scalar);
|
||||||
return @as(usize, @intCast(@divFloor(@as(Limb, @intCast(math.log2(w_value))), limb_bits) + 1));
|
return @as(usize, @intCast(@divFloor(@as(Limb, @intCast(math.log2(w_value))), limb_bits) + 1));
|
||||||
|
},
|
||||||
|
.float => {
|
||||||
|
const repr: std.math.FloatRepr(@TypeOf(scalar)) = @bitCast(scalar);
|
||||||
|
return switch (repr.exponent) {
|
||||||
|
.denormal => 1,
|
||||||
|
else => return calcNonZeroTwosCompLimbCount(@as(usize, 2) + @max(repr.exponent.unbias(), 0)),
|
||||||
|
.infinite => 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.comptime_float => return calcLimbLen(@as(f128, scalar)),
|
||||||
|
else => @compileError("expected float or int, got " ++ @typeName(@TypeOf(scalar))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize {
|
pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize {
|
||||||
@ -134,6 +145,22 @@ pub const TwosCompIntLimit = enum {
|
|||||||
max,
|
max,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Round = enum {
|
||||||
|
/// Round to the nearest representable value, with ties broken by the representation
|
||||||
|
/// that ends with a 0 bit.
|
||||||
|
nearest_even,
|
||||||
|
/// Round away from zero.
|
||||||
|
away,
|
||||||
|
/// Round towards zero.
|
||||||
|
trunc,
|
||||||
|
/// Round towards negative infinity.
|
||||||
|
floor,
|
||||||
|
/// Round towards positive infinity.
|
||||||
|
ceil,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Exactness = enum { inexact, exact };
|
||||||
|
|
||||||
/// A arbitrary-precision big integer, with a fixed set of mutable limbs.
|
/// A arbitrary-precision big integer, with a fixed set of mutable limbs.
|
||||||
pub const Mutable = struct {
|
pub const Mutable = struct {
|
||||||
/// Raw digits. These are:
|
/// Raw digits. These are:
|
||||||
@ -155,6 +182,20 @@ pub const Mutable = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const ConvertError = Const.ConvertError;
|
||||||
|
|
||||||
|
/// Convert `self` to `Int`.
|
||||||
|
///
|
||||||
|
/// Returns an error if self cannot be narrowed into the requested type without truncation.
|
||||||
|
pub fn toInt(self: Mutable, comptime Int: type) ConvertError!Int {
|
||||||
|
return self.toConst().toInt(Int);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert `self` to `Float`.
|
||||||
|
pub fn toFloat(self: Mutable, comptime Float: type, round: Round) struct { Float, Exactness } {
|
||||||
|
return self.toConst().toFloat(Float, round);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if `a == 0`.
|
/// Returns true if `a == 0`.
|
||||||
pub fn eqlZero(self: Mutable) bool {
|
pub fn eqlZero(self: Mutable) bool {
|
||||||
return self.toConst().eqlZero();
|
return self.toConst().eqlZero();
|
||||||
@ -401,6 +442,65 @@ pub const Mutable = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the Mutable to a float value rounded according to `round`.
|
||||||
|
/// Returns whether the conversion was exact (`round` had no effect on the result).
|
||||||
|
pub fn setFloat(self: *Mutable, value: anytype, round: Round) Exactness {
|
||||||
|
const Float = @TypeOf(value);
|
||||||
|
if (Float == comptime_float) return self.setFloat(@as(f128, value), round);
|
||||||
|
const abs_value = @abs(value);
|
||||||
|
if (abs_value < 1.0) {
|
||||||
|
if (abs_value == 0.0) {
|
||||||
|
self.set(0);
|
||||||
|
return .exact;
|
||||||
|
}
|
||||||
|
self.set(@as(i2, round: switch (round) {
|
||||||
|
.nearest_even => if (abs_value <= 0.5) 0 else continue :round .away,
|
||||||
|
.away => if (value < 0.0) -1 else 1,
|
||||||
|
.trunc => 0,
|
||||||
|
.floor => -@as(i2, @intFromBool(value < 0.0)),
|
||||||
|
.ceil => @intFromBool(value > 0.0),
|
||||||
|
}));
|
||||||
|
return .inexact;
|
||||||
|
}
|
||||||
|
const Repr = std.math.FloatRepr(Float);
|
||||||
|
const repr: Repr = @bitCast(value);
|
||||||
|
const exponent = repr.exponent.unbias();
|
||||||
|
assert(exponent >= 0);
|
||||||
|
const int_bit: Repr.Mantissa = 1 << (@bitSizeOf(Repr.Mantissa) - 1);
|
||||||
|
const mantissa = int_bit | repr.mantissa;
|
||||||
|
if (exponent >= @bitSizeOf(Repr.Normalized.Fraction)) {
|
||||||
|
self.set(mantissa);
|
||||||
|
self.shiftLeft(self.toConst(), @intCast(exponent - @bitSizeOf(Repr.Normalized.Fraction)));
|
||||||
|
self.positive = repr.sign == .positive;
|
||||||
|
return .exact;
|
||||||
|
}
|
||||||
|
self.set(mantissa >> @intCast(@bitSizeOf(Repr.Normalized.Fraction) - exponent));
|
||||||
|
const round_bits: Repr.Normalized.Fraction = @truncate(mantissa << @intCast(exponent));
|
||||||
|
if (round_bits == 0) {
|
||||||
|
self.positive = repr.sign == .positive;
|
||||||
|
return .exact;
|
||||||
|
}
|
||||||
|
round: switch (round) {
|
||||||
|
.nearest_even => {
|
||||||
|
const half: Repr.Normalized.Fraction = 1 << (@bitSizeOf(Repr.Normalized.Fraction) - 1);
|
||||||
|
if (round_bits >= half) self.addScalar(self.toConst(), 1);
|
||||||
|
if (round_bits == half) self.limbs[0] &= ~@as(Limb, 1);
|
||||||
|
},
|
||||||
|
.away => self.addScalar(self.toConst(), 1),
|
||||||
|
.trunc => {},
|
||||||
|
.floor => switch (repr.sign) {
|
||||||
|
.positive => {},
|
||||||
|
.negative => continue :round .away,
|
||||||
|
},
|
||||||
|
.ceil => switch (repr.sign) {
|
||||||
|
.positive => continue :round .away,
|
||||||
|
.negative => {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.positive = repr.sign == .positive;
|
||||||
|
return .inexact;
|
||||||
|
}
|
||||||
|
|
||||||
/// r = a + scalar
|
/// r = a + scalar
|
||||||
///
|
///
|
||||||
/// r and a may be aliases.
|
/// r and a may be aliases.
|
||||||
@ -2117,25 +2217,25 @@ pub const Const = struct {
|
|||||||
/// Deprecated; use `toInt`.
|
/// Deprecated; use `toInt`.
|
||||||
pub const to = toInt;
|
pub const to = toInt;
|
||||||
|
|
||||||
/// Convert self to integer type T.
|
/// Convert `self` to `Int`.
|
||||||
///
|
///
|
||||||
/// Returns an error if self cannot be narrowed into the requested type without truncation.
|
/// Returns an error if self cannot be narrowed into the requested type without truncation.
|
||||||
pub fn toInt(self: Const, comptime T: type) ConvertError!T {
|
pub fn toInt(self: Const, comptime Int: type) ConvertError!Int {
|
||||||
switch (@typeInfo(T)) {
|
switch (@typeInfo(Int)) {
|
||||||
.int => |info| {
|
.int => |info| {
|
||||||
// Make sure -0 is handled correctly.
|
// Make sure -0 is handled correctly.
|
||||||
if (self.eqlZero()) return 0;
|
if (self.eqlZero()) return 0;
|
||||||
|
|
||||||
const UT = std.meta.Int(.unsigned, info.bits);
|
const Unsigned = std.meta.Int(.unsigned, info.bits);
|
||||||
|
|
||||||
if (!self.fitsInTwosComp(info.signedness, info.bits)) {
|
if (!self.fitsInTwosComp(info.signedness, info.bits)) {
|
||||||
return error.TargetTooSmall;
|
return error.TargetTooSmall;
|
||||||
}
|
}
|
||||||
|
|
||||||
var r: UT = 0;
|
var r: Unsigned = 0;
|
||||||
|
|
||||||
if (@sizeOf(UT) <= @sizeOf(Limb)) {
|
if (@sizeOf(Unsigned) <= @sizeOf(Limb)) {
|
||||||
r = @as(UT, @intCast(self.limbs[0]));
|
r = @intCast(self.limbs[0]);
|
||||||
} else {
|
} else {
|
||||||
for (self.limbs[0..self.limbs.len], 0..) |_, ri| {
|
for (self.limbs[0..self.limbs.len], 0..) |_, ri| {
|
||||||
const limb = self.limbs[self.limbs.len - ri - 1];
|
const limb = self.limbs[self.limbs.len - ri - 1];
|
||||||
@ -2145,40 +2245,76 @@ pub const Const = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (info.signedness == .unsigned) {
|
if (info.signedness == .unsigned) {
|
||||||
return if (self.positive) @as(T, @intCast(r)) else error.NegativeIntoUnsigned;
|
return if (self.positive) @intCast(r) else error.NegativeIntoUnsigned;
|
||||||
} else {
|
} else {
|
||||||
if (self.positive) {
|
if (self.positive) {
|
||||||
return @intCast(r);
|
return @intCast(r);
|
||||||
} else {
|
} else {
|
||||||
if (math.cast(T, r)) |ok| {
|
if (math.cast(Int, r)) |ok| {
|
||||||
return -ok;
|
return -ok;
|
||||||
} else {
|
} else {
|
||||||
return minInt(T);
|
return minInt(Int);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => @compileError("expected int type, found '" ++ @typeName(T) ++ "'"),
|
else => @compileError("expected int type, found '" ++ @typeName(Int) ++ "'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert self to float type T.
|
/// Convert self to `Float`.
|
||||||
pub fn toFloat(self: Const, comptime T: type) T {
|
pub fn toFloat(self: Const, comptime Float: type, round: Round) struct { Float, Exactness } {
|
||||||
if (self.limbs.len == 0) return 0;
|
if (Float == comptime_float) return self.toFloat(f128, round);
|
||||||
|
const normalized_abs: Const = .{
|
||||||
|
.limbs = self.limbs[0..llnormalize(self.limbs)],
|
||||||
|
.positive = true,
|
||||||
|
};
|
||||||
|
if (normalized_abs.eqlZero()) return .{ if (self.positive) 0.0 else -0.0, .exact };
|
||||||
|
|
||||||
const base = std.math.maxInt(std.math.big.Limb) + 1;
|
const Repr = std.math.FloatRepr(Float);
|
||||||
var result: f128 = 0;
|
var mantissa_limbs: [calcNonZeroTwosCompLimbCount(1 + @bitSizeOf(Repr.Mantissa))]Limb = undefined;
|
||||||
var i: usize = self.limbs.len;
|
var mantissa: Mutable = .{
|
||||||
while (i != 0) {
|
.limbs = &mantissa_limbs,
|
||||||
i -= 1;
|
.positive = undefined,
|
||||||
const limb: f128 = @floatFromInt(self.limbs[i]);
|
.len = undefined,
|
||||||
result = @mulAdd(f128, base, result, limb);
|
};
|
||||||
|
var exponent = normalized_abs.bitCountAbs() - 1;
|
||||||
|
const exactness: Exactness = exactness: {
|
||||||
|
if (exponent <= @bitSizeOf(Repr.Normalized.Fraction)) {
|
||||||
|
mantissa.shiftLeft(normalized_abs, @intCast(@bitSizeOf(Repr.Normalized.Fraction) - exponent));
|
||||||
|
break :exactness .exact;
|
||||||
}
|
}
|
||||||
if (self.positive) {
|
const shift: usize = @intCast(exponent - @bitSizeOf(Repr.Normalized.Fraction));
|
||||||
return @floatCast(result);
|
mantissa.shiftRight(normalized_abs, shift);
|
||||||
} else {
|
const final_limb_index = (shift - 1) / limb_bits;
|
||||||
return @floatCast(-result);
|
const round_bits = normalized_abs.limbs[final_limb_index] << @truncate(-%shift) |
|
||||||
|
@intFromBool(!std.mem.allEqual(Limb, normalized_abs.limbs[0..final_limb_index], 0));
|
||||||
|
if (round_bits == 0) break :exactness .exact;
|
||||||
|
round: switch (round) {
|
||||||
|
.nearest_even => {
|
||||||
|
const half: Limb = 1 << (limb_bits - 1);
|
||||||
|
if (round_bits >= half) mantissa.addScalar(mantissa.toConst(), 1);
|
||||||
|
if (round_bits == half) mantissa.limbs[0] &= ~@as(Limb, 1);
|
||||||
|
},
|
||||||
|
.away => mantissa.addScalar(mantissa.toConst(), 1),
|
||||||
|
.trunc => {},
|
||||||
|
.floor => if (!self.positive) continue :round .away,
|
||||||
|
.ceil => if (self.positive) continue :round .away,
|
||||||
}
|
}
|
||||||
|
break :exactness .inexact;
|
||||||
|
};
|
||||||
|
const normalized_res: Repr.Normalized = .{
|
||||||
|
.fraction = @truncate(mantissa.toInt(Repr.Mantissa) catch |err| switch (err) {
|
||||||
|
error.NegativeIntoUnsigned => unreachable,
|
||||||
|
error.TargetTooSmall => fraction: {
|
||||||
|
assert(mantissa.toConst().orderAgainstScalar(1 << @bitSizeOf(Repr.Mantissa)).compare(.eq));
|
||||||
|
exponent += 1;
|
||||||
|
break :fraction 1 << (@bitSizeOf(Repr.Mantissa) - 1);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
.exponent = std.math.lossyCast(Repr.Normalized.Exponent, exponent),
|
||||||
|
};
|
||||||
|
return .{ normalized_res.reconstruct(if (self.positive) .positive else .negative), exactness };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// To allow `std.fmt.format` to work with this type.
|
/// To allow `std.fmt.format` to work with this type.
|
||||||
@ -2739,16 +2875,16 @@ pub const Managed = struct {
|
|||||||
/// Deprecated; use `toInt`.
|
/// Deprecated; use `toInt`.
|
||||||
pub const to = toInt;
|
pub const to = toInt;
|
||||||
|
|
||||||
/// Convert self to integer type T.
|
/// Convert `self` to `Int`.
|
||||||
///
|
///
|
||||||
/// Returns an error if self cannot be narrowed into the requested type without truncation.
|
/// Returns an error if self cannot be narrowed into the requested type without truncation.
|
||||||
pub fn toInt(self: Managed, comptime T: type) ConvertError!T {
|
pub fn toInt(self: Managed, comptime Int: type) ConvertError!Int {
|
||||||
return self.toConst().toInt(T);
|
return self.toConst().toInt(Int);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert self to float type T.
|
/// Convert `self` to `Float`.
|
||||||
pub fn toFloat(self: Managed, comptime T: type) T {
|
pub fn toFloat(self: Managed, comptime Float: type, round: Round) struct { Float, Exactness } {
|
||||||
return self.toConst().toFloat(T);
|
return self.toConst().toFloat(Float, round);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set self from the string representation `value`.
|
/// Set self from the string representation `value`.
|
||||||
@ -3807,7 +3943,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) usize {
|
|||||||
|
|
||||||
// if the most significant limb becomes 0 after the shift
|
// if the most significant limb becomes 0 after the shift
|
||||||
const shrink = a[a.len - 1] >> bit_shift == 0;
|
const shrink = a[a.len - 1] >> bit_shift == 0;
|
||||||
std.debug.assert(r.len >= a.len - @intFromBool(!shrink));
|
std.debug.assert(r.len >= a.len - @intFromBool(shrink));
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < a.len - 1) : (i += 1) {
|
while (i < a.len - 1) : (i += 1) {
|
||||||
@ -4240,7 +4376,7 @@ test {
|
|||||||
|
|
||||||
const testing_allocator = std.testing.allocator;
|
const testing_allocator = std.testing.allocator;
|
||||||
test "llshl shift by whole number of limb" {
|
test "llshl shift by whole number of limb" {
|
||||||
const padding = std.math.maxInt(Limb);
|
const padding = maxInt(Limb);
|
||||||
|
|
||||||
var r: [10]Limb = @splat(padding);
|
var r: [10]Limb = @splat(padding);
|
||||||
|
|
||||||
@ -4390,8 +4526,8 @@ test "llshr to 0" {
|
|||||||
try testOneShiftCase(.llshr, .{1, &.{0}, &.{1}});
|
try testOneShiftCase(.llshr, .{1, &.{0}, &.{1}});
|
||||||
try testOneShiftCase(.llshr, .{5, &.{0}, &.{1}});
|
try testOneShiftCase(.llshr, .{5, &.{0}, &.{1}});
|
||||||
try testOneShiftCase(.llshr, .{65, &.{0}, &.{0, 1}});
|
try testOneShiftCase(.llshr, .{65, &.{0}, &.{0, 1}});
|
||||||
try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, std.math.maxInt(Limb)}});
|
try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, maxInt(Limb)}});
|
||||||
try testOneShiftCase(.llshr, .{193, &.{0}, &.{std.math.maxInt(Limb), 1, std.math.maxInt(Limb)}});
|
try testOneShiftCase(.llshr, .{193, &.{0}, &.{maxInt(Limb), 1, maxInt(Limb)}});
|
||||||
try testOneShiftCase(.llshr, .{193, &.{0}, &.{0xdeadbeef, 0xabcdefab, 0x1234}});
|
try testOneShiftCase(.llshr, .{193, &.{0}, &.{0xdeadbeef, 0xabcdefab, 0x1234}});
|
||||||
// zig fmt: on
|
// zig fmt: on
|
||||||
}
|
}
|
||||||
@ -4475,7 +4611,7 @@ fn testOneShiftCase(comptime function: enum { llshr, llshl }, case: Case) !void
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case) !void {
|
fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case) !void {
|
||||||
const padding = std.math.maxInt(Limb);
|
const padding = maxInt(Limb);
|
||||||
var r: [20]Limb = @splat(padding);
|
var r: [20]Limb = @splat(padding);
|
||||||
|
|
||||||
const shift = case[0];
|
const shift = case[0];
|
||||||
@ -4492,7 +4628,7 @@ fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn testOneShiftCaseAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case, shift_direction: isize) !void {
|
fn testOneShiftCaseAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case, shift_direction: isize) !void {
|
||||||
const padding = std.math.maxInt(Limb);
|
const padding = maxInt(Limb);
|
||||||
var r: [60]Limb = @splat(padding);
|
var r: [60]Limb = @splat(padding);
|
||||||
const base = 20;
|
const base = 20;
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,12 @@ const minInt = std.math.minInt;
|
|||||||
// They will still run on larger than this and should pass, but the multi-limb code-paths
|
// They will still run on larger than this and should pass, but the multi-limb code-paths
|
||||||
// may be untested in some cases.
|
// may be untested in some cases.
|
||||||
|
|
||||||
|
fn expectNormalized(expected: comptime_int, actual: std.math.big.int.Const) !void {
|
||||||
|
try testing.expectEqual(expected >= 0, actual.positive);
|
||||||
|
try testing.expectEqual(std.math.big.int.calcLimbLen(expected), actual.limbs.len);
|
||||||
|
try testing.expect(actual.orderAgainstScalar(expected).compare(.eq));
|
||||||
|
}
|
||||||
|
|
||||||
test "comptime_int set" {
|
test "comptime_int set" {
|
||||||
comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab;
|
comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab;
|
||||||
var a = try Managed.initSet(testing.allocator, s);
|
var a = try Managed.initSet(testing.allocator, s);
|
||||||
@ -85,6 +91,408 @@ test "to target too small error" {
|
|||||||
try testing.expectError(error.TargetTooSmall, a.toInt(u8));
|
try testing.expectError(error.TargetTooSmall, a.toInt(u8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setFloat(comptime Float: type) !void {
|
||||||
|
var res_limbs: [std.math.big.int.calcNonZeroTwosCompLimbCount(11)]Limb = undefined;
|
||||||
|
var res: Mutable = .{
|
||||||
|
.limbs = &res_limbs,
|
||||||
|
.len = undefined,
|
||||||
|
.positive = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .nearest_even));
|
||||||
|
try expectNormalized(-1 << 10, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .away));
|
||||||
|
try expectNormalized(-1 << 10, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .trunc));
|
||||||
|
try expectNormalized(-1 << 10, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .floor));
|
||||||
|
try expectNormalized(-1 << 10, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .ceil));
|
||||||
|
try expectNormalized(-1 << 10, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .nearest_even));
|
||||||
|
try expectNormalized(-2, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .away));
|
||||||
|
try expectNormalized(-2, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .trunc));
|
||||||
|
try expectNormalized(-2, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .floor));
|
||||||
|
try expectNormalized(-2, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .ceil));
|
||||||
|
try expectNormalized(-2, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .nearest_even));
|
||||||
|
try expectNormalized(-2, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .away));
|
||||||
|
try expectNormalized(-2, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .trunc));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .floor));
|
||||||
|
try expectNormalized(-2, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .ceil));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .nearest_even));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .away));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .trunc));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .floor));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .ceil));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .nearest_even));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .away));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .trunc));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .floor));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .ceil));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .nearest_even));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .away));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .trunc));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .floor));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .ceil));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .nearest_even));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .away));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .trunc));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .floor));
|
||||||
|
try expectNormalized(-1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .ceil));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .nearest_even));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .away));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .trunc));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .floor));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .ceil));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .nearest_even));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .away));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .trunc));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .floor));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .ceil));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .nearest_even));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .away));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .trunc));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .floor));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .ceil));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .nearest_even));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .away));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .trunc));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .floor));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .ceil));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .nearest_even));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .away));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .trunc));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .floor));
|
||||||
|
try expectNormalized(0, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .ceil));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .nearest_even));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .away));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .trunc));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .floor));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .ceil));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .nearest_even));
|
||||||
|
try expectNormalized(2, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .away));
|
||||||
|
try expectNormalized(2, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .trunc));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .floor));
|
||||||
|
try expectNormalized(1, res.toConst());
|
||||||
|
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .ceil));
|
||||||
|
try expectNormalized(2, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .nearest_even));
|
||||||
|
try expectNormalized(2, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .away));
|
||||||
|
try expectNormalized(2, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .trunc));
|
||||||
|
try expectNormalized(2, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .floor));
|
||||||
|
try expectNormalized(2, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .ceil));
|
||||||
|
try expectNormalized(2, res.toConst());
|
||||||
|
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .nearest_even));
|
||||||
|
try expectNormalized(1 << 10, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .away));
|
||||||
|
try expectNormalized(1 << 10, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .trunc));
|
||||||
|
try expectNormalized(1 << 10, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .floor));
|
||||||
|
try expectNormalized(1 << 10, res.toConst());
|
||||||
|
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .ceil));
|
||||||
|
try expectNormalized(1 << 10, res.toConst());
|
||||||
|
}
|
||||||
|
test setFloat {
|
||||||
|
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
|
||||||
|
|
||||||
|
try setFloat(f16);
|
||||||
|
try setFloat(f32);
|
||||||
|
try setFloat(f64);
|
||||||
|
try setFloat(f80);
|
||||||
|
try setFloat(f128);
|
||||||
|
try setFloat(c_longdouble);
|
||||||
|
try setFloat(comptime_float);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toFloat(comptime Float: type) !void {
|
||||||
|
const Result = struct { Float, std.math.big.int.Exactness };
|
||||||
|
const fractional_bits = std.math.floatFractionalBits(Float);
|
||||||
|
|
||||||
|
var int_limbs: [
|
||||||
|
std.math.big.int.calcNonZeroTwosCompLimbCount(2 + fractional_bits)
|
||||||
|
]Limb = undefined;
|
||||||
|
var int: Mutable = .{
|
||||||
|
.limbs = &int_limbs,
|
||||||
|
.len = undefined,
|
||||||
|
.positive = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
int.set(-(1 << (fractional_bits + 1)) - 1);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
|
||||||
|
int.toFloat(Float, .nearest_even),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.nextAfter(
|
||||||
|
Float,
|
||||||
|
-std.math.ldexp(@as(Float, 1), fractional_bits + 1),
|
||||||
|
-std.math.inf(Float),
|
||||||
|
), .inexact },
|
||||||
|
int.toFloat(Float, .away),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
|
||||||
|
int.toFloat(Float, .trunc),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.nextAfter(
|
||||||
|
Float,
|
||||||
|
-std.math.ldexp(@as(Float, 1), fractional_bits + 1),
|
||||||
|
-std.math.inf(Float),
|
||||||
|
), .inexact },
|
||||||
|
int.toFloat(Float, .floor),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
|
||||||
|
int.toFloat(Float, .ceil),
|
||||||
|
);
|
||||||
|
|
||||||
|
int.set(-1 << (fractional_bits + 1));
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
|
||||||
|
int.toFloat(Float, .nearest_even),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
|
||||||
|
int.toFloat(Float, .away),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
|
||||||
|
int.toFloat(Float, .trunc),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
|
||||||
|
int.toFloat(Float, .floor),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
|
||||||
|
int.toFloat(Float, .ceil),
|
||||||
|
);
|
||||||
|
|
||||||
|
int.set(-(1 << (fractional_bits + 1)) + 1);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
|
||||||
|
int.toFloat(Float, .nearest_even),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
|
||||||
|
int.toFloat(Float, .away),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
|
||||||
|
int.toFloat(Float, .trunc),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
|
||||||
|
int.toFloat(Float, .floor),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
|
||||||
|
int.toFloat(Float, .ceil),
|
||||||
|
);
|
||||||
|
|
||||||
|
int.set(-1 << 10);
|
||||||
|
try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .nearest_even));
|
||||||
|
try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .away));
|
||||||
|
try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .trunc));
|
||||||
|
try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .floor));
|
||||||
|
try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .ceil));
|
||||||
|
|
||||||
|
int.set(-1);
|
||||||
|
try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .nearest_even));
|
||||||
|
try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .away));
|
||||||
|
try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .trunc));
|
||||||
|
try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .floor));
|
||||||
|
try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .ceil));
|
||||||
|
|
||||||
|
int.set(0);
|
||||||
|
try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .nearest_even));
|
||||||
|
try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .away));
|
||||||
|
try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .trunc));
|
||||||
|
try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .floor));
|
||||||
|
try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .ceil));
|
||||||
|
|
||||||
|
int.set(1);
|
||||||
|
try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .nearest_even));
|
||||||
|
try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .away));
|
||||||
|
try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .trunc));
|
||||||
|
try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .floor));
|
||||||
|
try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .ceil));
|
||||||
|
|
||||||
|
int.set(1 << 10);
|
||||||
|
try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .nearest_even));
|
||||||
|
try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .away));
|
||||||
|
try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .trunc));
|
||||||
|
try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .floor));
|
||||||
|
try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .ceil));
|
||||||
|
|
||||||
|
int.set((1 << (fractional_bits + 1)) - 1);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
|
||||||
|
int.toFloat(Float, .nearest_even),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
|
||||||
|
int.toFloat(Float, .away),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
|
||||||
|
int.toFloat(Float, .trunc),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
|
||||||
|
int.toFloat(Float, .floor),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
|
||||||
|
int.toFloat(Float, .ceil),
|
||||||
|
);
|
||||||
|
|
||||||
|
int.set(1 << (fractional_bits + 1));
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
|
||||||
|
int.toFloat(Float, .nearest_even),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
|
||||||
|
int.toFloat(Float, .away),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
|
||||||
|
int.toFloat(Float, .trunc),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
|
||||||
|
int.toFloat(Float, .floor),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
|
||||||
|
int.toFloat(Float, .ceil),
|
||||||
|
);
|
||||||
|
|
||||||
|
int.set((1 << (fractional_bits + 1)) + 1);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
|
||||||
|
int.toFloat(Float, .nearest_even),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.nextAfter(
|
||||||
|
Float,
|
||||||
|
std.math.ldexp(@as(Float, 1), fractional_bits + 1),
|
||||||
|
std.math.inf(Float),
|
||||||
|
), .inexact },
|
||||||
|
int.toFloat(Float, .away),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
|
||||||
|
int.toFloat(Float, .trunc),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
|
||||||
|
int.toFloat(Float, .floor),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
Result{ comptime std.math.nextAfter(
|
||||||
|
Float,
|
||||||
|
std.math.ldexp(@as(Float, 1), fractional_bits + 1),
|
||||||
|
std.math.inf(Float),
|
||||||
|
), .inexact },
|
||||||
|
int.toFloat(Float, .ceil),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
test toFloat {
|
||||||
|
if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24191
|
||||||
|
try toFloat(f16);
|
||||||
|
try toFloat(f32);
|
||||||
|
try toFloat(f64);
|
||||||
|
try toFloat(f80);
|
||||||
|
try toFloat(f128);
|
||||||
|
try toFloat(c_longdouble);
|
||||||
|
}
|
||||||
|
|
||||||
test "normalize" {
|
test "normalize" {
|
||||||
var a = try Managed.init(testing.allocator);
|
var a = try Managed.init(testing.allocator);
|
||||||
defer a.deinit();
|
defer a.deinit();
|
||||||
|
|||||||
@ -1,820 +0,0 @@
|
|||||||
const std = @import("../../std.zig");
|
|
||||||
const builtin = @import("builtin");
|
|
||||||
const debug = std.debug;
|
|
||||||
const math = std.math;
|
|
||||||
const mem = std.mem;
|
|
||||||
const testing = std.testing;
|
|
||||||
const Allocator = mem.Allocator;
|
|
||||||
|
|
||||||
const Limb = std.math.big.Limb;
|
|
||||||
const DoubleLimb = std.math.big.DoubleLimb;
|
|
||||||
const Int = std.math.big.int.Managed;
|
|
||||||
const IntConst = std.math.big.int.Const;
|
|
||||||
|
|
||||||
/// An arbitrary-precision rational number.
|
|
||||||
///
|
|
||||||
/// Memory is allocated as needed for operations to ensure full precision is kept. The precision
|
|
||||||
/// of a Rational is only bounded by memory.
|
|
||||||
///
|
|
||||||
/// Rational's are always normalized. That is, for a Rational r = p/q where p and q are integers,
|
|
||||||
/// gcd(p, q) = 1 always.
|
|
||||||
///
|
|
||||||
/// TODO rework this to store its own allocator and use a non-managed big int, to avoid double
|
|
||||||
/// allocator storage.
|
|
||||||
pub const Rational = struct {
|
|
||||||
/// Numerator. Determines the sign of the Rational.
|
|
||||||
p: Int,
|
|
||||||
|
|
||||||
/// Denominator. Sign is ignored.
|
|
||||||
q: Int,
|
|
||||||
|
|
||||||
/// Create a new Rational. A small amount of memory will be allocated on initialization.
|
|
||||||
/// This will be 2 * Int.default_capacity.
|
|
||||||
pub fn init(a: Allocator) !Rational {
|
|
||||||
var p = try Int.init(a);
|
|
||||||
errdefer p.deinit();
|
|
||||||
return Rational{
|
|
||||||
.p = p,
|
|
||||||
.q = try Int.initSet(a, 1),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Frees all memory associated with a Rational.
|
|
||||||
pub fn deinit(self: *Rational) void {
|
|
||||||
self.p.deinit();
|
|
||||||
self.q.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a Rational from a primitive integer type.
|
|
||||||
pub fn setInt(self: *Rational, a: anytype) !void {
|
|
||||||
try self.p.set(a);
|
|
||||||
try self.q.set(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a Rational from a string of the form `A/B` where A and B are base-10 integers.
|
|
||||||
pub fn setFloatString(self: *Rational, str: []const u8) !void {
|
|
||||||
// TODO: Accept a/b fractions and exponent form
|
|
||||||
if (str.len == 0) {
|
|
||||||
return error.InvalidFloatString;
|
|
||||||
}
|
|
||||||
|
|
||||||
const State = enum {
|
|
||||||
Integer,
|
|
||||||
Fractional,
|
|
||||||
};
|
|
||||||
|
|
||||||
var state = State.Integer;
|
|
||||||
var point: ?usize = null;
|
|
||||||
|
|
||||||
var start: usize = 0;
|
|
||||||
if (str[0] == '-') {
|
|
||||||
start += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (str, 0..) |c, i| {
|
|
||||||
switch (state) {
|
|
||||||
State.Integer => {
|
|
||||||
switch (c) {
|
|
||||||
'.' => {
|
|
||||||
state = State.Fractional;
|
|
||||||
point = i;
|
|
||||||
},
|
|
||||||
'0'...'9' => {
|
|
||||||
// okay
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
return error.InvalidFloatString;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State.Fractional => {
|
|
||||||
switch (c) {
|
|
||||||
'0'...'9' => {
|
|
||||||
// okay
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
return error.InvalidFloatString;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: batch the multiplies by 10
|
|
||||||
if (point) |i| {
|
|
||||||
try self.p.setString(10, str[0..i]);
|
|
||||||
|
|
||||||
const base = IntConst{ .limbs = &[_]Limb{10}, .positive = true };
|
|
||||||
var local_buf: [@sizeOf(Limb) * Int.default_capacity]u8 align(@alignOf(Limb)) = undefined;
|
|
||||||
var fba = std.heap.FixedBufferAllocator.init(&local_buf);
|
|
||||||
const base_managed = try base.toManaged(fba.allocator());
|
|
||||||
|
|
||||||
var j: usize = start;
|
|
||||||
while (j < str.len - i - 1) : (j += 1) {
|
|
||||||
try self.p.ensureMulCapacity(self.p.toConst(), base);
|
|
||||||
try self.p.mul(&self.p, &base_managed);
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.q.setString(10, str[i + 1 ..]);
|
|
||||||
try self.p.add(&self.p, &self.q);
|
|
||||||
|
|
||||||
try self.q.set(1);
|
|
||||||
var k: usize = i + 1;
|
|
||||||
while (k < str.len) : (k += 1) {
|
|
||||||
try self.q.mul(&self.q, &base_managed);
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.reduce();
|
|
||||||
} else {
|
|
||||||
try self.p.setString(10, str[0..]);
|
|
||||||
try self.q.set(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a Rational from a floating-point value. The rational will have enough precision to
|
|
||||||
/// completely represent the provided float.
|
|
||||||
pub fn setFloat(self: *Rational, comptime T: type, f: T) !void {
|
|
||||||
// Translated from golang.go/src/math/big/rat.go.
|
|
||||||
debug.assert(@typeInfo(T) == .float);
|
|
||||||
|
|
||||||
const UnsignedInt = std.meta.Int(.unsigned, @typeInfo(T).float.bits);
|
|
||||||
const f_bits = @as(UnsignedInt, @bitCast(f));
|
|
||||||
|
|
||||||
const exponent_bits = math.floatExponentBits(T);
|
|
||||||
const exponent_bias = (1 << (exponent_bits - 1)) - 1;
|
|
||||||
const mantissa_bits = math.floatMantissaBits(T);
|
|
||||||
|
|
||||||
const exponent_mask = (1 << exponent_bits) - 1;
|
|
||||||
const mantissa_mask = (1 << mantissa_bits) - 1;
|
|
||||||
|
|
||||||
var exponent = @as(i16, @intCast((f_bits >> mantissa_bits) & exponent_mask));
|
|
||||||
var mantissa = f_bits & mantissa_mask;
|
|
||||||
|
|
||||||
switch (exponent) {
|
|
||||||
exponent_mask => {
|
|
||||||
return error.NonFiniteFloat;
|
|
||||||
},
|
|
||||||
0 => {
|
|
||||||
// denormal
|
|
||||||
exponent -= exponent_bias - 1;
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
// normal
|
|
||||||
mantissa |= 1 << mantissa_bits;
|
|
||||||
exponent -= exponent_bias;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var shift: i16 = mantissa_bits - exponent;
|
|
||||||
|
|
||||||
// factor out powers of two early from rational
|
|
||||||
while (mantissa & 1 == 0 and shift > 0) {
|
|
||||||
mantissa >>= 1;
|
|
||||||
shift -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.p.set(mantissa);
|
|
||||||
self.p.setSign(f >= 0);
|
|
||||||
|
|
||||||
try self.q.set(1);
|
|
||||||
if (shift >= 0) {
|
|
||||||
try self.q.shiftLeft(&self.q, @as(usize, @intCast(shift)));
|
|
||||||
} else {
|
|
||||||
try self.p.shiftLeft(&self.p, @as(usize, @intCast(-shift)));
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.reduce();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a floating-point value that is the closest value to a Rational.
|
|
||||||
///
|
|
||||||
/// The result may not be exact if the Rational is too precise or too large for the
|
|
||||||
/// target type.
|
|
||||||
pub fn toFloat(self: Rational, comptime T: type) !T {
|
|
||||||
// Translated from golang.go/src/math/big/rat.go.
|
|
||||||
// TODO: Indicate whether the result is not exact.
|
|
||||||
debug.assert(@typeInfo(T) == .float);
|
|
||||||
|
|
||||||
const fsize = @typeInfo(T).float.bits;
|
|
||||||
const BitReprType = std.meta.Int(.unsigned, fsize);
|
|
||||||
|
|
||||||
const msize = math.floatMantissaBits(T);
|
|
||||||
const msize1 = msize + 1;
|
|
||||||
const msize2 = msize1 + 1;
|
|
||||||
|
|
||||||
const esize = math.floatExponentBits(T);
|
|
||||||
const ebias = (1 << (esize - 1)) - 1;
|
|
||||||
const emin = 1 - ebias;
|
|
||||||
|
|
||||||
if (self.p.eqlZero()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. left-shift a or sub so that a/b is in [1 << msize1, 1 << (msize2 + 1)]
|
|
||||||
var exp = @as(isize, @intCast(self.p.bitCountTwosComp())) - @as(isize, @intCast(self.q.bitCountTwosComp()));
|
|
||||||
|
|
||||||
var a2 = try self.p.clone();
|
|
||||||
defer a2.deinit();
|
|
||||||
|
|
||||||
var b2 = try self.q.clone();
|
|
||||||
defer b2.deinit();
|
|
||||||
|
|
||||||
const shift = msize2 - exp;
|
|
||||||
if (shift >= 0) {
|
|
||||||
try a2.shiftLeft(&a2, @as(usize, @intCast(shift)));
|
|
||||||
} else {
|
|
||||||
try b2.shiftLeft(&b2, @as(usize, @intCast(-shift)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. compute quotient and remainder
|
|
||||||
var q = try Int.init(self.p.allocator);
|
|
||||||
defer q.deinit();
|
|
||||||
|
|
||||||
// unused
|
|
||||||
var r = try Int.init(self.p.allocator);
|
|
||||||
defer r.deinit();
|
|
||||||
|
|
||||||
try Int.divTrunc(&q, &r, &a2, &b2);
|
|
||||||
|
|
||||||
var mantissa = extractLowBits(q, BitReprType);
|
|
||||||
var have_rem = r.len() > 0;
|
|
||||||
|
|
||||||
// 3. q didn't fit in msize2 bits, redo division b2 << 1
|
|
||||||
if (mantissa >> msize2 == 1) {
|
|
||||||
if (mantissa & 1 == 1) {
|
|
||||||
have_rem = true;
|
|
||||||
}
|
|
||||||
mantissa >>= 1;
|
|
||||||
exp += 1;
|
|
||||||
}
|
|
||||||
if (mantissa >> msize1 != 1) {
|
|
||||||
// NOTE: This can be hit if the limb size is small (u8/16).
|
|
||||||
@panic("unexpected bits in result");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Rounding
|
|
||||||
if (emin - msize <= exp and exp <= emin) {
|
|
||||||
// denormal
|
|
||||||
const shift1 = @as(math.Log2Int(BitReprType), @intCast(emin - (exp - 1)));
|
|
||||||
const lost_bits = mantissa & ((@as(BitReprType, @intCast(1)) << shift1) - 1);
|
|
||||||
have_rem = have_rem or lost_bits != 0;
|
|
||||||
mantissa >>= shift1;
|
|
||||||
exp = 2 - ebias;
|
|
||||||
}
|
|
||||||
|
|
||||||
// round q using round-half-to-even
|
|
||||||
var exact = !have_rem;
|
|
||||||
if (mantissa & 1 != 0) {
|
|
||||||
exact = false;
|
|
||||||
if (have_rem or (mantissa & 2 != 0)) {
|
|
||||||
mantissa += 1;
|
|
||||||
if (mantissa >= 1 << msize2) {
|
|
||||||
// 11...1 => 100...0
|
|
||||||
mantissa >>= 1;
|
|
||||||
exp += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mantissa >>= 1;
|
|
||||||
|
|
||||||
const f = math.scalbn(@as(T, @floatFromInt(mantissa)), @as(i32, @intCast(exp - msize1)));
|
|
||||||
if (math.isInf(f)) {
|
|
||||||
exact = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (self.p.isPositive()) f else -f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a rational from an integer ratio.
|
|
||||||
pub fn setRatio(self: *Rational, p: anytype, q: anytype) !void {
|
|
||||||
try self.p.set(p);
|
|
||||||
try self.q.set(q);
|
|
||||||
|
|
||||||
self.p.setSign(@intFromBool(self.p.isPositive()) ^ @intFromBool(self.q.isPositive()) == 0);
|
|
||||||
self.q.setSign(true);
|
|
||||||
|
|
||||||
try self.reduce();
|
|
||||||
|
|
||||||
if (self.q.eqlZero()) {
|
|
||||||
@panic("cannot set rational with denominator = 0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a Rational directly from an Int.
|
|
||||||
pub fn copyInt(self: *Rational, a: Int) !void {
|
|
||||||
try self.p.copy(a.toConst());
|
|
||||||
try self.q.set(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a Rational directly from a ratio of two Int's.
|
|
||||||
pub fn copyRatio(self: *Rational, a: Int, b: Int) !void {
|
|
||||||
try self.p.copy(a.toConst());
|
|
||||||
try self.q.copy(b.toConst());
|
|
||||||
|
|
||||||
self.p.setSign(@intFromBool(self.p.isPositive()) ^ @intFromBool(self.q.isPositive()) == 0);
|
|
||||||
self.q.setSign(true);
|
|
||||||
|
|
||||||
try self.reduce();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make a Rational positive.
|
|
||||||
pub fn abs(r: *Rational) void {
|
|
||||||
r.p.abs();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Negate the sign of a Rational.
|
|
||||||
pub fn negate(r: *Rational) void {
|
|
||||||
r.p.negate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Efficiently swap a Rational with another. This swaps the limb pointers and a full copy is not
|
|
||||||
/// performed. The address of the limbs field will not be the same after this function.
|
|
||||||
pub fn swap(r: *Rational, other: *Rational) void {
|
|
||||||
r.p.swap(&other.p);
|
|
||||||
r.q.swap(&other.q);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns math.Order.lt, math.Order.eq, math.Order.gt if a < b, a == b or
|
|
||||||
/// a > b respectively.
|
|
||||||
pub fn order(a: Rational, b: Rational) !math.Order {
|
|
||||||
return cmpInternal(a, b, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns math.Order.lt, math.Order.eq, math.Order.gt if |a| < |b|, |a| ==
|
|
||||||
/// |b| or |a| > |b| respectively.
|
|
||||||
pub fn orderAbs(a: Rational, b: Rational) !math.Order {
|
|
||||||
return cmpInternal(a, b, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// p/q > x/y iff p*y > x*q
|
|
||||||
fn cmpInternal(a: Rational, b: Rational, is_abs: bool) !math.Order {
|
|
||||||
// TODO: Would a div compare algorithm of sorts be viable and quicker? Can we avoid
|
|
||||||
// the memory allocations here?
|
|
||||||
var q = try Int.init(a.p.allocator);
|
|
||||||
defer q.deinit();
|
|
||||||
|
|
||||||
var p = try Int.init(b.p.allocator);
|
|
||||||
defer p.deinit();
|
|
||||||
|
|
||||||
try q.mul(&a.p, &b.q);
|
|
||||||
try p.mul(&b.p, &a.q);
|
|
||||||
|
|
||||||
return if (is_abs) q.orderAbs(p) else q.order(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// rma = a + b.
|
|
||||||
///
|
|
||||||
/// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
|
|
||||||
///
|
|
||||||
/// Returns an error if memory could not be allocated.
|
|
||||||
pub fn add(rma: *Rational, a: Rational, b: Rational) !void {
|
|
||||||
var r = rma;
|
|
||||||
var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr;
|
|
||||||
|
|
||||||
var sr: Rational = undefined;
|
|
||||||
if (aliased) {
|
|
||||||
sr = try Rational.init(rma.p.allocator);
|
|
||||||
r = &sr;
|
|
||||||
aliased = true;
|
|
||||||
}
|
|
||||||
defer if (aliased) {
|
|
||||||
rma.swap(r);
|
|
||||||
r.deinit();
|
|
||||||
};
|
|
||||||
|
|
||||||
try r.p.mul(&a.p, &b.q);
|
|
||||||
try r.q.mul(&b.p, &a.q);
|
|
||||||
try r.p.add(&r.p, &r.q);
|
|
||||||
|
|
||||||
try r.q.mul(&a.q, &b.q);
|
|
||||||
try r.reduce();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// rma = a - b.
|
|
||||||
///
|
|
||||||
/// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
|
|
||||||
///
|
|
||||||
/// Returns an error if memory could not be allocated.
|
|
||||||
pub fn sub(rma: *Rational, a: Rational, b: Rational) !void {
|
|
||||||
var r = rma;
|
|
||||||
var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr;
|
|
||||||
|
|
||||||
var sr: Rational = undefined;
|
|
||||||
if (aliased) {
|
|
||||||
sr = try Rational.init(rma.p.allocator);
|
|
||||||
r = &sr;
|
|
||||||
aliased = true;
|
|
||||||
}
|
|
||||||
defer if (aliased) {
|
|
||||||
rma.swap(r);
|
|
||||||
r.deinit();
|
|
||||||
};
|
|
||||||
|
|
||||||
try r.p.mul(&a.p, &b.q);
|
|
||||||
try r.q.mul(&b.p, &a.q);
|
|
||||||
try r.p.sub(&r.p, &r.q);
|
|
||||||
|
|
||||||
try r.q.mul(&a.q, &b.q);
|
|
||||||
try r.reduce();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// rma = a * b.
|
|
||||||
///
|
|
||||||
/// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
|
|
||||||
///
|
|
||||||
/// Returns an error if memory could not be allocated.
|
|
||||||
pub fn mul(r: *Rational, a: Rational, b: Rational) !void {
|
|
||||||
try r.p.mul(&a.p, &b.p);
|
|
||||||
try r.q.mul(&a.q, &b.q);
|
|
||||||
try r.reduce();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// rma = a / b.
|
|
||||||
///
|
|
||||||
/// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
|
|
||||||
///
|
|
||||||
/// Returns an error if memory could not be allocated.
|
|
||||||
pub fn div(r: *Rational, a: Rational, b: Rational) !void {
|
|
||||||
if (b.p.eqlZero()) {
|
|
||||||
@panic("division by zero");
|
|
||||||
}
|
|
||||||
|
|
||||||
try r.p.mul(&a.p, &b.q);
|
|
||||||
try r.q.mul(&b.p, &a.q);
|
|
||||||
try r.reduce();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invert the numerator and denominator fields of a Rational. p/q => q/p.
|
|
||||||
pub fn invert(r: *Rational) void {
|
|
||||||
Int.swap(&r.p, &r.q);
|
|
||||||
}
|
|
||||||
|
|
||||||
// reduce r/q such that gcd(r, q) = 1
|
|
||||||
fn reduce(r: *Rational) !void {
|
|
||||||
var a = try Int.init(r.p.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
|
|
||||||
const sign = r.p.isPositive();
|
|
||||||
r.p.abs();
|
|
||||||
try a.gcd(&r.p, &r.q);
|
|
||||||
r.p.setSign(sign);
|
|
||||||
|
|
||||||
const one = IntConst{ .limbs = &[_]Limb{1}, .positive = true };
|
|
||||||
if (a.toConst().order(one) != .eq) {
|
|
||||||
var unused = try Int.init(r.p.allocator);
|
|
||||||
defer unused.deinit();
|
|
||||||
|
|
||||||
// TODO: divexact would be useful here
|
|
||||||
// TODO: don't copy r.q for div
|
|
||||||
try Int.divTrunc(&r.p, &unused, &r.p, &a);
|
|
||||||
try Int.divTrunc(&r.q, &unused, &r.q, &a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn extractLowBits(a: Int, comptime T: type) T {
|
|
||||||
debug.assert(@typeInfo(T) == .int);
|
|
||||||
|
|
||||||
const t_bits = @typeInfo(T).int.bits;
|
|
||||||
const limb_bits = @typeInfo(Limb).int.bits;
|
|
||||||
if (t_bits <= limb_bits) {
|
|
||||||
return @as(T, @truncate(a.limbs[0]));
|
|
||||||
} else {
|
|
||||||
var r: T = 0;
|
|
||||||
comptime var i: usize = 0;
|
|
||||||
|
|
||||||
// Remainder is always 0 since if t_bits >= limb_bits -> Limb | T and both
|
|
||||||
// are powers of two.
|
|
||||||
inline while (i < t_bits / limb_bits) : (i += 1) {
|
|
||||||
r |= math.shl(T, a.limbs[i], i * limb_bits);
|
|
||||||
}
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test extractLowBits {
|
|
||||||
var a = try Int.initSet(testing.allocator, 0x11112222333344441234567887654321);
|
|
||||||
defer a.deinit();
|
|
||||||
|
|
||||||
const a1 = extractLowBits(a, u8);
|
|
||||||
try testing.expect(a1 == 0x21);
|
|
||||||
|
|
||||||
const a2 = extractLowBits(a, u16);
|
|
||||||
try testing.expect(a2 == 0x4321);
|
|
||||||
|
|
||||||
const a3 = extractLowBits(a, u32);
|
|
||||||
try testing.expect(a3 == 0x87654321);
|
|
||||||
|
|
||||||
const a4 = extractLowBits(a, u64);
|
|
||||||
try testing.expect(a4 == 0x1234567887654321);
|
|
||||||
|
|
||||||
const a5 = extractLowBits(a, u128);
|
|
||||||
try testing.expect(a5 == 0x11112222333344441234567887654321);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "set" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
|
|
||||||
try a.setInt(5);
|
|
||||||
try testing.expect((try a.p.toInt(u32)) == 5);
|
|
||||||
try testing.expect((try a.q.toInt(u32)) == 1);
|
|
||||||
|
|
||||||
try a.setRatio(7, 3);
|
|
||||||
try testing.expect((try a.p.toInt(u32)) == 7);
|
|
||||||
try testing.expect((try a.q.toInt(u32)) == 3);
|
|
||||||
|
|
||||||
try a.setRatio(9, 3);
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == 3);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 1);
|
|
||||||
|
|
||||||
try a.setRatio(-9, 3);
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == -3);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 1);
|
|
||||||
|
|
||||||
try a.setRatio(9, -3);
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == -3);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 1);
|
|
||||||
|
|
||||||
try a.setRatio(-9, -3);
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == 3);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "setFloat" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
|
|
||||||
try a.setFloat(f64, 2.5);
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == 5);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 2);
|
|
||||||
|
|
||||||
try a.setFloat(f32, -2.5);
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == -5);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 2);
|
|
||||||
|
|
||||||
try a.setFloat(f32, 3.141593);
|
|
||||||
|
|
||||||
// = 3.14159297943115234375
|
|
||||||
try testing.expect((try a.p.toInt(u32)) == 3294199);
|
|
||||||
try testing.expect((try a.q.toInt(u32)) == 1048576);
|
|
||||||
|
|
||||||
try a.setFloat(f64, 72.141593120712409172417410926841290461290467124);
|
|
||||||
|
|
||||||
// = 72.1415931207124145885245525278151035308837890625
|
|
||||||
try testing.expect((try a.p.toInt(u128)) == 5076513310880537);
|
|
||||||
try testing.expect((try a.q.toInt(u128)) == 70368744177664);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "setFloatString" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
|
|
||||||
try a.setFloatString("72.14159312071241458852455252781510353");
|
|
||||||
|
|
||||||
// = 72.1415931207124145885245525278151035308837890625
|
|
||||||
try testing.expect((try a.p.toInt(u128)) == 7214159312071241458852455252781510353);
|
|
||||||
try testing.expect((try a.q.toInt(u128)) == 100000000000000000000000000000000000);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "toFloat" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
|
|
||||||
// = 3.14159297943115234375
|
|
||||||
try a.setRatio(3294199, 1048576);
|
|
||||||
try testing.expect((try a.toFloat(f64)) == 3.14159297943115234375);
|
|
||||||
|
|
||||||
// = 72.1415931207124145885245525278151035308837890625
|
|
||||||
try a.setRatio(5076513310880537, 70368744177664);
|
|
||||||
try testing.expect((try a.toFloat(f64)) == 72.141593120712409172417410926841290461290467124);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "set/to Float round-trip" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
var prng = std.Random.DefaultPrng.init(std.testing.random_seed);
|
|
||||||
const random = prng.random();
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < 512) : (i += 1) {
|
|
||||||
const r = random.float(f64);
|
|
||||||
try a.setFloat(f64, r);
|
|
||||||
try testing.expect((try a.toFloat(f64)) == r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "copy" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
|
|
||||||
var b = try Int.initSet(testing.allocator, 5);
|
|
||||||
defer b.deinit();
|
|
||||||
|
|
||||||
try a.copyInt(b);
|
|
||||||
try testing.expect((try a.p.toInt(u32)) == 5);
|
|
||||||
try testing.expect((try a.q.toInt(u32)) == 1);
|
|
||||||
|
|
||||||
var c = try Int.initSet(testing.allocator, 7);
|
|
||||||
defer c.deinit();
|
|
||||||
var d = try Int.initSet(testing.allocator, 3);
|
|
||||||
defer d.deinit();
|
|
||||||
|
|
||||||
try a.copyRatio(c, d);
|
|
||||||
try testing.expect((try a.p.toInt(u32)) == 7);
|
|
||||||
try testing.expect((try a.q.toInt(u32)) == 3);
|
|
||||||
|
|
||||||
var e = try Int.initSet(testing.allocator, 9);
|
|
||||||
defer e.deinit();
|
|
||||||
var f = try Int.initSet(testing.allocator, 3);
|
|
||||||
defer f.deinit();
|
|
||||||
|
|
||||||
try a.copyRatio(e, f);
|
|
||||||
try testing.expect((try a.p.toInt(u32)) == 3);
|
|
||||||
try testing.expect((try a.q.toInt(u32)) == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "negate" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
|
|
||||||
try a.setInt(-50);
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == -50);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 1);
|
|
||||||
|
|
||||||
a.negate();
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == 50);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 1);
|
|
||||||
|
|
||||||
a.negate();
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == -50);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "abs" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
|
|
||||||
try a.setInt(-50);
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == -50);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 1);
|
|
||||||
|
|
||||||
a.abs();
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == 50);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 1);
|
|
||||||
|
|
||||||
a.abs();
|
|
||||||
try testing.expect((try a.p.toInt(i32)) == 50);
|
|
||||||
try testing.expect((try a.q.toInt(i32)) == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "swap" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
var b = try Rational.init(testing.allocator);
|
|
||||||
defer b.deinit();
|
|
||||||
|
|
||||||
try a.setRatio(50, 23);
|
|
||||||
try b.setRatio(17, 3);
|
|
||||||
|
|
||||||
try testing.expect((try a.p.toInt(u32)) == 50);
|
|
||||||
try testing.expect((try a.q.toInt(u32)) == 23);
|
|
||||||
|
|
||||||
try testing.expect((try b.p.toInt(u32)) == 17);
|
|
||||||
try testing.expect((try b.q.toInt(u32)) == 3);
|
|
||||||
|
|
||||||
a.swap(&b);
|
|
||||||
|
|
||||||
try testing.expect((try a.p.toInt(u32)) == 17);
|
|
||||||
try testing.expect((try a.q.toInt(u32)) == 3);
|
|
||||||
|
|
||||||
try testing.expect((try b.p.toInt(u32)) == 50);
|
|
||||||
try testing.expect((try b.q.toInt(u32)) == 23);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "order" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
var b = try Rational.init(testing.allocator);
|
|
||||||
defer b.deinit();
|
|
||||||
|
|
||||||
try a.setRatio(500, 231);
|
|
||||||
try b.setRatio(18903, 8584);
|
|
||||||
try testing.expect((try a.order(b)) == .lt);
|
|
||||||
|
|
||||||
try a.setRatio(890, 10);
|
|
||||||
try b.setRatio(89, 1);
|
|
||||||
try testing.expect((try a.order(b)) == .eq);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "order/orderAbs with negative" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
var b = try Rational.init(testing.allocator);
|
|
||||||
defer b.deinit();
|
|
||||||
|
|
||||||
try a.setRatio(1, 1);
|
|
||||||
try b.setRatio(-2, 1);
|
|
||||||
try testing.expect((try a.order(b)) == .gt);
|
|
||||||
try testing.expect((try a.orderAbs(b)) == .lt);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "add single-limb" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
var b = try Rational.init(testing.allocator);
|
|
||||||
defer b.deinit();
|
|
||||||
|
|
||||||
try a.setRatio(500, 231);
|
|
||||||
try b.setRatio(18903, 8584);
|
|
||||||
try testing.expect((try a.order(b)) == .lt);
|
|
||||||
|
|
||||||
try a.setRatio(890, 10);
|
|
||||||
try b.setRatio(89, 1);
|
|
||||||
try testing.expect((try a.order(b)) == .eq);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "add" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
var b = try Rational.init(testing.allocator);
|
|
||||||
defer b.deinit();
|
|
||||||
var r = try Rational.init(testing.allocator);
|
|
||||||
defer r.deinit();
|
|
||||||
|
|
||||||
try a.setRatio(78923, 23341);
|
|
||||||
try b.setRatio(123097, 12441414);
|
|
||||||
try a.add(a, b);
|
|
||||||
|
|
||||||
try r.setRatio(984786924199, 290395044174);
|
|
||||||
try testing.expect((try a.order(r)) == .eq);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "sub" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
var b = try Rational.init(testing.allocator);
|
|
||||||
defer b.deinit();
|
|
||||||
var r = try Rational.init(testing.allocator);
|
|
||||||
defer r.deinit();
|
|
||||||
|
|
||||||
try a.setRatio(78923, 23341);
|
|
||||||
try b.setRatio(123097, 12441414);
|
|
||||||
try a.sub(a, b);
|
|
||||||
|
|
||||||
try r.setRatio(979040510045, 290395044174);
|
|
||||||
try testing.expect((try a.order(r)) == .eq);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "mul" {
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
var b = try Rational.init(testing.allocator);
|
|
||||||
defer b.deinit();
|
|
||||||
var r = try Rational.init(testing.allocator);
|
|
||||||
defer r.deinit();
|
|
||||||
|
|
||||||
try a.setRatio(78923, 23341);
|
|
||||||
try b.setRatio(123097, 12441414);
|
|
||||||
try a.mul(a, b);
|
|
||||||
|
|
||||||
try r.setRatio(571481443, 17082061422);
|
|
||||||
try testing.expect((try a.order(r)) == .eq);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "div" {
|
|
||||||
{
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
var b = try Rational.init(testing.allocator);
|
|
||||||
defer b.deinit();
|
|
||||||
var r = try Rational.init(testing.allocator);
|
|
||||||
defer r.deinit();
|
|
||||||
|
|
||||||
try a.setRatio(78923, 23341);
|
|
||||||
try b.setRatio(123097, 12441414);
|
|
||||||
try a.div(a, b);
|
|
||||||
|
|
||||||
try r.setRatio(75531824394, 221015929);
|
|
||||||
try testing.expect((try a.order(r)) == .eq);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var a = try Rational.init(testing.allocator);
|
|
||||||
defer a.deinit();
|
|
||||||
var r = try Rational.init(testing.allocator);
|
|
||||||
defer r.deinit();
|
|
||||||
|
|
||||||
try a.setRatio(78923, 23341);
|
|
||||||
a.invert();
|
|
||||||
|
|
||||||
try r.setRatio(23341, 78923);
|
|
||||||
try testing.expect((try a.order(r)) == .eq);
|
|
||||||
|
|
||||||
try a.setRatio(-78923, 23341);
|
|
||||||
a.invert();
|
|
||||||
|
|
||||||
try r.setRatio(-23341, 78923);
|
|
||||||
try testing.expect((try a.order(r)) == .eq);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,6 +4,119 @@ const assert = std.debug.assert;
|
|||||||
const expect = std.testing.expect;
|
const expect = std.testing.expect;
|
||||||
const expectEqual = std.testing.expectEqual;
|
const expectEqual = std.testing.expectEqual;
|
||||||
|
|
||||||
|
pub const Sign = enum(u1) { positive, negative };
|
||||||
|
|
||||||
|
pub fn FloatRepr(comptime Float: type) type {
|
||||||
|
const fractional_bits = floatFractionalBits(Float);
|
||||||
|
const exponent_bits = floatExponentBits(Float);
|
||||||
|
return packed struct {
|
||||||
|
const Repr = @This();
|
||||||
|
|
||||||
|
mantissa: StoredMantissa,
|
||||||
|
exponent: BiasedExponent,
|
||||||
|
sign: Sign,
|
||||||
|
|
||||||
|
pub const StoredMantissa = @Type(.{ .int = .{
|
||||||
|
.signedness = .unsigned,
|
||||||
|
.bits = floatMantissaBits(Float),
|
||||||
|
} });
|
||||||
|
pub const Mantissa = @Type(.{ .int = .{
|
||||||
|
.signedness = .unsigned,
|
||||||
|
.bits = 1 + fractional_bits,
|
||||||
|
} });
|
||||||
|
pub const Exponent = @Type(.{ .int = .{
|
||||||
|
.signedness = .signed,
|
||||||
|
.bits = exponent_bits,
|
||||||
|
} });
|
||||||
|
pub const BiasedExponent = enum(@Type(.{ .int = .{
|
||||||
|
.signedness = .unsigned,
|
||||||
|
.bits = exponent_bits,
|
||||||
|
} })) {
|
||||||
|
denormal = 0,
|
||||||
|
min_normal = 1,
|
||||||
|
zero = (1 << (exponent_bits - 1)) - 1,
|
||||||
|
max_normal = (1 << exponent_bits) - 2,
|
||||||
|
infinite = (1 << exponent_bits) - 1,
|
||||||
|
_,
|
||||||
|
|
||||||
|
pub const Int = @typeInfo(BiasedExponent).@"enum".tag_type;
|
||||||
|
|
||||||
|
pub fn unbias(biased: BiasedExponent) Exponent {
|
||||||
|
switch (biased) {
|
||||||
|
.denormal => unreachable,
|
||||||
|
else => return @bitCast(@intFromEnum(biased) -% @intFromEnum(BiasedExponent.zero)),
|
||||||
|
.infinite => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bias(unbiased: Exponent) BiasedExponent {
|
||||||
|
return @enumFromInt(@intFromEnum(BiasedExponent.zero) +% @as(Int, @bitCast(unbiased)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Normalized = struct {
|
||||||
|
fraction: Fraction,
|
||||||
|
exponent: Normalized.Exponent,
|
||||||
|
|
||||||
|
pub const Fraction = @Type(.{ .int = .{
|
||||||
|
.signedness = .unsigned,
|
||||||
|
.bits = fractional_bits,
|
||||||
|
} });
|
||||||
|
pub const Exponent = @Type(.{ .int = .{
|
||||||
|
.signedness = .signed,
|
||||||
|
.bits = 1 + exponent_bits,
|
||||||
|
} });
|
||||||
|
|
||||||
|
/// This currently truncates denormal values, which needs to be fixed before this can be used to
|
||||||
|
/// produce a rounded value.
|
||||||
|
pub fn reconstruct(normalized: Normalized, sign: Sign) Float {
|
||||||
|
if (normalized.exponent > BiasedExponent.max_normal.unbias()) return @bitCast(Repr{
|
||||||
|
.mantissa = 0,
|
||||||
|
.exponent = .infinite,
|
||||||
|
.sign = sign,
|
||||||
|
});
|
||||||
|
const mantissa = @as(Mantissa, 1 << fractional_bits) | normalized.fraction;
|
||||||
|
if (normalized.exponent < BiasedExponent.min_normal.unbias()) return @bitCast(Repr{
|
||||||
|
.mantissa = @truncate(std.math.shr(
|
||||||
|
Mantissa,
|
||||||
|
mantissa,
|
||||||
|
BiasedExponent.min_normal.unbias() - normalized.exponent,
|
||||||
|
)),
|
||||||
|
.exponent = .denormal,
|
||||||
|
.sign = sign,
|
||||||
|
});
|
||||||
|
return @bitCast(Repr{
|
||||||
|
.mantissa = @truncate(mantissa),
|
||||||
|
.exponent = .bias(@intCast(normalized.exponent)),
|
||||||
|
.sign = sign,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Classified = union(enum) { normalized: Normalized, infinity, nan, invalid };
|
||||||
|
fn classify(repr: Repr) Classified {
|
||||||
|
return switch (repr.exponent) {
|
||||||
|
.denormal => {
|
||||||
|
const mantissa: Mantissa = repr.mantissa;
|
||||||
|
const shift = @clz(mantissa);
|
||||||
|
return .{ .normalized = .{
|
||||||
|
.fraction = @truncate(mantissa << shift),
|
||||||
|
.exponent = @as(Normalized.Exponent, comptime BiasedExponent.min_normal.unbias()) - shift,
|
||||||
|
} };
|
||||||
|
},
|
||||||
|
else => if (repr.mantissa <= std.math.maxInt(Normalized.Fraction)) .{ .normalized = .{
|
||||||
|
.fraction = @intCast(repr.mantissa),
|
||||||
|
.exponent = repr.exponent.unbias(),
|
||||||
|
} } else .invalid,
|
||||||
|
.infinite => switch (repr.mantissa) {
|
||||||
|
0 => .infinity,
|
||||||
|
else => .nan,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic.
|
/// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic.
|
||||||
inline fn mantissaOne(comptime T: type) comptime_int {
|
inline fn mantissaOne(comptime T: type) comptime_int {
|
||||||
return if (@typeInfo(T).float.bits == 80) 1 << floatFractionalBits(T) else 0;
|
return if (@typeInfo(T).float.bits == 80) 1 << floatFractionalBits(T) else 0;
|
||||||
|
|||||||
@ -593,7 +593,7 @@ const Parser = struct {
|
|||||||
switch (node.get(self.zoir)) {
|
switch (node.get(self.zoir)) {
|
||||||
.int_literal => |int| switch (int) {
|
.int_literal => |int| switch (int) {
|
||||||
.small => |val| return @floatFromInt(val),
|
.small => |val| return @floatFromInt(val),
|
||||||
.big => |val| return val.toFloat(T),
|
.big => |val| return val.toFloat(T, .nearest_even)[0],
|
||||||
},
|
},
|
||||||
.float_literal => |val| return @floatCast(val),
|
.float_literal => |val| return @floatCast(val),
|
||||||
.pos_inf => return std.math.inf(T),
|
.pos_inf => return std.math.inf(T),
|
||||||
|
|||||||
@ -683,6 +683,10 @@ pub const Inst = struct {
|
|||||||
int_from_float,
|
int_from_float,
|
||||||
/// Same as `int_from_float` with optimized float mode.
|
/// Same as `int_from_float` with optimized float mode.
|
||||||
int_from_float_optimized,
|
int_from_float_optimized,
|
||||||
|
/// Same as `int_from_float`, but with a safety check that the operand is in bounds.
|
||||||
|
int_from_float_safe,
|
||||||
|
/// Same as `int_from_float_optimized`, but with a safety check that the operand is in bounds.
|
||||||
|
int_from_float_optimized_safe,
|
||||||
/// Given an integer operand, return the float with the closest mathematical meaning.
|
/// Given an integer operand, return the float with the closest mathematical meaning.
|
||||||
/// Uses the `ty_op` field.
|
/// Uses the `ty_op` field.
|
||||||
float_from_int,
|
float_from_int,
|
||||||
@ -1612,6 +1616,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
|
|||||||
.array_to_slice,
|
.array_to_slice,
|
||||||
.int_from_float,
|
.int_from_float,
|
||||||
.int_from_float_optimized,
|
.int_from_float_optimized,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
.float_from_int,
|
.float_from_int,
|
||||||
.splat,
|
.splat,
|
||||||
.get_union_tag,
|
.get_union_tag,
|
||||||
@ -1842,6 +1848,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
|
|||||||
.sub_safe,
|
.sub_safe,
|
||||||
.mul_safe,
|
.mul_safe,
|
||||||
.intcast_safe,
|
.intcast_safe,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
=> true,
|
=> true,
|
||||||
|
|
||||||
.add,
|
.add,
|
||||||
|
|||||||
@ -1,7 +1,36 @@
|
|||||||
pt: Zcu.PerThread,
|
pt: Zcu.PerThread,
|
||||||
air_instructions: std.MultiArrayList(Air.Inst),
|
air_instructions: std.MultiArrayList(Air.Inst),
|
||||||
air_extra: std.ArrayListUnmanaged(u32),
|
air_extra: std.ArrayListUnmanaged(u32),
|
||||||
|
features: if (switch (dev.env) {
|
||||||
|
.bootstrap => @import("../codegen/c.zig").legalizeFeatures(undefined),
|
||||||
|
else => null,
|
||||||
|
}) |bootstrap_features| struct {
|
||||||
|
fn init(features: *const Features) @This() {
|
||||||
|
assert(features.eql(bootstrap_features.*));
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
/// `inline` to propagate comptime-known result.
|
||||||
|
inline fn has(_: @This(), comptime feature: Feature) bool {
|
||||||
|
return comptime bootstrap_features.contains(feature);
|
||||||
|
}
|
||||||
|
/// `inline` to propagate comptime-known result.
|
||||||
|
fn hasAny(_: @This(), comptime features: []const Feature) bool {
|
||||||
|
return comptime !bootstrap_features.intersectWith(.initMany(features)).eql(.initEmpty());
|
||||||
|
}
|
||||||
|
} else struct {
|
||||||
features: *const Features,
|
features: *const Features,
|
||||||
|
/// `inline` to propagate whether `dev.check` returns.
|
||||||
|
inline fn init(features: *const Features) @This() {
|
||||||
|
dev.check(.legalize);
|
||||||
|
return .{ .features = features };
|
||||||
|
}
|
||||||
|
fn has(rt: @This(), comptime feature: Feature) bool {
|
||||||
|
return rt.features.contains(feature);
|
||||||
|
}
|
||||||
|
fn hasAny(rt: @This(), comptime features: []const Feature) bool {
|
||||||
|
return !rt.features.intersectWith(comptime .initMany(features)).eql(comptime .initEmpty());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
pub const Feature = enum {
|
pub const Feature = enum {
|
||||||
scalarize_add,
|
scalarize_add,
|
||||||
@ -83,6 +112,8 @@ pub const Feature = enum {
|
|||||||
scalarize_trunc,
|
scalarize_trunc,
|
||||||
scalarize_int_from_float,
|
scalarize_int_from_float,
|
||||||
scalarize_int_from_float_optimized,
|
scalarize_int_from_float_optimized,
|
||||||
|
scalarize_int_from_float_safe,
|
||||||
|
scalarize_int_from_float_optimized_safe,
|
||||||
scalarize_float_from_int,
|
scalarize_float_from_int,
|
||||||
scalarize_shuffle_one,
|
scalarize_shuffle_one,
|
||||||
scalarize_shuffle_two,
|
scalarize_shuffle_two,
|
||||||
@ -97,6 +128,12 @@ pub const Feature = enum {
|
|||||||
/// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure.
|
/// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure.
|
||||||
/// Not compatible with `scalarize_intcast_safe`.
|
/// Not compatible with `scalarize_intcast_safe`.
|
||||||
expand_intcast_safe,
|
expand_intcast_safe,
|
||||||
|
/// Replace `int_from_float_safe` with an explicit safety check which `call`s the panic function on failure.
|
||||||
|
/// Not compatible with `scalarize_int_from_float_safe`.
|
||||||
|
expand_int_from_float_safe,
|
||||||
|
/// Replace `int_from_float_optimized_safe` with an explicit safety check which `call`s the panic function on failure.
|
||||||
|
/// Not compatible with `scalarize_int_from_float_optimized_safe`.
|
||||||
|
expand_int_from_float_optimized_safe,
|
||||||
/// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure.
|
/// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure.
|
||||||
/// Not compatible with `scalarize_add_safe`.
|
/// Not compatible with `scalarize_add_safe`.
|
||||||
expand_add_safe,
|
expand_add_safe,
|
||||||
@ -196,10 +233,12 @@ pub const Feature = enum {
|
|||||||
.trunc => .scalarize_trunc,
|
.trunc => .scalarize_trunc,
|
||||||
.int_from_float => .scalarize_int_from_float,
|
.int_from_float => .scalarize_int_from_float,
|
||||||
.int_from_float_optimized => .scalarize_int_from_float_optimized,
|
.int_from_float_optimized => .scalarize_int_from_float_optimized,
|
||||||
|
.int_from_float_safe => .scalarize_int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe => .scalarize_int_from_float_optimized_safe,
|
||||||
.float_from_int => .scalarize_float_from_int,
|
.float_from_int => .scalarize_float_from_int,
|
||||||
.shuffle_one => .scalarize_shuffle_one,
|
.shuffle_one => .scalarize_shuffle_one,
|
||||||
.shuffle_two => .scalarize_shuffle_two,
|
.shuffle_two => .scalarize_shuffle_two,
|
||||||
.select => .scalarize_selects,
|
.select => .scalarize_select,
|
||||||
.mul_add => .scalarize_mul_add,
|
.mul_add => .scalarize_mul_add,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -210,13 +249,12 @@ pub const Features = std.enums.EnumSet(Feature);
|
|||||||
pub const Error = std.mem.Allocator.Error;
|
pub const Error = std.mem.Allocator.Error;
|
||||||
|
|
||||||
pub fn legalize(air: *Air, pt: Zcu.PerThread, features: *const Features) Error!void {
|
pub fn legalize(air: *Air, pt: Zcu.PerThread, features: *const Features) Error!void {
|
||||||
dev.check(.legalize);
|
|
||||||
assert(!features.eql(comptime .initEmpty())); // backend asked to run legalize, but no features were enabled
|
assert(!features.eql(comptime .initEmpty())); // backend asked to run legalize, but no features were enabled
|
||||||
var l: Legalize = .{
|
var l: Legalize = .{
|
||||||
.pt = pt,
|
.pt = pt,
|
||||||
.air_instructions = air.instructions.toMultiArrayList(),
|
.air_instructions = air.instructions.toMultiArrayList(),
|
||||||
.air_extra = air.extra,
|
.air_extra = air.extra,
|
||||||
.features = features,
|
.features = .init(features),
|
||||||
};
|
};
|
||||||
defer air.* = l.getTmpAir();
|
defer air.* = l.getTmpAir();
|
||||||
const main_extra = l.extraData(Air.Block, l.air_extra.items[@intFromEnum(Air.ExtraIndex.main_block)]);
|
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 {
|
|||||||
.bit_and,
|
.bit_and,
|
||||||
.bit_or,
|
.bit_or,
|
||||||
.xor,
|
.xor,
|
||||||
=> |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {
|
=> |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
|
||||||
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
||||||
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
|
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
|
||||||
},
|
},
|
||||||
.add_safe => if (l.features.contains(.expand_add_safe)) {
|
.add_safe => if (l.features.has(.expand_add_safe)) {
|
||||||
assert(!l.features.contains(.scalarize_add_safe)); // it doesn't make sense to do both
|
assert(!l.features.has(.scalarize_add_safe)); // it doesn't make sense to do both
|
||||||
continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .add_with_overflow));
|
continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .add_with_overflow));
|
||||||
} else if (l.features.contains(.scalarize_add_safe)) {
|
} else if (l.features.has(.scalarize_add_safe)) {
|
||||||
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
||||||
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
|
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
|
||||||
},
|
},
|
||||||
.sub_safe => if (l.features.contains(.expand_sub_safe)) {
|
.sub_safe => if (l.features.has(.expand_sub_safe)) {
|
||||||
assert(!l.features.contains(.scalarize_sub_safe)); // it doesn't make sense to do both
|
assert(!l.features.has(.scalarize_sub_safe)); // it doesn't make sense to do both
|
||||||
continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .sub_with_overflow));
|
continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .sub_with_overflow));
|
||||||
} else if (l.features.contains(.scalarize_sub_safe)) {
|
} else if (l.features.has(.scalarize_sub_safe)) {
|
||||||
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
||||||
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
|
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
|
||||||
},
|
},
|
||||||
.mul_safe => if (l.features.contains(.expand_mul_safe)) {
|
.mul_safe => if (l.features.has(.expand_mul_safe)) {
|
||||||
assert(!l.features.contains(.scalarize_mul_safe)); // it doesn't make sense to do both
|
assert(!l.features.has(.scalarize_mul_safe)); // it doesn't make sense to do both
|
||||||
continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .mul_with_overflow));
|
continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .mul_with_overflow));
|
||||||
} else if (l.features.contains(.scalarize_mul_safe)) {
|
} else if (l.features.has(.scalarize_mul_safe)) {
|
||||||
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
||||||
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
|
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
|
||||||
},
|
},
|
||||||
@ -308,7 +346,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
.sub_with_overflow,
|
.sub_with_overflow,
|
||||||
.mul_with_overflow,
|
.mul_with_overflow,
|
||||||
.shl_with_overflow,
|
.shl_with_overflow,
|
||||||
=> |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {
|
=> |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
|
||||||
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
|
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
|
||||||
if (ty_pl.ty.toType().fieldType(0, zcu).isVector(zcu)) continue :inst l.replaceInst(inst, .block, try l.scalarizeOverflowBlockPayload(inst));
|
if (ty_pl.ty.toType().fieldType(0, zcu).isVector(zcu)) continue :inst l.replaceInst(inst, .block, try l.scalarizeOverflowBlockPayload(inst));
|
||||||
},
|
},
|
||||||
@ -320,13 +358,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
.shl,
|
.shl,
|
||||||
.shl_exact,
|
.shl_exact,
|
||||||
.shl_sat,
|
.shl_sat,
|
||||||
=> |air_tag| if (!l.features.intersectWith(comptime .initMany(&.{
|
=> |air_tag| if (l.features.hasAny(&.{
|
||||||
.unsplat_shift_rhs,
|
.unsplat_shift_rhs,
|
||||||
.scalarize(air_tag),
|
.scalarize(air_tag),
|
||||||
})).eql(comptime .initEmpty())) {
|
})) {
|
||||||
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
||||||
if (l.typeOf(bin_op.rhs).isVector(zcu)) {
|
if (l.typeOf(bin_op.rhs).isVector(zcu)) {
|
||||||
if (l.features.contains(.unsplat_shift_rhs)) {
|
if (l.features.has(.unsplat_shift_rhs)) {
|
||||||
if (bin_op.rhs.toInterned()) |rhs_ip_index| switch (ip.indexToKey(rhs_ip_index)) {
|
if (bin_op.rhs.toInterned()) |rhs_ip_index| switch (ip.indexToKey(rhs_ip_index)) {
|
||||||
else => {},
|
else => {},
|
||||||
.aggregate => |aggregate| switch (aggregate.storage) {
|
.aggregate => |aggregate| switch (aggregate.storage) {
|
||||||
@ -347,7 +385,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (l.features.contains(comptime .scalarize(air_tag))) continue :inst try l.scalarize(inst, .bin_op);
|
if (l.features.has(comptime .scalarize(air_tag))) continue :inst try l.scalarize(inst, .bin_op);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inline .not,
|
inline .not,
|
||||||
@ -364,11 +402,11 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
.int_from_float,
|
.int_from_float,
|
||||||
.int_from_float_optimized,
|
.int_from_float_optimized,
|
||||||
.float_from_int,
|
.float_from_int,
|
||||||
=> |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {
|
=> |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
|
||||||
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
|
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
|
||||||
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
|
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
|
||||||
},
|
},
|
||||||
.bitcast => if (l.features.contains(.scalarize_bitcast)) {
|
.bitcast => if (l.features.has(.scalarize_bitcast)) {
|
||||||
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
|
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
|
||||||
|
|
||||||
const to_ty = ty_op.ty.toType();
|
const to_ty = ty_op.ty.toType();
|
||||||
@ -404,10 +442,24 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
};
|
};
|
||||||
if (!from_ty_legal) continue :inst l.replaceInst(inst, .block, try l.scalarizeBitcastOperandBlockPayload(inst));
|
if (!from_ty_legal) continue :inst l.replaceInst(inst, .block, try l.scalarizeBitcastOperandBlockPayload(inst));
|
||||||
},
|
},
|
||||||
.intcast_safe => if (l.features.contains(.expand_intcast_safe)) {
|
.intcast_safe => if (l.features.has(.expand_intcast_safe)) {
|
||||||
assert(!l.features.contains(.scalarize_intcast_safe)); // it doesn't make sense to do both
|
assert(!l.features.has(.scalarize_intcast_safe)); // it doesn't make sense to do both
|
||||||
continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst));
|
continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst));
|
||||||
} else if (l.features.contains(.scalarize_intcast_safe)) {
|
} else if (l.features.has(.scalarize_intcast_safe)) {
|
||||||
|
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
|
||||||
|
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
|
||||||
|
},
|
||||||
|
.int_from_float_safe => if (l.features.has(.expand_int_from_float_safe)) {
|
||||||
|
assert(!l.features.has(.scalarize_int_from_float_safe));
|
||||||
|
continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, false));
|
||||||
|
} else if (l.features.has(.scalarize_int_from_float_safe)) {
|
||||||
|
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
|
||||||
|
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
|
||||||
|
},
|
||||||
|
.int_from_float_optimized_safe => if (l.features.has(.expand_int_from_float_optimized_safe)) {
|
||||||
|
assert(!l.features.has(.scalarize_int_from_float_optimized_safe));
|
||||||
|
continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, true));
|
||||||
|
} else if (l.features.has(.scalarize_int_from_float_optimized_safe)) {
|
||||||
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
|
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
|
||||||
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
|
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
|
||||||
},
|
},
|
||||||
@ -442,7 +494,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
.trunc_float,
|
.trunc_float,
|
||||||
.neg,
|
.neg,
|
||||||
.neg_optimized,
|
.neg_optimized,
|
||||||
=> |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {
|
=> |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
|
||||||
const un_op = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op;
|
const un_op = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op;
|
||||||
if (l.typeOf(un_op).isVector(zcu)) continue :inst try l.scalarize(inst, .un_op);
|
if (l.typeOf(un_op).isVector(zcu)) continue :inst try l.scalarize(inst, .un_op);
|
||||||
},
|
},
|
||||||
@ -459,7 +511,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
.cmp_neq,
|
.cmp_neq,
|
||||||
.cmp_neq_optimized,
|
.cmp_neq_optimized,
|
||||||
=> {},
|
=> {},
|
||||||
inline .cmp_vector, .cmp_vector_optimized => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) {
|
inline .cmp_vector, .cmp_vector_optimized => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
|
||||||
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
|
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
|
||||||
if (ty_pl.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .cmp_vector);
|
if (ty_pl.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .cmp_vector);
|
||||||
},
|
},
|
||||||
@ -513,13 +565,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
.bool_and,
|
.bool_and,
|
||||||
.bool_or,
|
.bool_or,
|
||||||
=> {},
|
=> {},
|
||||||
.load => if (l.features.contains(.expand_packed_load)) {
|
.load => if (l.features.has(.expand_packed_load)) {
|
||||||
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
|
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
|
||||||
const ptr_info = l.typeOf(ty_op.operand).ptrInfo(zcu);
|
const ptr_info = l.typeOf(ty_op.operand).ptrInfo(zcu);
|
||||||
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));
|
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));
|
||||||
},
|
},
|
||||||
.ret, .ret_safe, .ret_load => {},
|
.ret, .ret_safe, .ret_load => {},
|
||||||
.store, .store_safe => if (l.features.contains(.expand_packed_store)) {
|
.store, .store_safe => if (l.features.has(.expand_packed_store)) {
|
||||||
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
|
||||||
const ptr_info = l.typeOf(bin_op.lhs).ptrInfo(zcu);
|
const ptr_info = l.typeOf(bin_op.lhs).ptrInfo(zcu);
|
||||||
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));
|
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 {
|
|||||||
.struct_field_ptr_index_2,
|
.struct_field_ptr_index_2,
|
||||||
.struct_field_ptr_index_3,
|
.struct_field_ptr_index_3,
|
||||||
=> {},
|
=> {},
|
||||||
.struct_field_val => if (l.features.contains(.expand_packed_struct_field_val)) {
|
.struct_field_val => if (l.features.has(.expand_packed_struct_field_val)) {
|
||||||
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
|
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
|
||||||
const extra = l.extraData(Air.StructField, ty_pl.payload).data;
|
const extra = l.extraData(Air.StructField, ty_pl.payload).data;
|
||||||
switch (l.typeOf(extra.struct_operand).containerLayout(zcu)) {
|
switch (l.typeOf(extra.struct_operand).containerLayout(zcu)) {
|
||||||
@ -564,7 +616,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
.ptr_elem_ptr,
|
.ptr_elem_ptr,
|
||||||
.array_to_slice,
|
.array_to_slice,
|
||||||
=> {},
|
=> {},
|
||||||
.reduce, .reduce_optimized => if (l.features.contains(.reduce_one_elem_to_bitcast)) {
|
.reduce, .reduce_optimized => if (l.features.has(.reduce_one_elem_to_bitcast)) {
|
||||||
const reduce = l.air_instructions.items(.data)[@intFromEnum(inst)].reduce;
|
const reduce = l.air_instructions.items(.data)[@intFromEnum(inst)].reduce;
|
||||||
const vector_ty = l.typeOf(reduce.operand);
|
const vector_ty = l.typeOf(reduce.operand);
|
||||||
switch (vector_ty.vectorLen(zcu)) {
|
switch (vector_ty.vectorLen(zcu)) {
|
||||||
@ -577,9 +629,9 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.splat => {},
|
.splat => {},
|
||||||
.shuffle_one => if (l.features.contains(.scalarize_shuffle_one)) continue :inst try l.scalarize(inst, .shuffle_one),
|
.shuffle_one => if (l.features.has(.scalarize_shuffle_one)) continue :inst try l.scalarize(inst, .shuffle_one),
|
||||||
.shuffle_two => if (l.features.contains(.scalarize_shuffle_two)) continue :inst try l.scalarize(inst, .shuffle_two),
|
.shuffle_two => if (l.features.has(.scalarize_shuffle_two)) continue :inst try l.scalarize(inst, .shuffle_two),
|
||||||
.select => if (l.features.contains(.scalarize_select)) continue :inst try l.scalarize(inst, .select),
|
.select => if (l.features.has(.scalarize_select)) continue :inst try l.scalarize(inst, .select),
|
||||||
.memset,
|
.memset,
|
||||||
.memset_safe,
|
.memset_safe,
|
||||||
.memcpy,
|
.memcpy,
|
||||||
@ -597,7 +649,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
.error_name,
|
.error_name,
|
||||||
.error_set_has_value,
|
.error_set_has_value,
|
||||||
=> {},
|
=> {},
|
||||||
.aggregate_init => if (l.features.contains(.expand_packed_aggregate_init)) {
|
.aggregate_init => if (l.features.has(.expand_packed_aggregate_init)) {
|
||||||
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
|
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
|
||||||
const agg_ty = ty_pl.ty.toType();
|
const agg_ty = ty_pl.ty.toType();
|
||||||
switch (agg_ty.zigTypeTag(zcu)) {
|
switch (agg_ty.zigTypeTag(zcu)) {
|
||||||
@ -609,7 +661,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.union_init, .prefetch => {},
|
.union_init, .prefetch => {},
|
||||||
.mul_add => if (l.features.contains(.scalarize_mul_add)) {
|
.mul_add => if (l.features.has(.scalarize_mul_add)) {
|
||||||
const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op;
|
const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op;
|
||||||
if (l.typeOf(pl_op.operand).isVector(zcu)) continue :inst try l.scalarize(inst, .pl_op_bin);
|
if (l.typeOf(pl_op.operand).isVector(zcu)) continue :inst try l.scalarize(inst, .pl_op_bin);
|
||||||
},
|
},
|
||||||
@ -636,6 +688,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ScalarizeForm = enum { un_op, ty_op, bin_op, pl_op_bin, bitcast, cmp_vector, shuffle_one, shuffle_two, select };
|
const ScalarizeForm = enum { un_op, ty_op, bin_op, pl_op_bin, bitcast, cmp_vector, shuffle_one, shuffle_two, select };
|
||||||
|
/// inline to propagate comptime-known `replaceInst` result.
|
||||||
inline fn scalarize(l: *Legalize, orig_inst: Air.Inst.Index, comptime form: ScalarizeForm) Error!Air.Inst.Tag {
|
inline fn scalarize(l: *Legalize, orig_inst: Air.Inst.Index, comptime form: ScalarizeForm) Error!Air.Inst.Tag {
|
||||||
return l.replaceInst(orig_inst, .block, try l.scalarizeBlockPayload(orig_inst, form));
|
return l.replaceInst(orig_inst, .block, try l.scalarizeBlockPayload(orig_inst, form));
|
||||||
}
|
}
|
||||||
@ -1972,6 +2025,115 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
|
|||||||
.payload = try l.addBlockBody(main_block.body()),
|
.payload = try l.addBlockBody(main_block.body()),
|
||||||
} };
|
} };
|
||||||
}
|
}
|
||||||
|
fn safeIntFromFloatBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, optimized: bool) Error!Air.Inst.Data {
|
||||||
|
const pt = l.pt;
|
||||||
|
const zcu = pt.zcu;
|
||||||
|
const gpa = zcu.gpa;
|
||||||
|
const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op;
|
||||||
|
|
||||||
|
const operand_ref = ty_op.operand;
|
||||||
|
const operand_ty = l.typeOf(operand_ref);
|
||||||
|
const dest_ty = ty_op.ty.toType();
|
||||||
|
|
||||||
|
const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
|
||||||
|
const dest_scalar_ty = dest_ty.scalarType(zcu);
|
||||||
|
const int_info = dest_scalar_ty.intInfo(zcu);
|
||||||
|
|
||||||
|
// We emit 9 instructions in the worst case.
|
||||||
|
var inst_buf: [9]Air.Inst.Index = undefined;
|
||||||
|
try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len);
|
||||||
|
var main_block: Block = .init(&inst_buf);
|
||||||
|
|
||||||
|
// This check is a bit annoying because of floating-point rounding and the fact that this
|
||||||
|
// builtin truncates. We'll use a bigint for our calculations, because we need to construct
|
||||||
|
// integers exceeding the bounds of the result integer type, and we need to convert it to a
|
||||||
|
// float with a specific rounding mode to avoid errors.
|
||||||
|
// Our bigint may exceed the twos complement limit by one, so add an extra limb.
|
||||||
|
const limbs = try gpa.alloc(
|
||||||
|
std.math.big.Limb,
|
||||||
|
std.math.big.int.calcTwosCompLimbCount(int_info.bits) + 1,
|
||||||
|
);
|
||||||
|
defer gpa.free(limbs);
|
||||||
|
var big: std.math.big.int.Mutable = .init(limbs, 0);
|
||||||
|
|
||||||
|
// Check if the operand is lower than `min_int` when truncated to an integer.
|
||||||
|
big.setTwosCompIntLimit(.min, int_info.signedness, int_info.bits);
|
||||||
|
const below_min_inst: Air.Inst.Index = if (!big.positive or big.eqlZero()) bad: {
|
||||||
|
// `min_int <= 0`, so check for `x <= min_int - 1`.
|
||||||
|
big.addScalar(big.toConst(), -1);
|
||||||
|
// For `<=`, we must round the RHS down, so that this value is the first `x` which returns `true`.
|
||||||
|
const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .floor);
|
||||||
|
break :bad try main_block.addCmp(l, .lte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
|
||||||
|
.vector = is_vector,
|
||||||
|
.optimized = optimized,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// `min_int > 0`, which is currently impossible. It would become possible under #3806, in
|
||||||
|
// which case we must detect `x < min_int`.
|
||||||
|
unreachable;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the operand is greater than `max_int` when truncated to an integer.
|
||||||
|
big.setTwosCompIntLimit(.max, int_info.signedness, int_info.bits);
|
||||||
|
const above_max_inst: Air.Inst.Index = if (big.positive or big.eqlZero()) bad: {
|
||||||
|
// `max_int >= 0`, so check for `x >= max_int + 1`.
|
||||||
|
big.addScalar(big.toConst(), 1);
|
||||||
|
// For `>=`, we must round the RHS up, so that this value is the first `x` which returns `true`.
|
||||||
|
const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .ceil);
|
||||||
|
break :bad try main_block.addCmp(l, .gte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
|
||||||
|
.vector = is_vector,
|
||||||
|
.optimized = optimized,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// `max_int < 0`, which is currently impossible. It would become possible under #3806, in
|
||||||
|
// which case we must detect `x > max_int`.
|
||||||
|
unreachable;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combine the conditions.
|
||||||
|
const out_of_bounds_inst: Air.Inst.Index = main_block.add(l, .{
|
||||||
|
.tag = .bool_or,
|
||||||
|
.data = .{ .bin_op = .{
|
||||||
|
.lhs = below_min_inst.toRef(),
|
||||||
|
.rhs = above_max_inst.toRef(),
|
||||||
|
} },
|
||||||
|
});
|
||||||
|
const scalar_out_of_bounds_inst: Air.Inst.Index = if (is_vector) main_block.add(l, .{
|
||||||
|
.tag = .reduce,
|
||||||
|
.data = .{ .reduce = .{
|
||||||
|
.operand = out_of_bounds_inst.toRef(),
|
||||||
|
.operation = .Or,
|
||||||
|
} },
|
||||||
|
}) else out_of_bounds_inst;
|
||||||
|
|
||||||
|
// Now emit the actual condbr. "true" will be safety panic. "false" will be "ok", meaning we do
|
||||||
|
// the `int_from_float` and `br` the result to `orig_inst`.
|
||||||
|
var condbr: CondBr = .init(l, scalar_out_of_bounds_inst.toRef(), &main_block, .{ .true = .cold });
|
||||||
|
condbr.then_block = .init(main_block.stealRemainingCapacity());
|
||||||
|
try condbr.then_block.addPanic(l, .integer_part_out_of_bounds);
|
||||||
|
condbr.else_block = .init(condbr.then_block.stealRemainingCapacity());
|
||||||
|
const cast_inst = condbr.else_block.add(l, .{
|
||||||
|
.tag = if (optimized) .int_from_float_optimized else .int_from_float,
|
||||||
|
.data = .{ .ty_op = .{
|
||||||
|
.ty = Air.internedToRef(dest_ty.toIntern()),
|
||||||
|
.operand = operand_ref,
|
||||||
|
} },
|
||||||
|
});
|
||||||
|
_ = condbr.else_block.add(l, .{
|
||||||
|
.tag = .br,
|
||||||
|
.data = .{ .br = .{
|
||||||
|
.block_inst = orig_inst,
|
||||||
|
.operand = cast_inst.toRef(),
|
||||||
|
} },
|
||||||
|
});
|
||||||
|
_ = condbr.else_block.stealRemainingCapacity(); // we might not have used it all
|
||||||
|
try condbr.finish(l);
|
||||||
|
|
||||||
|
return .{ .ty_pl = .{
|
||||||
|
.ty = Air.internedToRef(dest_ty.toIntern()),
|
||||||
|
.payload = try l.addBlockBody(main_block.body()),
|
||||||
|
} };
|
||||||
|
}
|
||||||
fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data {
|
fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data {
|
||||||
const pt = l.pt;
|
const pt = l.pt;
|
||||||
const zcu = pt.zcu;
|
const zcu = pt.zcu;
|
||||||
@ -2349,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro
|
|||||||
} };
|
} };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given a `std.math.big.int.Const`, converts it to a `Value` which is a float of type `float_ty`
|
||||||
|
/// representing the same numeric value. If the integer cannot be exactly represented, `round`
|
||||||
|
/// decides whether the value should be rounded up or down. If `is_vector`, then `float_ty` is
|
||||||
|
/// instead a vector of floats, and the result value is a vector containing the converted scalar
|
||||||
|
/// repeated N times.
|
||||||
|
fn floatFromBigIntVal(
|
||||||
|
pt: Zcu.PerThread,
|
||||||
|
is_vector: bool,
|
||||||
|
float_ty: Type,
|
||||||
|
x: std.math.big.int.Const,
|
||||||
|
round: std.math.big.int.Round,
|
||||||
|
) Error!Value {
|
||||||
|
const zcu = pt.zcu;
|
||||||
|
const scalar_ty = switch (is_vector) {
|
||||||
|
true => float_ty.childType(zcu),
|
||||||
|
false => float_ty,
|
||||||
|
};
|
||||||
|
assert(scalar_ty.zigTypeTag(zcu) == .float);
|
||||||
|
const scalar_val: Value = switch (scalar_ty.floatBits(zcu.getTarget())) {
|
||||||
|
16 => try pt.floatValue(scalar_ty, x.toFloat(f16, round)[0]),
|
||||||
|
32 => try pt.floatValue(scalar_ty, x.toFloat(f32, round)[0]),
|
||||||
|
64 => try pt.floatValue(scalar_ty, x.toFloat(f64, round)[0]),
|
||||||
|
80 => try pt.floatValue(scalar_ty, x.toFloat(f80, round)[0]),
|
||||||
|
128 => try pt.floatValue(scalar_ty, x.toFloat(f128, round)[0]),
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
if (is_vector) {
|
||||||
|
return .fromInterned(try pt.intern(.{ .aggregate = .{
|
||||||
|
.ty = float_ty.toIntern(),
|
||||||
|
.storage = .{ .repeated_elem = scalar_val.toIntern() },
|
||||||
|
} }));
|
||||||
|
} else {
|
||||||
|
return scalar_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const Block = struct {
|
const Block = struct {
|
||||||
instructions: []Air.Inst.Index,
|
instructions: []Air.Inst.Index,
|
||||||
len: usize,
|
len: usize,
|
||||||
@ -2691,7 +2889,7 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `tag` to remind the caller to `continue :inst` the result.
|
/// Returns `tag` to remind the caller to `continue :inst` the result.
|
||||||
/// This is inline to propagate the comptime-known `tag`.
|
/// `inline` to propagate the comptime-known `tag` result.
|
||||||
inline fn replaceInst(l: *Legalize, inst: Air.Inst.Index, comptime tag: Air.Inst.Tag, data: Air.Inst.Data) Air.Inst.Tag {
|
inline fn replaceInst(l: *Legalize, inst: Air.Inst.Index, comptime tag: Air.Inst.Tag, data: Air.Inst.Data) Air.Inst.Tag {
|
||||||
const orig_ty = if (std.debug.runtime_safety) l.typeOfIndex(inst) else {};
|
const orig_ty = if (std.debug.runtime_safety) l.typeOfIndex(inst) else {};
|
||||||
l.air_instructions.set(@intFromEnum(inst), .{ .tag = tag, .data = data });
|
l.air_instructions.set(@intFromEnum(inst), .{ .tag = tag, .data = data });
|
||||||
@ -2706,4 +2904,5 @@ const InternPool = @import("../InternPool.zig");
|
|||||||
const Legalize = @This();
|
const Legalize = @This();
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Type = @import("../Type.zig");
|
const Type = @import("../Type.zig");
|
||||||
|
const Value = @import("../Value.zig");
|
||||||
const Zcu = @import("../Zcu.zig");
|
const Zcu = @import("../Zcu.zig");
|
||||||
|
|||||||
@ -374,6 +374,8 @@ pub fn categorizeOperand(
|
|||||||
.array_to_slice,
|
.array_to_slice,
|
||||||
.int_from_float,
|
.int_from_float,
|
||||||
.int_from_float_optimized,
|
.int_from_float_optimized,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
.float_from_int,
|
.float_from_int,
|
||||||
.get_union_tag,
|
.get_union_tag,
|
||||||
.clz,
|
.clz,
|
||||||
@ -1015,6 +1017,8 @@ fn analyzeInst(
|
|||||||
.array_to_slice,
|
.array_to_slice,
|
||||||
.int_from_float,
|
.int_from_float,
|
||||||
.int_from_float_optimized,
|
.int_from_float_optimized,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
.float_from_int,
|
.float_from_int,
|
||||||
.get_union_tag,
|
.get_union_tag,
|
||||||
.clz,
|
.clz,
|
||||||
|
|||||||
@ -107,6 +107,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
|
|||||||
.array_to_slice,
|
.array_to_slice,
|
||||||
.int_from_float,
|
.int_from_float,
|
||||||
.int_from_float_optimized,
|
.int_from_float_optimized,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
.float_from_int,
|
.float_from_int,
|
||||||
.get_union_tag,
|
.get_union_tag,
|
||||||
.clz,
|
.clz,
|
||||||
|
|||||||
@ -250,6 +250,8 @@ const Writer = struct {
|
|||||||
.splat,
|
.splat,
|
||||||
.int_from_float,
|
.int_from_float,
|
||||||
.int_from_float_optimized,
|
.int_from_float_optimized,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
.get_union_tag,
|
.get_union_tag,
|
||||||
.clz,
|
.clz,
|
||||||
.ctz,
|
.ctz,
|
||||||
|
|||||||
@ -130,6 +130,8 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
|
|||||||
.array_to_slice,
|
.array_to_slice,
|
||||||
.int_from_float,
|
.int_from_float,
|
||||||
.int_from_float_optimized,
|
.int_from_float_optimized,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
.float_from_int,
|
.float_from_int,
|
||||||
.splat,
|
.splat,
|
||||||
.error_set_has_value,
|
.error_set_has_value,
|
||||||
|
|||||||
178
src/Sema.zig
178
src/Sema.zig
@ -8934,9 +8934,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
|
|||||||
|
|
||||||
try sema.requireRuntimeBlock(block, src, operand_src);
|
try sema.requireRuntimeBlock(block, src, operand_src);
|
||||||
if (block.wantSafety()) {
|
if (block.wantSafety()) {
|
||||||
if (zcu.backendSupportsFeature(.panic_fn)) {
|
try sema.preparePanicId(src, .invalid_enum_value);
|
||||||
_ = try sema.preparePanicId(src, .invalid_enum_value);
|
|
||||||
}
|
|
||||||
return block.addTyOp(.intcast_safe, dest_ty, operand);
|
return block.addTyOp(.intcast_safe, dest_ty, operand);
|
||||||
}
|
}
|
||||||
return block.addTyOp(.intcast, dest_ty, operand);
|
return block.addTyOp(.intcast, dest_ty, operand);
|
||||||
@ -10340,9 +10338,7 @@ fn intCast(
|
|||||||
|
|
||||||
try sema.requireRuntimeBlock(block, src, operand_src);
|
try sema.requireRuntimeBlock(block, src, operand_src);
|
||||||
if (block.wantSafety()) {
|
if (block.wantSafety()) {
|
||||||
if (zcu.backendSupportsFeature(.panic_fn)) {
|
try sema.preparePanicId(src, .integer_out_of_bounds);
|
||||||
_ = try sema.preparePanicId(src, .integer_out_of_bounds);
|
|
||||||
}
|
|
||||||
return block.addTyOp(.intcast_safe, dest_ty, operand);
|
return block.addTyOp(.intcast_safe, dest_ty, operand);
|
||||||
}
|
}
|
||||||
return block.addTyOp(.intcast, dest_ty, operand);
|
return block.addTyOp(.intcast, dest_ty, operand);
|
||||||
@ -16395,9 +16391,7 @@ fn analyzeArithmetic(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (block.wantSafety() and want_safety and scalar_tag == .int) {
|
if (block.wantSafety() and want_safety and scalar_tag == .int) {
|
||||||
if (air_tag != air_tag_safe and zcu.backendSupportsFeature(.panic_fn)) {
|
if (air_tag != air_tag_safe) try sema.preparePanicId(src, .integer_overflow);
|
||||||
_ = try sema.preparePanicId(src, .integer_overflow);
|
|
||||||
}
|
|
||||||
return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
|
return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
|
||||||
}
|
}
|
||||||
return block.addBinOp(air_tag, casted_lhs, casted_rhs);
|
return block.addBinOp(air_tag, casted_lhs, casted_rhs);
|
||||||
@ -22178,44 +22172,32 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
|
|||||||
|
|
||||||
try sema.requireRuntimeBlock(block, src, operand_src);
|
try sema.requireRuntimeBlock(block, src, operand_src);
|
||||||
if (dest_scalar_ty.intInfo(zcu).bits == 0) {
|
if (dest_scalar_ty.intInfo(zcu).bits == 0) {
|
||||||
if (!is_vector) {
|
|
||||||
if (block.wantSafety()) {
|
if (block.wantSafety()) {
|
||||||
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()));
|
// Emit an explicit safety check. We can do this one like `abs(x) < 1`.
|
||||||
try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
|
const abs_ref = try block.addTyOp(.abs, operand_ty, operand);
|
||||||
}
|
const max_abs_ref = if (is_vector) try block.addReduce(abs_ref, .Max) else abs_ref;
|
||||||
return Air.internedToRef((try pt.intValue(dest_ty, 0)).toIntern());
|
const one_ref = Air.internedToRef((try pt.floatValue(operand_scalar_ty, 1.0)).toIntern());
|
||||||
}
|
const ok_ref = try block.addBinOp(.cmp_lt, max_abs_ref, one_ref);
|
||||||
if (block.wantSafety()) {
|
try sema.addSafetyCheck(block, src, ok_ref, .integer_part_out_of_bounds);
|
||||||
const len = dest_ty.vectorLen(zcu);
|
|
||||||
for (0..len) |i| {
|
|
||||||
const idx_ref = try pt.intRef(.usize, i);
|
|
||||||
const elem_ref = try block.addBinOp(.array_elem_val, operand, idx_ref);
|
|
||||||
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()));
|
|
||||||
try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const scalar_val = try pt.intValue(dest_scalar_ty, 0);
|
||||||
|
if (!is_vector) return Air.internedToRef(scalar_val.toIntern());
|
||||||
return Air.internedToRef(try pt.intern(.{ .aggregate = .{
|
return Air.internedToRef(try pt.intern(.{ .aggregate = .{
|
||||||
.ty = dest_ty.toIntern(),
|
.ty = dest_ty.toIntern(),
|
||||||
.storage = .{ .repeated_elem = (try pt.intValue(dest_scalar_ty, 0)).toIntern() },
|
.storage = .{ .repeated_elem = scalar_val.toIntern() },
|
||||||
} }));
|
} }));
|
||||||
}
|
}
|
||||||
const result = try block.addTyOp(if (block.float_mode == .optimized) .int_from_float_optimized else .int_from_float, dest_ty, operand);
|
|
||||||
if (block.wantSafety()) {
|
if (block.wantSafety()) {
|
||||||
const back = try block.addTyOp(.float_from_int, operand_ty, result);
|
try sema.preparePanicId(src, .integer_part_out_of_bounds);
|
||||||
const diff = try block.addBinOp(if (block.float_mode == .optimized) .sub_optimized else .sub, operand, back);
|
return block.addTyOp(switch (block.float_mode) {
|
||||||
const ok = if (is_vector) ok: {
|
.optimized => .int_from_float_optimized_safe,
|
||||||
const ok_pos = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, 1.0))).toIntern()), .lt);
|
.strict => .int_from_float_safe,
|
||||||
const ok_neg = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, -1.0))).toIntern()), .gt);
|
}, dest_ty, operand);
|
||||||
const ok = try block.addBinOp(.bit_and, ok_pos, ok_neg);
|
|
||||||
break :ok try block.addReduce(ok, .And);
|
|
||||||
} else ok: {
|
|
||||||
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()));
|
|
||||||
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()));
|
|
||||||
break :ok try block.addBinOp(.bool_and, ok_pos, ok_neg);
|
|
||||||
};
|
|
||||||
try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
|
|
||||||
}
|
}
|
||||||
return result;
|
return block.addTyOp(switch (block.float_mode) {
|
||||||
|
.optimized => .int_from_float_optimized,
|
||||||
|
.strict => .int_from_float,
|
||||||
|
}, dest_ty, operand);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
||||||
@ -26871,7 +26853,15 @@ fn explainWhyTypeIsNotPacked(
|
|||||||
/// Backends depend on panic decls being available when lowering safety-checked
|
/// Backends depend on panic decls being available when lowering safety-checked
|
||||||
/// instructions. This function ensures the panic function will be available to
|
/// instructions. This function ensures the panic function will be available to
|
||||||
/// be called during that time.
|
/// be called during that time.
|
||||||
fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index {
|
fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !void {
|
||||||
|
// If the backend doesn't support `.panic_fn`, it doesn't want us to lower the panic handlers.
|
||||||
|
// The backend will transform panics into traps instead.
|
||||||
|
if (sema.pt.zcu.backendSupportsFeature(.panic_fn)) {
|
||||||
|
_ = try sema.getPanicIdFunc(src, panic_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getPanicIdFunc(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index {
|
||||||
const zcu = sema.pt.zcu;
|
const zcu = sema.pt.zcu;
|
||||||
try sema.ensureMemoizedStateResolved(src, .panic);
|
try sema.ensureMemoizedStateResolved(src, .panic);
|
||||||
const panic_func = zcu.builtin_decl_values.get(panic_id.toBuiltin());
|
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
|
|||||||
if (!sema.pt.zcu.backendSupportsFeature(.panic_fn)) {
|
if (!sema.pt.zcu.backendSupportsFeature(.panic_fn)) {
|
||||||
_ = try block.addNoOp(.trap);
|
_ = try block.addNoOp(.trap);
|
||||||
} else {
|
} else {
|
||||||
const panic_fn = try sema.preparePanicId(src, panic_id);
|
const panic_fn = try sema.getPanicIdFunc(src, panic_id);
|
||||||
try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{}, .@"safety check");
|
try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{}, .@"safety check");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32843,24 +32833,21 @@ fn cmpNumeric(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (lhs_is_float) {
|
if (lhs_is_float) {
|
||||||
if (lhs_val.floatHasFraction(zcu)) {
|
const float = lhs_val.toFloat(f128, zcu);
|
||||||
switch (op) {
|
var big_int: std.math.big.int.Mutable = .{
|
||||||
|
.limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
|
||||||
|
.len = undefined,
|
||||||
|
.positive = undefined,
|
||||||
|
};
|
||||||
|
switch (big_int.setFloat(float, .away)) {
|
||||||
|
.inexact => switch (op) {
|
||||||
.eq => return .bool_false,
|
.eq => return .bool_false,
|
||||||
.neq => return .bool_true,
|
.neq => return .bool_true,
|
||||||
else => {},
|
else => {},
|
||||||
|
},
|
||||||
|
.exact => {},
|
||||||
}
|
}
|
||||||
}
|
lhs_bits = big_int.toConst().bitCountTwosComp();
|
||||||
|
|
||||||
var bigint = try float128IntPartToBigInt(sema.gpa, lhs_val.toFloat(f128, zcu));
|
|
||||||
defer bigint.deinit();
|
|
||||||
if (lhs_val.floatHasFraction(zcu)) {
|
|
||||||
if (lhs_is_signed) {
|
|
||||||
try bigint.addScalar(&bigint, -1);
|
|
||||||
} else {
|
|
||||||
try bigint.addScalar(&bigint, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lhs_bits = bigint.toConst().bitCountTwosComp();
|
|
||||||
} else {
|
} else {
|
||||||
lhs_bits = lhs_val.intBitCountTwosComp(zcu);
|
lhs_bits = lhs_val.intBitCountTwosComp(zcu);
|
||||||
}
|
}
|
||||||
@ -32890,24 +32877,21 @@ fn cmpNumeric(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rhs_is_float) {
|
if (rhs_is_float) {
|
||||||
if (rhs_val.floatHasFraction(zcu)) {
|
const float = rhs_val.toFloat(f128, zcu);
|
||||||
switch (op) {
|
var big_int: std.math.big.int.Mutable = .{
|
||||||
|
.limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
|
||||||
|
.len = undefined,
|
||||||
|
.positive = undefined,
|
||||||
|
};
|
||||||
|
switch (big_int.setFloat(float, .away)) {
|
||||||
|
.inexact => switch (op) {
|
||||||
.eq => return .bool_false,
|
.eq => return .bool_false,
|
||||||
.neq => return .bool_true,
|
.neq => return .bool_true,
|
||||||
else => {},
|
else => {},
|
||||||
|
},
|
||||||
|
.exact => {},
|
||||||
}
|
}
|
||||||
}
|
rhs_bits = big_int.toConst().bitCountTwosComp();
|
||||||
|
|
||||||
var bigint = try float128IntPartToBigInt(sema.gpa, rhs_val.toFloat(f128, zcu));
|
|
||||||
defer bigint.deinit();
|
|
||||||
if (rhs_val.floatHasFraction(zcu)) {
|
|
||||||
if (rhs_is_signed) {
|
|
||||||
try bigint.addScalar(&bigint, -1);
|
|
||||||
} else {
|
|
||||||
try bigint.addScalar(&bigint, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rhs_bits = bigint.toConst().bitCountTwosComp();
|
|
||||||
} else {
|
} else {
|
||||||
rhs_bits = rhs_val.intBitCountTwosComp(zcu);
|
rhs_bits = rhs_val.intBitCountTwosComp(zcu);
|
||||||
}
|
}
|
||||||
@ -36955,31 +36939,6 @@ fn intFromFloat(
|
|||||||
return sema.intFromFloatScalar(block, src, val, int_ty, mode);
|
return sema.intFromFloatScalar(block, src, val, int_ty, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// float is expected to be finite and non-NaN
|
|
||||||
fn float128IntPartToBigInt(
|
|
||||||
arena: Allocator,
|
|
||||||
float: f128,
|
|
||||||
) !std.math.big.int.Managed {
|
|
||||||
const is_negative = std.math.signbit(float);
|
|
||||||
const floored = @floor(@abs(float));
|
|
||||||
|
|
||||||
var rational = try std.math.big.Rational.init(arena);
|
|
||||||
defer rational.q.deinit();
|
|
||||||
rational.setFloat(f128, floored) catch |err| switch (err) {
|
|
||||||
error.NonFiniteFloat => unreachable,
|
|
||||||
error.OutOfMemory => return error.OutOfMemory,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The float is reduced in rational.setFloat, so we assert that denominator is equal to one
|
|
||||||
const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true };
|
|
||||||
assert(rational.q.toConst().eqlAbs(big_one));
|
|
||||||
|
|
||||||
if (is_negative) {
|
|
||||||
rational.negate();
|
|
||||||
}
|
|
||||||
return rational.p;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn intFromFloatScalar(
|
fn intFromFloatScalar(
|
||||||
sema: *Sema,
|
sema: *Sema,
|
||||||
block: *Block,
|
block: *Block,
|
||||||
@ -36993,13 +36952,6 @@ fn intFromFloatScalar(
|
|||||||
|
|
||||||
if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src);
|
if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src);
|
||||||
|
|
||||||
if (mode == .exact and val.floatHasFraction(zcu)) return sema.fail(
|
|
||||||
block,
|
|
||||||
src,
|
|
||||||
"fractional component prevents float value '{}' from coercion to type '{}'",
|
|
||||||
.{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },
|
|
||||||
);
|
|
||||||
|
|
||||||
const float = val.toFloat(f128, zcu);
|
const float = val.toFloat(f128, zcu);
|
||||||
if (std.math.isNan(float)) {
|
if (std.math.isNan(float)) {
|
||||||
return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{
|
return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{
|
||||||
@ -37012,12 +36964,28 @@ fn intFromFloatScalar(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var big_int = try float128IntPartToBigInt(sema.arena, float);
|
var big_int: std.math.big.int.Mutable = .{
|
||||||
defer big_int.deinit();
|
.limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
|
||||||
|
.len = undefined,
|
||||||
|
.positive = undefined,
|
||||||
|
};
|
||||||
|
switch (big_int.setFloat(float, .trunc)) {
|
||||||
|
.inexact => switch (mode) {
|
||||||
|
.exact => return sema.fail(
|
||||||
|
block,
|
||||||
|
src,
|
||||||
|
"fractional component prevents float value '{}' from coercion to type '{}'",
|
||||||
|
.{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },
|
||||||
|
),
|
||||||
|
.truncate => {},
|
||||||
|
},
|
||||||
|
.exact => {},
|
||||||
|
}
|
||||||
const cti_result = try pt.intValue_big(.comptime_int, big_int.toConst());
|
const cti_result = try pt.intValue_big(.comptime_int, big_int.toConst());
|
||||||
|
if (int_ty.toIntern() == .comptime_int_type) return cti_result;
|
||||||
|
|
||||||
if (!(try sema.intFitsInType(cti_result, int_ty, null))) {
|
const int_info = int_ty.intInfo(zcu);
|
||||||
|
if (!big_int.toConst().fitsInTwosComp(int_info.signedness, int_info.bits)) {
|
||||||
return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{
|
return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{
|
||||||
val.fmtValueSema(pt, sema), int_ty.fmt(pt),
|
val.fmtValueSema(pt, sema), int_ty.fmt(pt),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -509,30 +509,23 @@ fn lowerInt(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
.float_literal => |val| {
|
.float_literal => |val| {
|
||||||
// Check for fractional components
|
var big_int: std.math.big.int.Mutable = .{
|
||||||
if (@rem(val, 1) != 0) {
|
.limbs = try self.sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(val)),
|
||||||
return self.fail(
|
.len = undefined,
|
||||||
|
.positive = undefined,
|
||||||
|
};
|
||||||
|
switch (big_int.setFloat(val, .trunc)) {
|
||||||
|
.inexact => return self.fail(
|
||||||
node,
|
node,
|
||||||
"fractional component prevents float value '{}' from coercion to type '{}'",
|
"fractional component prevents float value '{}' from coercion to type '{}'",
|
||||||
.{ val, res_ty.fmt(self.sema.pt) },
|
.{ val, res_ty.fmt(self.sema.pt) },
|
||||||
);
|
),
|
||||||
|
.exact => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a rational representation of the float
|
|
||||||
var rational = try std.math.big.Rational.init(self.sema.arena);
|
|
||||||
rational.setFloat(f128, val) catch |err| switch (err) {
|
|
||||||
error.NonFiniteFloat => unreachable,
|
|
||||||
error.OutOfMemory => return error.OutOfMemory,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The float is reduced in rational.setFloat, so we assert that denominator is equal to
|
|
||||||
// one
|
|
||||||
const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true };
|
|
||||||
assert(rational.q.toConst().eqlAbs(big_one));
|
|
||||||
|
|
||||||
// Check that the result is in range of the result type
|
// Check that the result is in range of the result type
|
||||||
const int_info = res_ty.intInfo(self.sema.pt.zcu);
|
const int_info = res_ty.intInfo(self.sema.pt.zcu);
|
||||||
if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) {
|
if (!big_int.toConst().fitsInTwosComp(int_info.signedness, int_info.bits)) {
|
||||||
return self.fail(
|
return self.fail(
|
||||||
node,
|
node,
|
||||||
"type '{}' cannot represent integer value '{}'",
|
"type '{}' cannot represent integer value '{}'",
|
||||||
@ -543,7 +536,7 @@ fn lowerInt(
|
|||||||
return self.sema.pt.intern(.{
|
return self.sema.pt.intern(.{
|
||||||
.int = .{
|
.int = .{
|
||||||
.ty = res_ty.toIntern(),
|
.ty = res_ty.toIntern(),
|
||||||
.storage = .{ .big_int = rational.p.toConst() },
|
.storage = .{ .big_int = big_int.toConst() },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -584,7 +577,7 @@ fn lowerFloat(
|
|||||||
const value = switch (node.get(self.file.zoir.?)) {
|
const value = switch (node.get(self.file.zoir.?)) {
|
||||||
.int_literal => |int| switch (int) {
|
.int_literal => |int| switch (int) {
|
||||||
.small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
|
.small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
|
||||||
.big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128)),
|
.big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128, .nearest_even)[0]),
|
||||||
},
|
},
|
||||||
.float_literal => |val| try self.sema.pt.floatValue(res_ty, val),
|
.float_literal => |val| try self.sema.pt.floatValue(res_ty, val),
|
||||||
.char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
|
.char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
|
||||||
|
|||||||
@ -898,7 +898,7 @@ pub fn readFromPackedMemory(
|
|||||||
pub fn toFloat(val: Value, comptime T: type, zcu: *const Zcu) T {
|
pub fn toFloat(val: Value, comptime T: type, zcu: *const Zcu) T {
|
||||||
return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
|
return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
|
||||||
.int => |int| switch (int.storage) {
|
.int => |int| switch (int.storage) {
|
||||||
.big_int => |big_int| big_int.toFloat(T),
|
.big_int => |big_int| big_int.toFloat(T, .nearest_even)[0],
|
||||||
inline .u64, .i64 => |x| {
|
inline .u64, .i64 => |x| {
|
||||||
if (T == f80) {
|
if (T == f80) {
|
||||||
@panic("TODO we can't lower this properly on non-x86 llvm backend yet");
|
@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 {
|
|||||||
} }));
|
} }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts the value is a float
|
|
||||||
pub fn floatHasFraction(self: Value, zcu: *const Zcu) bool {
|
|
||||||
return switch (zcu.intern_pool.indexToKey(self.toIntern())) {
|
|
||||||
.float => |float| switch (float.storage) {
|
|
||||||
inline else => |x| @rem(x, 1) != 0,
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn orderAgainstZero(lhs: Value, zcu: *Zcu) std.math.Order {
|
pub fn orderAgainstZero(lhs: Value, zcu: *Zcu) std.math.Order {
|
||||||
return orderAgainstZeroInner(lhs, .normal, zcu, {}) catch unreachable;
|
return orderAgainstZeroInner(lhs, .normal, zcu, {}) catch unreachable;
|
||||||
}
|
}
|
||||||
@ -1557,17 +1547,13 @@ pub fn floatFromIntAdvanced(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptime strat: ResolveStrat) !Value {
|
pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptime strat: ResolveStrat) !Value {
|
||||||
const zcu = pt.zcu;
|
return switch (pt.zcu.intern_pool.indexToKey(val.toIntern())) {
|
||||||
return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
|
|
||||||
.undef => try pt.undefValue(float_ty),
|
.undef => try pt.undefValue(float_ty),
|
||||||
.int => |int| switch (int.storage) {
|
.int => |int| switch (int.storage) {
|
||||||
.big_int => |big_int| {
|
.big_int => |big_int| pt.floatValue(float_ty, big_int.toFloat(f128, .nearest_even)[0]),
|
||||||
const float = big_int.toFloat(f128);
|
|
||||||
return pt.floatValue(float_ty, float);
|
|
||||||
},
|
|
||||||
inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt),
|
inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt),
|
||||||
.lazy_align => |ty| return floatFromIntInner((try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), pt.zcu, pt.tid)).scalar.toByteUnits() orelse 0, float_ty, pt),
|
.lazy_align => |ty| floatFromIntInner((try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), pt.zcu, pt.tid)).scalar.toByteUnits() orelse 0, float_ty, pt),
|
||||||
.lazy_size => |ty| return floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, float_ty, pt),
|
.lazy_size => |ty| floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, float_ty, pt),
|
||||||
},
|
},
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -861,6 +861,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
|||||||
.sub_safe,
|
.sub_safe,
|
||||||
.mul_safe,
|
.mul_safe,
|
||||||
.intcast_safe,
|
.intcast_safe,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
=> return self.fail("TODO implement safety_checked_instructions", .{}),
|
=> return self.fail("TODO implement safety_checked_instructions", .{}),
|
||||||
|
|
||||||
.is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
|
.is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
|
||||||
|
|||||||
@ -850,6 +850,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
|||||||
.sub_safe,
|
.sub_safe,
|
||||||
.mul_safe,
|
.mul_safe,
|
||||||
.intcast_safe,
|
.intcast_safe,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
=> return self.fail("TODO implement safety_checked_instructions", .{}),
|
=> return self.fail("TODO implement safety_checked_instructions", .{}),
|
||||||
|
|
||||||
.is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
|
.is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
|
||||||
|
|||||||
@ -54,6 +54,8 @@ const InnerError = CodeGenError || error{OutOfRegisters};
|
|||||||
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
|
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
|
||||||
return comptime &.initMany(&.{
|
return comptime &.initMany(&.{
|
||||||
.expand_intcast_safe,
|
.expand_intcast_safe,
|
||||||
|
.expand_int_from_float_safe,
|
||||||
|
.expand_int_from_float_optimized_safe,
|
||||||
.expand_add_safe,
|
.expand_add_safe,
|
||||||
.expand_sub_safe,
|
.expand_sub_safe,
|
||||||
.expand_mul_safe,
|
.expand_mul_safe,
|
||||||
@ -1474,6 +1476,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
|
|||||||
.sub_safe,
|
.sub_safe,
|
||||||
.mul_safe,
|
.mul_safe,
|
||||||
.intcast_safe,
|
.intcast_safe,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
=> return func.fail("TODO implement safety_checked_instructions", .{}),
|
=> return func.fail("TODO implement safety_checked_instructions", .{}),
|
||||||
|
|
||||||
.cmp_lt,
|
.cmp_lt,
|
||||||
|
|||||||
@ -696,6 +696,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
|||||||
.sub_safe,
|
.sub_safe,
|
||||||
.mul_safe,
|
.mul_safe,
|
||||||
.intcast_safe,
|
.intcast_safe,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
=> @panic("TODO implement safety_checked_instructions"),
|
=> @panic("TODO implement safety_checked_instructions"),
|
||||||
|
|
||||||
.is_named_enum_value => @panic("TODO implement is_named_enum_value"),
|
.is_named_enum_value => @panic("TODO implement is_named_enum_value"),
|
||||||
|
|||||||
@ -31,6 +31,8 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
|
|||||||
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
|
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
|
||||||
return comptime &.initMany(&.{
|
return comptime &.initMany(&.{
|
||||||
.expand_intcast_safe,
|
.expand_intcast_safe,
|
||||||
|
.expand_int_from_float_safe,
|
||||||
|
.expand_int_from_float_optimized_safe,
|
||||||
.expand_add_safe,
|
.expand_add_safe,
|
||||||
.expand_sub_safe,
|
.expand_sub_safe,
|
||||||
.expand_mul_safe,
|
.expand_mul_safe,
|
||||||
@ -2020,6 +2022,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
|
|||||||
.sub_safe,
|
.sub_safe,
|
||||||
.mul_safe,
|
.mul_safe,
|
||||||
.intcast_safe,
|
.intcast_safe,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
=> return cg.fail("TODO implement safety_checked_instructions", .{}),
|
=> return cg.fail("TODO implement safety_checked_instructions", .{}),
|
||||||
|
|
||||||
.work_item_id,
|
.work_item_id,
|
||||||
|
|||||||
@ -102,6 +102,8 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features
|
|||||||
.reduce_one_elem_to_bitcast = true,
|
.reduce_one_elem_to_bitcast = true,
|
||||||
|
|
||||||
.expand_intcast_safe = true,
|
.expand_intcast_safe = true,
|
||||||
|
.expand_int_from_float_safe = true,
|
||||||
|
.expand_int_from_float_optimized_safe = true,
|
||||||
.expand_add_safe = true,
|
.expand_add_safe = true,
|
||||||
.expand_sub_safe = true,
|
.expand_sub_safe = true,
|
||||||
.expand_mul_safe = true,
|
.expand_mul_safe = true,
|
||||||
@ -107763,6 +107765,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
|
|||||||
};
|
};
|
||||||
try res[0].finish(inst, &.{ty_op.operand}, &ops, cg);
|
try res[0].finish(inst, &.{ty_op.operand}, &ops, cg);
|
||||||
},
|
},
|
||||||
|
.int_from_float_safe => unreachable,
|
||||||
|
.int_from_float_optimized_safe => unreachable,
|
||||||
.float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else {
|
.float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else {
|
||||||
const ty_op = air_datas[@intFromEnum(inst)].ty_op;
|
const ty_op = air_datas[@intFromEnum(inst)].ty_op;
|
||||||
var ops = try cg.tempsFromOperands(inst, .{ty_op.operand});
|
var ops = try cg.tempsFromOperands(inst, .{ty_op.operand});
|
||||||
|
|||||||
@ -23,12 +23,21 @@ const BigIntLimb = std.math.big.Limb;
|
|||||||
const BigInt = std.math.big.int;
|
const BigInt = std.math.big.int;
|
||||||
|
|
||||||
pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
|
pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
|
||||||
return if (dev.env.supports(.legalize)) comptime &.initMany(&.{
|
return comptime switch (dev.env.supports(.legalize)) {
|
||||||
.expand_intcast_safe,
|
inline false, true => |supports_legalize| &.init(.{
|
||||||
.expand_add_safe,
|
// we don't currently ask zig1 to use safe optimization modes
|
||||||
.expand_sub_safe,
|
.expand_intcast_safe = supports_legalize,
|
||||||
.expand_mul_safe,
|
.expand_int_from_float_safe = supports_legalize,
|
||||||
}) else null; // we don't currently ask zig1 to use safe optimization modes
|
.expand_int_from_float_optimized_safe = supports_legalize,
|
||||||
|
.expand_add_safe = supports_legalize,
|
||||||
|
.expand_sub_safe = supports_legalize,
|
||||||
|
.expand_mul_safe = supports_legalize,
|
||||||
|
|
||||||
|
.expand_packed_load = true,
|
||||||
|
.expand_packed_store = true,
|
||||||
|
.expand_packed_struct_field_val = true,
|
||||||
|
}),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For most backends, MIR is basically a sequence of machine code instructions, perhaps with some
|
/// 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,
|
|||||||
.sub_safe,
|
.sub_safe,
|
||||||
.mul_safe,
|
.mul_safe,
|
||||||
.intcast_safe,
|
.intcast_safe,
|
||||||
|
.int_from_float_safe,
|
||||||
|
.int_from_float_optimized_safe,
|
||||||
=> return f.fail("TODO implement safety_checked_instructions", .{}),
|
=> return f.fail("TODO implement safety_checked_instructions", .{}),
|
||||||
|
|
||||||
.is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
|
.is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
|
||||||
|
|||||||
@ -37,7 +37,10 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
|
|||||||
const Error = error{ OutOfMemory, CodegenFail };
|
const Error = error{ OutOfMemory, CodegenFail };
|
||||||
|
|
||||||
pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
|
pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
|
||||||
return null;
|
return comptime &.initMany(&.{
|
||||||
|
.expand_int_from_float_safe,
|
||||||
|
.expand_int_from_float_optimized_safe,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 {
|
fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 {
|
||||||
@ -4987,6 +4990,8 @@ pub const FuncGen = struct {
|
|||||||
|
|
||||||
.int_from_float => try self.airIntFromFloat(inst, .normal),
|
.int_from_float => try self.airIntFromFloat(inst, .normal),
|
||||||
.int_from_float_optimized => try self.airIntFromFloat(inst, .fast),
|
.int_from_float_optimized => try self.airIntFromFloat(inst, .fast),
|
||||||
|
.int_from_float_safe => unreachable, // handled by `legalizeFeatures`
|
||||||
|
.int_from_float_optimized_safe => unreachable, // handled by `legalizeFeatures`
|
||||||
|
|
||||||
.array_to_slice => try self.airArrayToSlice(inst),
|
.array_to_slice => try self.airArrayToSlice(inst),
|
||||||
.float_from_int => try self.airFloatFromInt(inst),
|
.float_from_int => try self.airFloatFromInt(inst),
|
||||||
|
|||||||
@ -31,6 +31,8 @@ const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
|
|||||||
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
|
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
|
||||||
return comptime &.initMany(&.{
|
return comptime &.initMany(&.{
|
||||||
.expand_intcast_safe,
|
.expand_intcast_safe,
|
||||||
|
.expand_int_from_float_safe,
|
||||||
|
.expand_int_from_float_optimized_safe,
|
||||||
.expand_add_safe,
|
.expand_add_safe,
|
||||||
.expand_sub_safe,
|
.expand_sub_safe,
|
||||||
.expand_mul_safe,
|
.expand_mul_safe,
|
||||||
|
|||||||
@ -272,6 +272,15 @@
|
|||||||
#define zig_linksection_fn zig_linksection
|
#define zig_linksection_fn zig_linksection
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if zig_has_attribute(visibility)
|
||||||
|
#define zig_visibility(name) __attribute__((visibility(#name)))
|
||||||
|
#else
|
||||||
|
#define zig_visibility(name) zig_visibility_##name
|
||||||
|
#define zig_visibility_default
|
||||||
|
#define zig_visibility_hidden zig_visibility_hidden_unavailable
|
||||||
|
#define zig_visibility_protected zig_visibility_protected_unavailable
|
||||||
|
#endif
|
||||||
|
|
||||||
#if zig_has_builtin(unreachable) || defined(zig_gcc) || defined(zig_tinyc)
|
#if zig_has_builtin(unreachable) || defined(zig_gcc) || defined(zig_tinyc)
|
||||||
#define zig_unreachable() __builtin_unreachable()
|
#define zig_unreachable() __builtin_unreachable()
|
||||||
#elif defined(zig_msvc)
|
#elif defined(zig_msvc)
|
||||||
|
|||||||
BIN
stage1/zig1.wasm
BIN
stage1/zig1.wasm
Binary file not shown.
@ -102,6 +102,7 @@ test "comptime_int @floatFromInt" {
|
|||||||
test "@floatFromInt" {
|
test "@floatFromInt" {
|
||||||
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
||||||
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
||||||
|
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
|
||||||
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
||||||
|
|
||||||
const S = struct {
|
const S = struct {
|
||||||
@ -2737,3 +2738,66 @@ test "peer type resolution: slice of sentinel-terminated array" {
|
|||||||
try expect(result[0][0] == 10);
|
try expect(result[0][0] == 10);
|
||||||
try expect(result[0][1] == 20);
|
try expect(result[0][1] == 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "@intFromFloat boundary cases" {
|
||||||
|
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
|
||||||
|
|
||||||
|
const S = struct {
|
||||||
|
fn case(comptime I: type, x: f32, bump: enum { up, down }, expected: I) !void {
|
||||||
|
const input: f32 = switch (bump) {
|
||||||
|
.up => std.math.nextAfter(f32, x, std.math.inf(f32)),
|
||||||
|
.down => std.math.nextAfter(f32, x, -std.math.inf(f32)),
|
||||||
|
};
|
||||||
|
const output: I = @intFromFloat(input);
|
||||||
|
try expect(output == expected);
|
||||||
|
}
|
||||||
|
fn doTheTest() !void {
|
||||||
|
try case(u8, 256.0, .down, 255);
|
||||||
|
try case(u8, -1.0, .up, 0);
|
||||||
|
try case(i8, 128.0, .down, 127);
|
||||||
|
try case(i8, -129.0, .up, -128);
|
||||||
|
|
||||||
|
try case(u0, 1.0, .down, 0);
|
||||||
|
try case(u0, -1.0, .up, 0);
|
||||||
|
try case(i0, 1.0, .down, 0);
|
||||||
|
try case(i0, -1.0, .up, 0);
|
||||||
|
|
||||||
|
try case(u10, 1024.0, .down, 1023);
|
||||||
|
try case(u10, -1.0, .up, 0);
|
||||||
|
try case(i10, 512.0, .down, 511);
|
||||||
|
try case(i10, -513.0, .up, -512);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try S.doTheTest();
|
||||||
|
try comptime S.doTheTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "@intFromFloat vector boundary cases" {
|
||||||
|
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
|
||||||
|
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
|
||||||
|
if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
|
||||||
|
|
||||||
|
const S = struct {
|
||||||
|
fn case(comptime I: type, unshifted_inputs: [2]f32, expected: [2]I) !void {
|
||||||
|
const inputs: @Vector(2, f32) = .{
|
||||||
|
std.math.nextAfter(f32, unshifted_inputs[0], std.math.inf(f32)),
|
||||||
|
std.math.nextAfter(f32, unshifted_inputs[1], -std.math.inf(f32)),
|
||||||
|
};
|
||||||
|
const outputs: @Vector(2, I) = @intFromFloat(inputs);
|
||||||
|
try expect(outputs[0] == expected[0]);
|
||||||
|
try expect(outputs[1] == expected[1]);
|
||||||
|
}
|
||||||
|
fn doTheTest() !void {
|
||||||
|
try case(u8, .{ -1.0, 256.0 }, .{ 0, 255 });
|
||||||
|
try case(i8, .{ -129.0, 128.0 }, .{ -128, 127 });
|
||||||
|
|
||||||
|
try case(u0, .{ -1.0, 1.0 }, .{ 0, 0 });
|
||||||
|
try case(i0, .{ -1.0, 1.0 }, .{ 0, 0 });
|
||||||
|
|
||||||
|
try case(u10, .{ -1.0, 1024.0 }, .{ 0, 1023 });
|
||||||
|
try case(i10, .{ -513.0, 512.0 }, .{ -512, 511 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try S.doTheTest();
|
||||||
|
try comptime S.doTheTest();
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
|
||||||
|
_ = stack_trace;
|
||||||
|
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
var x: f32 = 1.0;
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = @as(i0, @intFromFloat(x));
|
||||||
|
return error.TestFailed;
|
||||||
|
}
|
||||||
|
// run
|
||||||
|
// backend=stage2,llvm
|
||||||
|
// target=native
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
|
||||||
|
_ = stack_trace;
|
||||||
|
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
var x: f32 = -1.0;
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = @as(i0, @intFromFloat(x));
|
||||||
|
return error.TestFailed;
|
||||||
|
}
|
||||||
|
// run
|
||||||
|
// backend=stage2,llvm
|
||||||
|
// target=native
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
|
||||||
|
_ = stack_trace;
|
||||||
|
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
var x: f32 = 128;
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = @as(i8, @intFromFloat(x));
|
||||||
|
return error.TestFailed;
|
||||||
|
}
|
||||||
|
// run
|
||||||
|
// backend=stage2,llvm
|
||||||
|
// target=native
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
|
||||||
|
_ = stack_trace;
|
||||||
|
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
var x: f32 = -129;
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = @as(i8, @intFromFloat(x));
|
||||||
|
return error.TestFailed;
|
||||||
|
}
|
||||||
|
// run
|
||||||
|
// backend=stage2,llvm
|
||||||
|
// target=native
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
|
||||||
|
_ = stack_trace;
|
||||||
|
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
var x: f32 = 1.0;
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = @as(u0, @intFromFloat(x));
|
||||||
|
return error.TestFailed;
|
||||||
|
}
|
||||||
|
// run
|
||||||
|
// backend=stage2,llvm
|
||||||
|
// target=native
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
|
||||||
|
_ = stack_trace;
|
||||||
|
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
var x: f32 = -1.0;
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = @as(u0, @intFromFloat(x));
|
||||||
|
return error.TestFailed;
|
||||||
|
}
|
||||||
|
// run
|
||||||
|
// backend=stage2,llvm
|
||||||
|
// target=native
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
|
||||||
|
_ = stack_trace;
|
||||||
|
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
var x: f32 = 256;
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = @as(u8, @intFromFloat(x));
|
||||||
|
return error.TestFailed;
|
||||||
|
}
|
||||||
|
// run
|
||||||
|
// backend=stage2,llvm
|
||||||
|
// target=native
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
|
||||||
|
_ = stack_trace;
|
||||||
|
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
var x: f32 = -1;
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = @as(u8, @intFromFloat(x));
|
||||||
|
return error.TestFailed;
|
||||||
|
}
|
||||||
|
// run
|
||||||
|
// backend=stage2,llvm
|
||||||
|
// target=native
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
|
||||||
|
_ = stack_trace;
|
||||||
|
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
var x: @Vector(2, f32) = .{ 100, 512 };
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = @as(@Vector(2, i10), @intFromFloat(x));
|
||||||
|
return error.TestFailed;
|
||||||
|
}
|
||||||
|
// run
|
||||||
|
// backend=stage2,llvm
|
||||||
|
// target=native
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
|
||||||
|
_ = stack_trace;
|
||||||
|
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
var x: @Vector(2, f32) = .{ 100, -513 };
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = @as(@Vector(2, i10), @intFromFloat(x));
|
||||||
|
return error.TestFailed;
|
||||||
|
}
|
||||||
|
// run
|
||||||
|
// backend=stage2,llvm
|
||||||
|
// target=native
|
||||||
Loading…
x
Reference in New Issue
Block a user