T: typesignificand: Texponent: i32pub fn Frexp(comptime T: type) type {
return struct {
significand: T,
exponent: i32,
};
}T: typefpart: Tipart: Tpub fn Modf(comptime T: type) type {
return struct {
fpart: T,
ipart: T,
};
}A complex number consisting of a real an imaginary part. T must be a floating-point value.
T: typere: TReal part.
im: TImaginary part.
pub fn init(re: T, im: T) SelfCreate a new Complex number from the given real and imaginary parts.
re: Tim: TReturns the sum of two complex numbers.
Returns the subtraction of two complex numbers.
Returns the product of two complex numbers.
Returns the quotient of two complex numbers.
pub fn Complex(comptime T: type) type {
return struct {
const Self = @This();
/// Real part.
re: T,
/// Imaginary part.
im: T,
/// Create a new Complex number from the given real and imaginary parts.
pub fn init(re: T, im: T) Self {
return Self{
.re = re,
.im = im,
};
}
/// Returns the sum of two complex numbers.
pub fn add(self: Self, other: Self) Self {
return Self{
.re = self.re + other.re,
.im = self.im + other.im,
};
}
/// Returns the subtraction of two complex numbers.
pub fn sub(self: Self, other: Self) Self {
return Self{
.re = self.re - other.re,
.im = self.im - other.im,
};
}
/// Returns the product of two complex numbers.
pub fn mul(self: Self, other: Self) Self {
return Self{
.re = self.re * other.re - self.im * other.im,
.im = self.im * other.re + self.re * other.im,
};
}
/// Returns the quotient of two complex numbers.
pub fn div(self: Self, other: Self) Self {
const re_num = self.re * other.re + self.im * other.im;
const im_num = self.im * other.re - self.re * other.im;
const den = other.re * other.re + other.im * other.im;
return Self{
.re = re_num / den,
.im = im_num / den,
};
}
/// Returns the complex conjugate of a number.
pub fn conjugate(self: Self) Self {
return Self{
.re = self.re,
.im = -self.im,
};
}
/// Returns the negation of a complex number.
pub fn neg(self: Self) Self {
return Self{
.re = -self.re,
.im = -self.im,
};
}
/// Returns the product of complex number and i=sqrt(-1)
pub fn mulbyi(self: Self) Self {
return Self{
.re = -self.im,
.im = self.re,
};
}
/// Returns the reciprocal of a complex number.
pub fn reciprocal(self: Self) Self {
const m = self.re * self.re + self.im * self.im;
return Self{
.re = self.re / m,
.im = -self.im / m,
};
}
/// Returns the magnitude of a complex number.
pub fn magnitude(self: Self) T {
return @sqrt(self.re * self.re + self.im * self.im);
}
pub fn squaredMagnitude(self: Self) T {
return self.re * self.re + self.im * self.im;
}
};
}Given two types, returns the smallest one which is capable of holding the full range of the minimum value.
A: typeB: typepub fn Min(comptime A: type, comptime B: type) type {
switch (@typeInfo(A)) {
.int => |a_info| switch (@typeInfo(B)) {
.int => |b_info| if (a_info.signedness == .unsigned and b_info.signedness == .unsigned) {
if (a_info.bits < b_info.bits) {
return A;
} else {
return B;
}
},
else => {},
},
else => {},
}
return @TypeOf(@as(A, 0) + @as(B, 0));
}Returns an unsigned int type that can hold the number of bits in T - 1. Suitable for 0-based bit indices of T.
T: typeReturns an unsigned int type that can hold the number of bits in T.
T: typeReturns the smallest integer type that can hold both from and to.
from: comptime_intto: comptime_inttest IntFittingRange {
try testing.expect(IntFittingRange(0, 0) == u0);
try testing.expect(IntFittingRange(0, 1) == u1);
try testing.expect(IntFittingRange(0, 2) == u2);
try testing.expect(IntFittingRange(0, 3) == u2);
try testing.expect(IntFittingRange(0, 4) == u3);
try testing.expect(IntFittingRange(0, 7) == u3);
try testing.expect(IntFittingRange(0, 8) == u4);
try testing.expect(IntFittingRange(0, 9) == u4);
try testing.expect(IntFittingRange(0, 15) == u4);
try testing.expect(IntFittingRange(0, 16) == u5);
try testing.expect(IntFittingRange(0, 17) == u5);
try testing.expect(IntFittingRange(0, 4095) == u12);
try testing.expect(IntFittingRange(2000, 4095) == u12);
try testing.expect(IntFittingRange(0, 4096) == u13);
try testing.expect(IntFittingRange(2000, 4096) == u13);
try testing.expect(IntFittingRange(0, 4097) == u13);
try testing.expect(IntFittingRange(2000, 4097) == u13);
try testing.expect(IntFittingRange(0, 123456789123456798123456789) == u87);
try testing.expect(IntFittingRange(0, 123456789123456798123456789123456789123456798123456789) == u177);
try testing.expect(IntFittingRange(-1, -1) == i1);
try testing.expect(IntFittingRange(-1, 0) == i1);
try testing.expect(IntFittingRange(-1, 1) == i2);
try testing.expect(IntFittingRange(-2, -2) == i2);
try testing.expect(IntFittingRange(-2, -1) == i2);
try testing.expect(IntFittingRange(-2, 0) == i2);
try testing.expect(IntFittingRange(-2, 1) == i2);
try testing.expect(IntFittingRange(-2, 2) == i3);
try testing.expect(IntFittingRange(-1, 2) == i3);
try testing.expect(IntFittingRange(-1, 3) == i3);
try testing.expect(IntFittingRange(-1, 4) == i4);
try testing.expect(IntFittingRange(-1, 7) == i4);
try testing.expect(IntFittingRange(-1, 8) == i5);
try testing.expect(IntFittingRange(-1, 9) == i5);
try testing.expect(IntFittingRange(-1, 15) == i5);
try testing.expect(IntFittingRange(-1, 16) == i6);
try testing.expect(IntFittingRange(-1, 17) == i6);
try testing.expect(IntFittingRange(-1, 4095) == i13);
try testing.expect(IntFittingRange(-4096, 4095) == i13);
try testing.expect(IntFittingRange(-1, 4096) == i14);
try testing.expect(IntFittingRange(-4097, 4095) == i14);
try testing.expect(IntFittingRange(-1, 4097) == i14);
try testing.expect(IntFittingRange(-1, 123456789123456798123456789) == i88);
try testing.expect(IntFittingRange(-1, 123456789123456798123456789123456789123456798123456789) == i178);
}pub fn IntFittingRange(comptime from: comptime_int, comptime to: comptime_int) type {
assert(from <= to);
if (from == 0 and to == 0) {
return u0;
}
const signedness: std.builtin.Signedness = if (from < 0) .signed else .unsigned;
const largest_positive_integer = @max(if (from < 0) (-from) - 1 else from, to); // two's complement
const base = log2(largest_positive_integer);
const upper = (1 << base) - 1;
var magnitude_bits = if (upper >= largest_positive_integer) base else base + 1;
if (signedness == .signed) {
magnitude_bits += 1;
}
return std.meta.Int(signedness, magnitude_bits);
}Aligns the given integer type bit width to a width divisible by 8.
T: typetest ByteAlignedInt {
try testing.expect(ByteAlignedInt(u0) == u0);
try testing.expect(ByteAlignedInt(i0) == i0);
try testing.expect(ByteAlignedInt(u3) == u8);
try testing.expect(ByteAlignedInt(u8) == u8);
try testing.expect(ByteAlignedInt(i111) == i112);
try testing.expect(ByteAlignedInt(u129) == u136);
}Euler's number (e)
pub const e = 2.71828182845904523536028747135266249775724709369995Archimedes' constant (π)
pub const pi = 3.14159265358979323846264338327950288419716939937510Phi or Golden ratio constant (Φ) = (1 + sqrt(5))/2
pub const phi = 1.6180339887498948482045868343656381177203091798057628621log2(e)
pub const log2e = 1.442695040888963407359924681001892137log10(e)
pub const log10e = 0.434294481903251827651128918916605082ln(10)
pub const ln10 = 2.3025850929940456840179914546843642082/sqrt(π)
pub const two_sqrtpi = 1.128379167095512573896158903121545172sqrt(2)
pub const sqrt2 = 1.4142135623730950488016887242096980791/sqrt(2)
pub const sqrt1_2 = 0.707106781186547524400844362104849039pi/180.0
pub const rad_per_deg = 0.0174532925199432957692369076848861271344287188854172545609719144180.0/pi
pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861anyerror means the error set is known only at runtime.
pub const AlignCastError = error{UnalignedMemory}pub inline fn floatExponentBits(comptime T: type) comptime_intReturns the number of bits in the exponent of floating point type T.
T: typepub inline fn floatExponentBits(comptime T: type) comptime_int {
comptime assert(@typeInfo(T) == .float);
return switch (@typeInfo(T).float.bits) {
16 => 5,
32 => 8,
64 => 11,
80 => 15,
128 => 15,
else => @compileError("unknown floating point type " ++ @typeName(T)),
};
}pub inline fn floatMantissaBits(comptime T: type) comptime_intReturns the number of bits in the mantissa of floating point type T.
T: typepub inline fn floatMantissaBits(comptime T: type) comptime_int {
comptime assert(@typeInfo(T) == .float);
return switch (@typeInfo(T).float.bits) {
16 => 10,
32 => 23,
64 => 52,
80 => 64,
128 => 112,
else => @compileError("unknown floating point type " ++ @typeName(T)),
};
}pub inline fn floatFractionalBits(comptime T: type) comptime_intReturns the number of fractional bits in the mantissa of floating point type T.
T: typepub inline fn floatFractionalBits(comptime T: type) comptime_int {
comptime assert(@typeInfo(T) == .float);
// standard IEEE floats have an implicit 0.m or 1.m integer part
// f80 is special and has an explicitly stored bit in the MSB
// this function corresponds to `MANT_DIG - 1' from C
return switch (@typeInfo(T).float.bits) {
16 => 10,
32 => 23,
64 => 52,
80 => 63,
128 => 112,
else => @compileError("unknown floating point type " ++ @typeName(T)),
};
}pub inline fn floatExponentMin(comptime T: type) comptime_intReturns the minimum exponent that can represent a normalised value in floating point type T.
T: typepub inline fn floatExponentMin(comptime T: type) comptime_int {
return -floatExponentMax(T) + 1;
}pub inline fn floatExponentMax(comptime T: type) comptime_intReturns the maximum exponent that can represent a normalised value in floating point type T.
T: typepub inline fn floatExponentMax(comptime T: type) comptime_int {
return (1 << (floatExponentBits(T) - 1)) - 1;
}pub inline fn floatTrueMin(comptime T: type) TReturns the smallest subnormal number representable in floating point type T.
T: typepub inline fn floatTrueMin(comptime T: type) T {
return reconstructFloat(T, floatExponentMin(T) - 1, 1);
}pub inline fn floatMin(comptime T: type) TReturns the smallest normal number representable in floating point type T.
T: typepub inline fn floatMin(comptime T: type) T {
return reconstructFloat(T, floatExponentMin(T), mantissaOne(T));
}pub inline fn floatMax(comptime T: type) TReturns the largest normal number representable in floating point type T.
T: typepub inline fn floatMax(comptime T: type) T {
const all1s_mantissa = (1 << floatMantissaBits(T)) - 1;
return reconstructFloat(T, floatExponentMax(T), all1s_mantissa);
}pub inline fn floatEps(comptime T: type) TReturns the machine epsilon of floating point type T.
T: typepub inline fn floatEps(comptime T: type) T {
return reconstructFloat(T, -floatFractionalBits(T), mantissaOne(T));
}pub inline fn floatEpsAt(comptime T: type, x: T) TReturns the local epsilon of floating point type T.
T: typex: Tpub inline fn floatEpsAt(comptime T: type, x: T) T {
switch (@typeInfo(T)) {
.float => |F| {
const U: type = @Type(.{ .int = .{ .signedness = .unsigned, .bits = F.bits } });
const u: U = @bitCast(x);
const y: T = @bitCast(u ^ 1);
return @abs(x - y);
},
else => @compileError("floatEpsAt only supports floats"),
}
}pub inline fn inf(comptime T: type) TReturns the value inf for floating point type T.
T: typetest inf {
const inf_u16: u16 = 0x7C00;
const inf_u32: u32 = 0x7F800000;
const inf_u64: u64 = 0x7FF0000000000000;
const inf_u80: u80 = 0x7FFF8000000000000000;
const inf_u128: u128 = 0x7FFF0000000000000000000000000000;
try expectEqual(inf_u16, @as(u16, @bitCast(inf(f16))));
try expectEqual(inf_u32, @as(u32, @bitCast(inf(f32))));
try expectEqual(inf_u64, @as(u64, @bitCast(inf(f64))));
try expectEqual(inf_u80, @as(u80, @bitCast(inf(f80))));
try expectEqual(inf_u128, @as(u128, @bitCast(inf(f128))));
}pub inline fn inf(comptime T: type) T {
return reconstructFloat(T, floatExponentMax(T) + 1, mantissaOne(T));
}pub inline fn nan(comptime T: type) TReturns the canonical quiet NaN representation for floating point type T.
T: typetest nan {
const qnan_u16: u16 = 0x7E00;
const qnan_u32: u32 = 0x7FC00000;
const qnan_u64: u64 = 0x7FF8000000000000;
const qnan_u80: u80 = 0x7FFFC000000000000000;
const qnan_u128: u128 = 0x7FFF8000000000000000000000000000;
try expectEqual(qnan_u16, @as(u16, @bitCast(nan(f16))));
try expectEqual(qnan_u32, @as(u32, @bitCast(nan(f32))));
try expectEqual(qnan_u64, @as(u64, @bitCast(nan(f64))));
try expectEqual(qnan_u80, @as(u80, @bitCast(nan(f80))));
try expectEqual(qnan_u128, @as(u128, @bitCast(nan(f128))));
}pub inline fn nan(comptime T: type) T {
return reconstructFloat(
T,
floatExponentMax(T) + 1,
mantissaOne(T) | 1 << (floatFractionalBits(T) - 1),
);
}pub inline fn snan(comptime T: type) TReturns a signalling NaN representation for floating point type T.
TODO: LLVM is known to miscompile on some architectures to quiet NaN - this is tracked by https://github.com/ziglang/zig/issues/14366
T: typetest snan {
const snan_u16: u16 = 0x7D00;
const snan_u32: u32 = 0x7FA00000;
const snan_u64: u64 = 0x7FF4000000000000;
const snan_u80: u80 = 0x7FFFA000000000000000;
const snan_u128: u128 = 0x7FFF4000000000000000000000000000;
try expectEqual(snan_u16, @as(u16, @bitCast(snan(f16))));
try expectEqual(snan_u32, @as(u32, @bitCast(snan(f32))));
try expectEqual(snan_u64, @as(u64, @bitCast(snan(f64))));
try expectEqual(snan_u80, @as(u80, @bitCast(snan(f80))));
try expectEqual(snan_u128, @as(u128, @bitCast(snan(f128))));
}pub inline fn snan(comptime T: type) T {
return reconstructFloat(
T,
floatExponentMax(T) + 1,
mantissaOne(T) | 1 << (floatFractionalBits(T) - 2),
);
}pub fn approxEqAbs(comptime T: type, x: T, y: T, tolerance: T) boolPerforms an approximate comparison of two floating point values x and y.
Returns true if the absolute difference between them is less or equal than
the specified tolerance.
The tolerance parameter is the absolute tolerance used when determining if
the two numbers are close enough; a good value for this parameter is a small
multiple of floatEps(T).
Note that this function is recommended for comparing small numbers
around zero; using approxEqRel is suggested otherwise.
NaN values are never considered equal to any value.
T: typex: Ty: Ttolerance: Ttest approxEqAbs {
inline for ([_]type{ f16, f32, f64, f128 }) |T| {
const eps_value = comptime floatEps(T);
const min_value = comptime floatMin(T);
try testing.expect(approxEqAbs(T, 0.0, 0.0, eps_value));
try testing.expect(approxEqAbs(T, -0.0, -0.0, eps_value));
try testing.expect(approxEqAbs(T, 0.0, -0.0, eps_value));
try testing.expect(!approxEqAbs(T, 1.0 + 2 * eps_value, 1.0, eps_value));
try testing.expect(approxEqAbs(T, 1.0 + 1 * eps_value, 1.0, eps_value));
try testing.expect(approxEqAbs(T, min_value, 0.0, eps_value * 2));
try testing.expect(approxEqAbs(T, -min_value, 0.0, eps_value * 2));
}
comptime {
// `comptime_float` is guaranteed to have the same precision and operations of
// the largest other floating point type, which is f128 but it doesn't have a
// defined layout so we can't rely on `@bitCast` to construct the smallest
// possible epsilon value like we do in the tests above. In the same vein, we
// also can't represent a max/min, `NaN` or `Inf` values.
const eps_value = 1e-4;
try testing.expect(approxEqAbs(comptime_float, 0.0, 0.0, eps_value));
try testing.expect(approxEqAbs(comptime_float, -0.0, -0.0, eps_value));
try testing.expect(approxEqAbs(comptime_float, 0.0, -0.0, eps_value));
try testing.expect(!approxEqAbs(comptime_float, 1.0 + 2 * eps_value, 1.0, eps_value));
try testing.expect(approxEqAbs(comptime_float, 1.0 + 1 * eps_value, 1.0, eps_value));
}
}pub fn approxEqAbs(comptime T: type, x: T, y: T, tolerance: T) bool {
assert(@typeInfo(T) == .float or @typeInfo(T) == .comptime_float);
assert(tolerance >= 0);
// Fast path for equal values (and signed zeros and infinites).
if (x == y)
return true;
if (isNan(x) or isNan(y))
return false;
return @abs(x - y) <= tolerance;
}pub fn approxEqRel(comptime T: type, x: T, y: T, tolerance: T) boolPerforms an approximate comparison of two floating point values x and y.
Returns true if the absolute difference between them is less or equal than
max(|x|, |y|) * tolerance, where tolerance is a positive number greater
than zero.
The tolerance parameter is the relative tolerance used when determining if
the two numbers are close enough; a good value for this parameter is usually
sqrt(floatEps(T)), meaning that the two numbers are considered equal if at
least half of the digits are equal.
Note that for comparisons of small numbers around zero this function won't
give meaningful results, use approxEqAbs instead.
NaN values are never considered equal to any value.
T: typex: Ty: Ttolerance: Ttest approxEqRel {
inline for ([_]type{ f16, f32, f64, f128 }) |T| {
const eps_value = comptime floatEps(T);
const sqrt_eps_value = comptime sqrt(eps_value);
const nan_value = comptime nan(T);
const inf_value = comptime inf(T);
const min_value = comptime floatMin(T);
try testing.expect(approxEqRel(T, 1.0, 1.0, sqrt_eps_value));
try testing.expect(!approxEqRel(T, 1.0, 0.0, sqrt_eps_value));
try testing.expect(!approxEqRel(T, 1.0, nan_value, sqrt_eps_value));
try testing.expect(!approxEqRel(T, nan_value, nan_value, sqrt_eps_value));
try testing.expect(approxEqRel(T, inf_value, inf_value, sqrt_eps_value));
try testing.expect(approxEqRel(T, min_value, min_value, sqrt_eps_value));
try testing.expect(approxEqRel(T, -min_value, -min_value, sqrt_eps_value));
}
comptime {
// `comptime_float` is guaranteed to have the same precision and operations of
// the largest other floating point type, which is f128 but it doesn't have a
// defined layout so we can't rely on `@bitCast` to construct the smallest
// possible epsilon value like we do in the tests above. In the same vein, we
// also can't represent a max/min, `NaN` or `Inf` values.
const eps_value = 1e-4;
const sqrt_eps_value = sqrt(eps_value);
try testing.expect(approxEqRel(comptime_float, 1.0, 1.0, sqrt_eps_value));
try testing.expect(!approxEqRel(comptime_float, 1.0, 0.0, sqrt_eps_value));
}
}pub fn approxEqRel(comptime T: type, x: T, y: T, tolerance: T) bool {
assert(@typeInfo(T) == .float or @typeInfo(T) == .comptime_float);
assert(tolerance > 0);
// Fast path for equal values (and signed zeros and infinites).
if (x == y)
return true;
if (isNan(x) or isNan(y))
return false;
return @abs(x - y) <= @max(@abs(x), @abs(y)) * tolerance;
}pub fn raiseInvalid() voidpub fn raiseInvalid() void {
// Raise INVALID fpu exception
}pub fn raiseUnderflow() voidpub fn raiseUnderflow() void {
// Raise UNDERFLOW fpu exception
}pub fn raiseOverflow() voidpub fn raiseOverflow() void {
// Raise OVERFLOW fpu exception
}pub fn raiseInexact() voidpub fn raiseInexact() void {
// Raise INEXACT fpu exception
}pub fn raiseDivByZero() voidpub fn raiseDivByZero() void {
// Raise INEXACT fpu exception
}pub fn isNan(x: anytype) booltest isNan {
inline for ([_]type{ f16, f32, f64, f80, f128, c_longdouble }) |T| {
try expect(isNan(math.nan(T)));
try expect(isNan(-math.nan(T)));
try expect(isNan(math.snan(T)));
try expect(!isNan(@as(T, 1.0)));
try expect(!isNan(@as(T, math.inf(T))));
}
}pub fn isNan(x: anytype) bool {
return x != x;
}pub fn isSignalNan(x: anytype) boolTODO: LLVM is known to miscompile on some architectures to quiet NaN - this is tracked by https://github.com/ziglang/zig/issues/14366
test isSignalNan {
inline for ([_]type{ f16, f32, f64, f80, f128, c_longdouble }) |T| {
// TODO: Signalling NaN values get converted to quiet NaN values in
// some cases where they shouldn't such that this can fail.
// See https://github.com/ziglang/zig/issues/14366
if (!builtin.cpu.arch.isArm() and
!builtin.cpu.arch.isAARCH64() and
!builtin.cpu.arch.isMIPS32() and
!builtin.cpu.arch.isPowerPC() and
builtin.zig_backend != .stage2_c)
{
try expect(isSignalNan(math.snan(T)));
}
try expect(!isSignalNan(math.nan(T)));
try expect(!isSignalNan(@as(T, 1.0)));
try expect(!isSignalNan(math.inf(T)));
}
}pub fn isSignalNan(x: anytype) bool {
const T = @TypeOf(x);
const U = meta.Int(.unsigned, @bitSizeOf(T));
const quiet_signal_bit_mask = 1 << (math.floatFractionalBits(T) - 1);
return isNan(x) and (@as(U, @bitCast(x)) & quiet_signal_bit_mask == 0);
}pub fn frexp(x: anytype) Frexp(@TypeOf(x))Breaks x into a normalized fraction and an integral power of two. f == frac * 2^exp, with |frac| in the interval [0.5, 1).
Special Cases:
test frexp {
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
const max_exponent = math.floatExponentMax(T) + 1;
const min_exponent = math.floatExponentMin(T) + 1;
const truemin_exponent = min_exponent - math.floatFractionalBits(T);
var result: Frexp(T) = undefined;
comptime var x: T = undefined;
// basic usage
// value -> {significand, exponent},
// value == significand * (2 ^ exponent)
x = 1234.5678;
result = frexp(x);
try expectEqual(11, result.exponent);
try expectApproxEqAbs(0.602816, result.significand, 1e-6);
try expectEqual(x, math.ldexp(result.significand, result.exponent));
// float maximum
x = math.floatMax(T);
result = frexp(x);
try expectEqual(max_exponent, result.exponent);
try expectEqual(1.0 - math.floatEps(T) / 2, result.significand);
try expectEqual(x, math.ldexp(result.significand, result.exponent));
// float minimum
x = math.floatMin(T);
result = frexp(x);
try expectEqual(min_exponent, result.exponent);
try expectEqual(0.5, result.significand);
try expectEqual(x, math.ldexp(result.significand, result.exponent));
// float true minimum
// subnormal -> {normal, exponent}
x = math.floatTrueMin(T);
result = frexp(x);
try expectEqual(truemin_exponent, result.exponent);
try expectEqual(0.5, result.significand);
try expectEqual(x, math.ldexp(result.significand, result.exponent));
// infinity -> {infinity, zero} (+)
result = frexp(math.inf(T));
try expectEqual(0, result.exponent);
try expect(math.isPositiveInf(result.significand));
// infinity -> {infinity, zero} (-)
result = frexp(-math.inf(T));
try expectEqual(0, result.exponent);
try expect(math.isNegativeInf(result.significand));
// zero -> {zero, zero} (+)
result = frexp(@as(T, 0.0));
try expectEqual(0, result.exponent);
try expect(math.isPositiveZero(result.significand));
// zero -> {zero, zero} (-)
result = frexp(@as(T, -0.0));
try expectEqual(0, result.exponent);
try expect(math.isNegativeZero(result.significand));
// nan -> {nan, undefined}
result = frexp(math.nan(T));
try expect(math.isNan(result.significand));
}
}pub fn frexp(x: anytype) Frexp(@TypeOf(x)) {
const T: type = @TypeOf(x);
const bits: comptime_int = @typeInfo(T).float.bits;
const Int: type = std.meta.Int(.unsigned, bits);
const exp_bits: comptime_int = math.floatExponentBits(T);
const mant_bits: comptime_int = math.floatMantissaBits(T);
const frac_bits: comptime_int = math.floatFractionalBits(T);
const exp_min: comptime_int = math.floatExponentMin(T);
const ExpInt: type = std.meta.Int(.unsigned, exp_bits);
const MantInt: type = std.meta.Int(.unsigned, mant_bits);
const FracInt: type = std.meta.Int(.unsigned, frac_bits);
const unreal_exponent: comptime_int = (1 << exp_bits) - 1;
const bias: comptime_int = (1 << (exp_bits - 1)) - 2;
const exp_mask: comptime_int = unreal_exponent << mant_bits;
const zero_exponent: comptime_int = bias << mant_bits;
const sign_mask: comptime_int = 1 << (bits - 1);
const not_exp: comptime_int = ~@as(Int, exp_mask);
const ones_place: comptime_int = mant_bits - frac_bits;
const extra_denorm_shift: comptime_int = 1 - ones_place;
var result: Frexp(T) = undefined;
var v: Int = @bitCast(x);
const m: MantInt = @truncate(v);
const e: ExpInt = @truncate(v >> mant_bits);
switch (e) {
0 => {
if (m != 0) {
// subnormal
const offset = @clz(m);
const shift = offset + extra_denorm_shift;
v &= sign_mask;
v |= zero_exponent;
v |= math.shl(MantInt, m, shift);
result.exponent = exp_min - @as(i32, offset) + ones_place;
} else {
// +-0 = (+-0, 0)
result.exponent = 0;
}
},
unreal_exponent => {
// +-nan -> {+-nan, undefined}
result.exponent = undefined;
// +-inf -> {+-inf, 0}
if (@as(FracInt, @truncate(v)) == 0)
result.exponent = 0;
},
else => {
// normal
v &= not_exp;
v |= zero_exponent;
result.exponent = @as(i32, e) - bias;
},
}
result.significand = @bitCast(v);
return result;
}pub fn modf(x: anytype) Modf(@TypeOf(x))Returns the integer and fractional floating-point numbers that sum to x. The sign of each result is the same as the sign of x. In comptime, may be used with comptime_float
Special Cases:
test modf {
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
const epsilon: comptime_float = @max(1e-6, math.floatEps(T));
var r: Modf(T) = undefined;
r = modf(@as(T, 1.0));
try expectEqual(1.0, r.ipart);
try expectEqual(0.0, r.fpart);
r = modf(@as(T, 0.34682));
try expectEqual(0.0, r.ipart);
try expectApproxEqAbs(@as(T, 0.34682), r.fpart, epsilon);
r = modf(@as(T, 2.54576));
try expectEqual(2.0, r.ipart);
try expectApproxEqAbs(0.54576, r.fpart, epsilon);
r = modf(@as(T, 3.9782));
try expectEqual(3.0, r.ipart);
try expectApproxEqAbs(0.9782, r.fpart, epsilon);
}
}pub fn modf(x: anytype) Modf(@TypeOf(x)) {
const ipart = @trunc(x);
return .{
.ipart = ipart,
.fpart = x - ipart,
};
}pub fn copysign(magnitude: anytype, sign: @TypeOf(magnitude)) @TypeOf(magnitude)Returns a value with the magnitude of magnitude and the sign of sign.
sign: @TypeOf(magnitude)test copysign {
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
try expect(copysign(@as(T, 1.0), @as(T, 1.0)) == 1.0);
try expect(copysign(@as(T, 2.0), @as(T, -2.0)) == -2.0);
try expect(copysign(@as(T, -3.0), @as(T, 3.0)) == 3.0);
try expect(copysign(@as(T, -4.0), @as(T, -4.0)) == -4.0);
try expect(copysign(@as(T, 5.0), @as(T, -500.0)) == -5.0);
try expect(copysign(math.inf(T), @as(T, -0.0)) == -math.inf(T));
try expect(copysign(@as(T, 6.0), -math.nan(T)) == -6.0);
}
}pub fn copysign(magnitude: anytype, sign: @TypeOf(magnitude)) @TypeOf(magnitude) {
const T = @TypeOf(magnitude);
const TBits = std.meta.Int(.unsigned, @typeInfo(T).float.bits);
const sign_bit_mask = @as(TBits, 1) << (@bitSizeOf(T) - 1);
const mag = @as(TBits, @bitCast(magnitude)) & ~sign_bit_mask;
const sgn = @as(TBits, @bitCast(sign)) & sign_bit_mask;
return @as(T, @bitCast(mag | sgn));
}pub fn isFinite(x: anytype) boolReturns whether x is a finite value.
test isFinite {
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
// normals
try expect(isFinite(@as(T, 1.0)));
try expect(isFinite(-@as(T, 1.0)));
// zero & subnormals
try expect(isFinite(@as(T, 0.0)));
try expect(isFinite(@as(T, -0.0)));
try expect(isFinite(math.floatTrueMin(T)));
// other float limits
try expect(isFinite(math.floatMin(T)));
try expect(isFinite(math.floatMax(T)));
// inf & nan
try expect(!isFinite(math.inf(T)));
try expect(!isFinite(-math.inf(T)));
try expect(!isFinite(math.nan(T)));
try expect(!isFinite(-math.nan(T)));
}
}pub inline fn isInf(x: anytype) boolReturns whether x is an infinity, ignoring sign.
pub inline fn isPositiveInf(x: anytype) boolReturns whether x is an infinity with a positive sign.
test isPositiveInf {
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
try expect(!isPositiveInf(@as(T, 0.0)));
try expect(!isPositiveInf(@as(T, -0.0)));
try expect(isPositiveInf(math.inf(T)));
try expect(!isPositiveInf(-math.inf(T)));
try expect(!isInf(math.nan(T)));
try expect(!isInf(-math.nan(T)));
}
}pub inline fn isNegativeInf(x: anytype) boolReturns whether x is an infinity with a negative sign.
test isNegativeInf {
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
try expect(!isNegativeInf(@as(T, 0.0)));
try expect(!isNegativeInf(@as(T, -0.0)));
try expect(!isNegativeInf(math.inf(T)));
try expect(isNegativeInf(-math.inf(T)));
try expect(!isInf(math.nan(T)));
try expect(!isInf(-math.nan(T)));
}
}pub inline fn isPositiveZero(x: anytype) boolReturns whether x is positive zero.
test isPositiveZero {
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
try expect(isPositiveZero(@as(T, 0.0)));
try expect(!isPositiveZero(@as(T, -0.0)));
try expect(!isPositiveZero(math.floatMin(T)));
try expect(!isPositiveZero(math.floatMax(T)));
try expect(!isPositiveZero(math.inf(T)));
try expect(!isPositiveZero(-math.inf(T)));
}
}pub inline fn isNegativeZero(x: anytype) boolReturns whether x is negative zero.
test isNegativeZero {
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
try expect(isNegativeZero(@as(T, -0.0)));
try expect(!isNegativeZero(@as(T, 0.0)));
try expect(!isNegativeZero(math.floatMin(T)));
try expect(!isNegativeZero(math.floatMax(T)));
try expect(!isNegativeZero(math.inf(T)));
try expect(!isNegativeZero(-math.inf(T)));
}
}pub fn isNormal(x: anytype) boolReturns whether x is neither zero, subnormal, infinity, or NaN.
test isNormal {
// TODO add `c_longdouble' when math.inf(T) supports it
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
const TBits = std.meta.Int(.unsigned, @bitSizeOf(T));
// normals
try expect(isNormal(@as(T, 1.0)));
try expect(isNormal(math.floatMin(T)));
try expect(isNormal(math.floatMax(T)));
// subnormals
try expect(!isNormal(@as(T, -0.0)));
try expect(!isNormal(@as(T, 0.0)));
try expect(!isNormal(@as(T, math.floatTrueMin(T))));
// largest subnormal
try expect(!isNormal(@as(T, @bitCast(~(~@as(TBits, 0) << math.floatFractionalBits(T))))));
// non-finite numbers
try expect(!isNormal(-math.inf(T)));
try expect(!isNormal(math.inf(T)));
try expect(!isNormal(math.nan(T)));
// overflow edge-case (described in implementation, also see #10133)
try expect(!isNormal(@as(T, @bitCast(~@as(TBits, 0)))));
}
}pub fn isNormal(x: anytype) bool {
const T = @TypeOf(x);
const TBits = std.meta.Int(.unsigned, @typeInfo(T).float.bits);
const increment_exp = 1 << math.floatMantissaBits(T);
const remove_sign = ~@as(TBits, 0) >> 1;
// We add 1 to the exponent, and if it overflows to 0 or becomes 1,
// then it was all zeroes (subnormal) or all ones (special, inf/nan).
// The sign bit is removed because all ones would overflow into it.
// For f80, even though it has an explicit integer part stored,
// the exponent effectively takes priority if mismatching.
const value = @as(TBits, @bitCast(x)) +% increment_exp;
return value & remove_sign >= (increment_exp << 1);
}pub fn nextAfter(comptime T: type, x: T, y: T) TReturns the next representable value after x in the direction of y.
Special cases:
x == y, y is returned.x or y is a NaN, a NaN is returned.x == 0.0 and @abs(y) > 0.0, the smallest subnormal number with the sign of
y is returned.T: typex: Ty: Tpub fn nextAfter(comptime T: type, x: T, y: T) T {
return switch (@typeInfo(T)) {
.int, .comptime_int => nextAfterInt(T, x, y),
.float => nextAfterFloat(T, x, y),
else => @compileError("expected int or non-comptime float, found '" ++ @typeName(T) ++ "'"),
};
}pub fn signbit(x: anytype) boolReturns whether x is negative or negative 0.
test signbit {
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
try expect(!signbit(@as(T, 0.0)));
try expect(!signbit(@as(T, 1.0)));
try expect(signbit(@as(T, -2.0)));
try expect(signbit(@as(T, -0.0)));
try expect(!signbit(math.inf(T)));
try expect(signbit(-math.inf(T)));
try expect(!signbit(math.nan(T)));
try expect(signbit(-math.nan(T)));
}
}pub fn ldexp(x: anytype, n: i32) @TypeOf(x)Returns x * 2^n.
n: i32test ldexp {
// subnormals
try expect(ldexp(@as(f16, 0x1.1FFp14), -14 - 9 - 15) == math.floatTrueMin(f16));
try expect(ldexp(@as(f32, 0x1.3FFFFFp-1), -126 - 22) == math.floatTrueMin(f32));
try expect(ldexp(@as(f64, 0x1.7FFFFFFFFFFFFp-1), -1022 - 51) == math.floatTrueMin(f64));
try expect(ldexp(@as(f80, 0x1.7FFFFFFFFFFFFFFEp-1), -16382 - 62) == math.floatTrueMin(f80));
try expect(ldexp(@as(f128, 0x1.7FFFFFFFFFFFFFFFFFFFFFFFFFFFp-1), -16382 - 111) == math.floatTrueMin(f128));
try expect(ldexp(math.floatMax(f32), -128 - 149) > 0.0);
try expect(ldexp(math.floatMax(f32), -128 - 149 - 1) == 0.0);
@setEvalBranchQuota(10_000);
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
const fractional_bits = math.floatFractionalBits(T);
const min_exponent = math.floatExponentMin(T);
const max_exponent = math.floatExponentMax(T);
const exponent_bias = max_exponent;
// basic usage
try expect(ldexp(@as(T, 1.5), 4) == 24.0);
// normals -> subnormals
try expect(math.isNormal(ldexp(@as(T, 1.0), min_exponent)));
try expect(!math.isNormal(ldexp(@as(T, 1.0), min_exponent - 1)));
// normals -> zero
try expect(ldexp(@as(T, 1.0), min_exponent - fractional_bits) > 0.0);
try expect(ldexp(@as(T, 1.0), min_exponent - fractional_bits - 1) == 0.0);
// subnormals -> zero
try expect(ldexp(math.floatTrueMin(T), 0) > 0.0);
try expect(ldexp(math.floatTrueMin(T), -1) == 0.0);
// Multiplications might flush the denormals to zero, esp. at
// runtime, so we manually construct the constants here instead.
const Z = std.meta.Int(.unsigned, @bitSizeOf(T));
const EightTimesTrueMin = @as(T, @bitCast(@as(Z, 8)));
const TwoTimesTrueMin = @as(T, @bitCast(@as(Z, 2)));
// subnormals -> subnormals
try expect(ldexp(math.floatTrueMin(T), 3) == EightTimesTrueMin);
try expect(ldexp(EightTimesTrueMin, -2) == TwoTimesTrueMin);
try expect(ldexp(EightTimesTrueMin, -3) == math.floatTrueMin(T));
// subnormals -> normals (+)
try expect(ldexp(math.floatTrueMin(T), fractional_bits) == math.floatMin(T));
try expect(ldexp(math.floatTrueMin(T), fractional_bits - 1) == math.floatMin(T) * 0.5);
// subnormals -> normals (-)
try expect(ldexp(-math.floatTrueMin(T), fractional_bits) == -math.floatMin(T));
try expect(ldexp(-math.floatTrueMin(T), fractional_bits - 1) == -math.floatMin(T) * 0.5);
// subnormals -> float limits (+inf)
try expect(math.isFinite(ldexp(math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits - 1)));
try expect(ldexp(math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits) == math.inf(T));
// subnormals -> float limits (-inf)
try expect(math.isFinite(ldexp(-math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits - 1)));
try expect(ldexp(-math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits) == -math.inf(T));
// infinity -> infinity
try expect(ldexp(math.inf(T), math.maxInt(i32)) == math.inf(T));
try expect(ldexp(math.inf(T), math.minInt(i32)) == math.inf(T));
try expect(ldexp(math.inf(T), max_exponent) == math.inf(T));
try expect(ldexp(math.inf(T), min_exponent) == math.inf(T));
try expect(ldexp(-math.inf(T), math.maxInt(i32)) == -math.inf(T));
try expect(ldexp(-math.inf(T), math.minInt(i32)) == -math.inf(T));
// extremely large n
try expect(ldexp(math.floatMax(T), math.maxInt(i32)) == math.inf(T));
try expect(ldexp(math.floatMax(T), -math.maxInt(i32)) == 0.0);
try expect(ldexp(math.floatMax(T), math.minInt(i32)) == 0.0);
try expect(ldexp(math.floatTrueMin(T), math.maxInt(i32)) == math.inf(T));
try expect(ldexp(math.floatTrueMin(T), -math.maxInt(i32)) == 0.0);
try expect(ldexp(math.floatTrueMin(T), math.minInt(i32)) == 0.0);
}
}pub fn ldexp(x: anytype, n: i32) @TypeOf(x) {
const T = @TypeOf(x);
const TBits = std.meta.Int(.unsigned, @typeInfo(T).float.bits);
const exponent_bits = math.floatExponentBits(T);
const mantissa_bits = math.floatMantissaBits(T);
const fractional_bits = math.floatFractionalBits(T);
const max_biased_exponent = 2 * math.floatExponentMax(T);
const mantissa_mask = @as(TBits, (1 << mantissa_bits) - 1);
const repr = @as(TBits, @bitCast(x));
const sign_bit = repr & (1 << (exponent_bits + mantissa_bits));
if (math.isNan(x) or !math.isFinite(x))
return x;
var exponent: i32 = @as(i32, @intCast((repr << 1) >> (mantissa_bits + 1)));
if (exponent == 0)
exponent += (@as(i32, exponent_bits) + @intFromBool(T == f80)) - @clz(repr << 1);
if (n >= 0) {
if (n > max_biased_exponent - exponent) {
// Overflow. Return +/- inf
return @as(T, @bitCast(@as(TBits, @bitCast(math.inf(T))) | sign_bit));
} else if (exponent + n <= 0) {
// Result is subnormal
return @as(T, @bitCast((repr << @as(Log2Int(TBits), @intCast(n))) | sign_bit));
} else if (exponent <= 0) {
// Result is normal, but needs shifting
var result = @as(TBits, @intCast(n + exponent)) << mantissa_bits;
result |= (repr << @as(Log2Int(TBits), @intCast(1 - exponent))) & mantissa_mask;
return @as(T, @bitCast(result | sign_bit));
}
// Result needs no shifting
return @as(T, @bitCast(repr + (@as(TBits, @intCast(n)) << mantissa_bits)));
} else {
if (n <= -exponent) {
if (n < -(mantissa_bits + exponent))
return @as(T, @bitCast(sign_bit)); // Severe underflow. Return +/- 0
// Result underflowed, we need to shift and round
const shift = @as(Log2Int(TBits), @intCast(@min(-n, -(exponent + n) + 1)));
const exact_tie: bool = @ctz(repr) == shift - 1;
var result = repr & mantissa_mask;
if (T != f80) // Include integer bit
result |= @as(TBits, @intFromBool(exponent > 0)) << fractional_bits;
result = @as(TBits, @intCast((result >> (shift - 1))));
// Round result, including round-to-even for exact ties
result = ((result + 1) >> 1) & ~@as(TBits, @intFromBool(exact_tie));
return @as(T, @bitCast(result | sign_bit));
}
// Result is exact, and needs no shifting
return @as(T, @bitCast(repr - (@as(TBits, @intCast(-n)) << mantissa_bits)));
}
}pub fn ldexp(x: anytype, n: i32) @TypeOf(x)Returns x * 2^n.
n: i32test ldexp {
// subnormals
try expect(ldexp(@as(f16, 0x1.1FFp14), -14 - 9 - 15) == math.floatTrueMin(f16));
try expect(ldexp(@as(f32, 0x1.3FFFFFp-1), -126 - 22) == math.floatTrueMin(f32));
try expect(ldexp(@as(f64, 0x1.7FFFFFFFFFFFFp-1), -1022 - 51) == math.floatTrueMin(f64));
try expect(ldexp(@as(f80, 0x1.7FFFFFFFFFFFFFFEp-1), -16382 - 62) == math.floatTrueMin(f80));
try expect(ldexp(@as(f128, 0x1.7FFFFFFFFFFFFFFFFFFFFFFFFFFFp-1), -16382 - 111) == math.floatTrueMin(f128));
try expect(ldexp(math.floatMax(f32), -128 - 149) > 0.0);
try expect(ldexp(math.floatMax(f32), -128 - 149 - 1) == 0.0);
@setEvalBranchQuota(10_000);
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
const fractional_bits = math.floatFractionalBits(T);
const min_exponent = math.floatExponentMin(T);
const max_exponent = math.floatExponentMax(T);
const exponent_bias = max_exponent;
// basic usage
try expect(ldexp(@as(T, 1.5), 4) == 24.0);
// normals -> subnormals
try expect(math.isNormal(ldexp(@as(T, 1.0), min_exponent)));
try expect(!math.isNormal(ldexp(@as(T, 1.0), min_exponent - 1)));
// normals -> zero
try expect(ldexp(@as(T, 1.0), min_exponent - fractional_bits) > 0.0);
try expect(ldexp(@as(T, 1.0), min_exponent - fractional_bits - 1) == 0.0);
// subnormals -> zero
try expect(ldexp(math.floatTrueMin(T), 0) > 0.0);
try expect(ldexp(math.floatTrueMin(T), -1) == 0.0);
// Multiplications might flush the denormals to zero, esp. at
// runtime, so we manually construct the constants here instead.
const Z = std.meta.Int(.unsigned, @bitSizeOf(T));
const EightTimesTrueMin = @as(T, @bitCast(@as(Z, 8)));
const TwoTimesTrueMin = @as(T, @bitCast(@as(Z, 2)));
// subnormals -> subnormals
try expect(ldexp(math.floatTrueMin(T), 3) == EightTimesTrueMin);
try expect(ldexp(EightTimesTrueMin, -2) == TwoTimesTrueMin);
try expect(ldexp(EightTimesTrueMin, -3) == math.floatTrueMin(T));
// subnormals -> normals (+)
try expect(ldexp(math.floatTrueMin(T), fractional_bits) == math.floatMin(T));
try expect(ldexp(math.floatTrueMin(T), fractional_bits - 1) == math.floatMin(T) * 0.5);
// subnormals -> normals (-)
try expect(ldexp(-math.floatTrueMin(T), fractional_bits) == -math.floatMin(T));
try expect(ldexp(-math.floatTrueMin(T), fractional_bits - 1) == -math.floatMin(T) * 0.5);
// subnormals -> float limits (+inf)
try expect(math.isFinite(ldexp(math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits - 1)));
try expect(ldexp(math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits) == math.inf(T));
// subnormals -> float limits (-inf)
try expect(math.isFinite(ldexp(-math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits - 1)));
try expect(ldexp(-math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits) == -math.inf(T));
// infinity -> infinity
try expect(ldexp(math.inf(T), math.maxInt(i32)) == math.inf(T));
try expect(ldexp(math.inf(T), math.minInt(i32)) == math.inf(T));
try expect(ldexp(math.inf(T), max_exponent) == math.inf(T));
try expect(ldexp(math.inf(T), min_exponent) == math.inf(T));
try expect(ldexp(-math.inf(T), math.maxInt(i32)) == -math.inf(T));
try expect(ldexp(-math.inf(T), math.minInt(i32)) == -math.inf(T));
// extremely large n
try expect(ldexp(math.floatMax(T), math.maxInt(i32)) == math.inf(T));
try expect(ldexp(math.floatMax(T), -math.maxInt(i32)) == 0.0);
try expect(ldexp(math.floatMax(T), math.minInt(i32)) == 0.0);
try expect(ldexp(math.floatTrueMin(T), math.maxInt(i32)) == math.inf(T));
try expect(ldexp(math.floatTrueMin(T), -math.maxInt(i32)) == 0.0);
try expect(ldexp(math.floatTrueMin(T), math.minInt(i32)) == 0.0);
}
}pub fn ldexp(x: anytype, n: i32) @TypeOf(x) {
const T = @TypeOf(x);
const TBits = std.meta.Int(.unsigned, @typeInfo(T).float.bits);
const exponent_bits = math.floatExponentBits(T);
const mantissa_bits = math.floatMantissaBits(T);
const fractional_bits = math.floatFractionalBits(T);
const max_biased_exponent = 2 * math.floatExponentMax(T);
const mantissa_mask = @as(TBits, (1 << mantissa_bits) - 1);
const repr = @as(TBits, @bitCast(x));
const sign_bit = repr & (1 << (exponent_bits + mantissa_bits));
if (math.isNan(x) or !math.isFinite(x))
return x;
var exponent: i32 = @as(i32, @intCast((repr << 1) >> (mantissa_bits + 1)));
if (exponent == 0)
exponent += (@as(i32, exponent_bits) + @intFromBool(T == f80)) - @clz(repr << 1);
if (n >= 0) {
if (n > max_biased_exponent - exponent) {
// Overflow. Return +/- inf
return @as(T, @bitCast(@as(TBits, @bitCast(math.inf(T))) | sign_bit));
} else if (exponent + n <= 0) {
// Result is subnormal
return @as(T, @bitCast((repr << @as(Log2Int(TBits), @intCast(n))) | sign_bit));
} else if (exponent <= 0) {
// Result is normal, but needs shifting
var result = @as(TBits, @intCast(n + exponent)) << mantissa_bits;
result |= (repr << @as(Log2Int(TBits), @intCast(1 - exponent))) & mantissa_mask;
return @as(T, @bitCast(result | sign_bit));
}
// Result needs no shifting
return @as(T, @bitCast(repr + (@as(TBits, @intCast(n)) << mantissa_bits)));
} else {
if (n <= -exponent) {
if (n < -(mantissa_bits + exponent))
return @as(T, @bitCast(sign_bit)); // Severe underflow. Return +/- 0
// Result underflowed, we need to shift and round
const shift = @as(Log2Int(TBits), @intCast(@min(-n, -(exponent + n) + 1)));
const exact_tie: bool = @ctz(repr) == shift - 1;
var result = repr & mantissa_mask;
if (T != f80) // Include integer bit
result |= @as(TBits, @intFromBool(exponent > 0)) << fractional_bits;
result = @as(TBits, @intCast((result >> (shift - 1))));
// Round result, including round-to-even for exact ties
result = ((result + 1) >> 1) & ~@as(TBits, @intFromBool(exact_tie));
return @as(T, @bitCast(result | sign_bit));
}
// Result is exact, and needs no shifting
return @as(T, @bitCast(repr - (@as(TBits, @intCast(-n)) << mantissa_bits)));
}
}pub fn pow(comptime T: type, x: T, y: T) TReturns x raised to the power of y (x^y).
Special Cases:
T: typex: Ty: Ttest pow {
const epsilon = 0.000001;
try expect(math.approxEqAbs(f32, pow(f32, 0.0, 3.3), 0.0, epsilon));
try expect(math.approxEqAbs(f32, pow(f32, 0.8923, 3.3), 0.686572, epsilon));
try expect(math.approxEqAbs(f32, pow(f32, 0.2, 3.3), 0.004936, epsilon));
try expect(math.approxEqAbs(f32, pow(f32, 1.5, 3.3), 3.811546, epsilon));
try expect(math.approxEqAbs(f32, pow(f32, 37.45, 3.3), 155736.703125, epsilon));
try expect(math.approxEqAbs(f32, pow(f32, 89.123, 3.3), 2722489.5, epsilon));
try expect(math.approxEqAbs(f64, pow(f64, 0.0, 3.3), 0.0, epsilon));
try expect(math.approxEqAbs(f64, pow(f64, 0.8923, 3.3), 0.686572, epsilon));
try expect(math.approxEqAbs(f64, pow(f64, 0.2, 3.3), 0.004936, epsilon));
try expect(math.approxEqAbs(f64, pow(f64, 1.5, 3.3), 3.811546, epsilon));
try expect(math.approxEqAbs(f64, pow(f64, 37.45, 3.3), 155736.7160616, epsilon));
try expect(math.approxEqAbs(f64, pow(f64, 89.123, 3.3), 2722490.231436, epsilon));
}pub fn pow(comptime T: type, x: T, y: T) T {
if (@typeInfo(T) == .int) {
return math.powi(T, x, y) catch unreachable;
}
if (T != f32 and T != f64) {
@compileError("pow not implemented for " ++ @typeName(T));
}
// pow(x, +-0) = 1 for all x
// pow(1, y) = 1 for all y
if (y == 0 or x == 1) {
return 1;
}
// pow(nan, y) = nan for all y
// pow(x, nan) = nan for all x
if (math.isNan(x) or math.isNan(y)) {
@branchHint(.unlikely);
return math.nan(T);
}
// pow(x, 1) = x for all x
if (y == 1) {
return x;
}
if (x == 0) {
if (y < 0) {
// pow(+-0, y) = +- 0 for y an odd integer
if (isOddInteger(y)) {
return math.copysign(math.inf(T), x);
}
// pow(+-0, y) = +inf for y an even integer
else {
return math.inf(T);
}
} else {
if (isOddInteger(y)) {
return x;
} else {
return 0;
}
}
}
if (math.isInf(y)) {
// pow(-1, inf) = 1 for all x
if (x == -1) {
return 1.0;
}
// pow(x, +inf) = +0 for |x| < 1
// pow(x, -inf) = +0 for |x| > 1
else if ((@abs(x) < 1) == math.isPositiveInf(y)) {
return 0;
}
// pow(x, -inf) = +inf for |x| < 1
// pow(x, +inf) = +inf for |x| > 1
else {
return math.inf(T);
}
}
if (math.isInf(x)) {
if (math.isNegativeInf(x)) {
return pow(T, 1 / x, -y);
}
// pow(+inf, y) = +0 for y < 0
else if (y < 0) {
return 0;
}
// pow(+inf, y) = +0 for y > 0
else if (y > 0) {
return math.inf(T);
}
}
// special case sqrt
if (y == 0.5) {
return @sqrt(x);
}
if (y == -0.5) {
return 1 / @sqrt(x);
}
const r1 = math.modf(@abs(y));
var yi = r1.ipart;
var yf = r1.fpart;
if (yf != 0 and x < 0) {
return math.nan(T);
}
if (yi >= 1 << (@typeInfo(T).float.bits - 1)) {
return @exp(y * @log(x));
}
// a = a1 * 2^ae
var a1: T = 1.0;
var ae: i32 = 0;
// a *= x^yf
if (yf != 0) {
if (yf > 0.5) {
yf -= 1;
yi += 1;
}
a1 = @exp(yf * @log(x));
}
// a *= x^yi
const r2 = math.frexp(x);
var xe = r2.exponent;
var x1 = r2.significand;
var i = @as(std.meta.Int(.signed, @typeInfo(T).float.bits), @intFromFloat(yi));
while (i != 0) : (i >>= 1) {
const overflow_shift = math.floatExponentBits(T) + 1;
if (xe < -(1 << overflow_shift) or (1 << overflow_shift) < xe) {
// catch xe before it overflows the left shift below
// Since i != 0 it has at least one bit still set, so ae will accumulate xe
// on at least one more iteration, ae += xe is a lower bound on ae
// the lower bound on ae exceeds the size of a float exp
// so the final call to Ldexp will produce under/overflow (0/Inf)
ae += xe;
break;
}
if (i & 1 == 1) {
a1 *= x1;
ae += xe;
}
x1 *= x1;
xe <<= 1;
if (x1 < 0.5) {
x1 += x1;
xe -= 1;
}
}
// a *= a1 * 2^ae
if (y < 0) {
a1 = 1 / a1;
ae = -ae;
}
return math.scalbn(a1, ae);
}pub fn powi(comptime T: type, x: T, y: T) (error{ Overflow, Underflow, }!T)Returns the power of x raised by the integer y (x^y).
Errors:
T: typex: Ty: Ttest powi {
try testing.expectError(error.Overflow, powi(i8, -66, 6));
try testing.expectError(error.Overflow, powi(i16, -13, 13));
try testing.expectError(error.Overflow, powi(i32, -32, 21));
try testing.expectError(error.Overflow, powi(i64, -24, 61));
try testing.expectError(error.Overflow, powi(i17, -15, 15));
try testing.expectError(error.Overflow, powi(i42, -6, 40));
try testing.expect((try powi(i8, -5, 3)) == -125);
try testing.expect((try powi(i16, -16, 3)) == -4096);
try testing.expect((try powi(i32, -91, 3)) == -753571);
try testing.expect((try powi(i64, -36, 6)) == 2176782336);
try testing.expect((try powi(i17, -2, 15)) == -32768);
try testing.expect((try powi(i42, -5, 7)) == -78125);
try testing.expect((try powi(u8, 6, 2)) == 36);
try testing.expect((try powi(u16, 5, 4)) == 625);
try testing.expect((try powi(u32, 12, 6)) == 2985984);
try testing.expect((try powi(u64, 34, 2)) == 1156);
try testing.expect((try powi(u17, 16, 3)) == 4096);
try testing.expect((try powi(u42, 34, 6)) == 1544804416);
try testing.expectError(error.Overflow, powi(i8, 120, 7));
try testing.expectError(error.Overflow, powi(i16, 73, 15));
try testing.expectError(error.Overflow, powi(i32, 23, 31));
try testing.expectError(error.Overflow, powi(i64, 68, 61));
try testing.expectError(error.Overflow, powi(i17, 15, 15));
try testing.expectError(error.Overflow, powi(i42, 121312, 41));
try testing.expectError(error.Overflow, powi(u8, 123, 7));
try testing.expectError(error.Overflow, powi(u16, 2313, 15));
try testing.expectError(error.Overflow, powi(u32, 8968, 31));
try testing.expectError(error.Overflow, powi(u64, 2342, 63));
try testing.expectError(error.Overflow, powi(u17, 2723, 16));
try testing.expectError(error.Overflow, powi(u42, 8234, 41));
const minInt = std.math.minInt;
try testing.expect((try powi(i8, -2, 7)) == minInt(i8));
try testing.expect((try powi(i16, -2, 15)) == minInt(i16));
try testing.expect((try powi(i32, -2, 31)) == minInt(i32));
try testing.expect((try powi(i64, -2, 63)) == minInt(i64));
try testing.expectError(error.Underflow, powi(i8, 6, -2));
try testing.expectError(error.Underflow, powi(i16, 5, -4));
try testing.expectError(error.Underflow, powi(i32, 12, -6));
try testing.expectError(error.Underflow, powi(i64, 34, -2));
try testing.expectError(error.Underflow, powi(i17, 16, -3));
try testing.expectError(error.Underflow, powi(i42, 34, -6));
}pub fn powi(comptime T: type, x: T, y: T) (error{
Overflow,
Underflow,
}!T) {
const bit_size = @typeInfo(T).int.bits;
// `y & 1 == 0` won't compile when `does_one_overflow`.
const does_one_overflow = math.maxInt(T) < 1;
const is_y_even = !does_one_overflow and y & 1 == 0;
if (x == 1 or y == 0 or (x == -1 and is_y_even)) {
if (does_one_overflow) {
return error.Overflow;
} else {
return 1;
}
}
if (x == -1) {
return -1;
}
if (x == 0) {
if (y > 0) {
return 0;
} else {
// Infinity/NaN, not overflow in strict sense
return error.Overflow;
}
}
// x >= 2 or x <= -2 from this point
if (y >= bit_size) {
return error.Overflow;
}
if (y < 0) {
return error.Underflow;
}
// invariant :
// return value = powi(T, base, exp) * acc;
var base = x;
var exp = y;
var acc: T = if (does_one_overflow) unreachable else 1;
while (exp > 1) {
if (exp & 1 == 1) {
const ov = @mulWithOverflow(acc, base);
if (ov[1] != 0) return error.Overflow;
acc = ov[0];
}
exp >>= 1;
const ov = @mulWithOverflow(base, base);
if (ov[1] != 0) return error.Overflow;
base = ov[0];
}
if (exp == 1) {
const ov = @mulWithOverflow(acc, base);
if (ov[1] != 0) return error.Overflow;
acc = ov[0];
}
return acc;
}pub fn sqrt(x: anytype) Sqrt(@TypeOf(x))Returns the square root of x.
Special Cases:
pub fn sqrt(x: anytype) Sqrt(@TypeOf(x)) {
const T = @TypeOf(x);
switch (@typeInfo(T)) {
.float, .comptime_float => return @sqrt(x),
.comptime_int => comptime {
if (x > maxInt(u128)) {
@compileError("sqrt not implemented for comptime_int greater than 128 bits");
}
if (x < 0) {
@compileError("sqrt on negative number");
}
return @as(T, sqrt_int(u128, x));
},
.int => |IntType| switch (IntType.signedness) {
.signed => @compileError("sqrt not implemented for signed integers"),
.unsigned => return sqrt_int(T, x),
},
else => @compileError("sqrt not implemented for " ++ @typeName(T)),
}
}pub fn cbrt(x: anytype) @TypeOf(x)Returns the cube root of x.
Special Cases:
pub fn acos(x: anytype) @TypeOf(x)Returns the arc-cosine of x.
Special cases:
pub fn asin(x: anytype) @TypeOf(x)Returns the arc-sin of x.
Special Cases:
pub fn atan(x: anytype) @TypeOf(x)Returns the arc-tangent of x.
Special Cases:
pub fn atan2(y: anytype, x: anytype) @TypeOf(x, y)Returns the arc-tangent of y/x.
Special Cases:
| y | x | radians |
|---|---|---|
| fin | nan | nan |
| nan | fin | nan |
| +0 | >=+0 | +0 |
| -0 | >=+0 | -0 |
| +0 | <=-0 | pi |
| -0 | <=-0 | -pi |
| pos | 0 | +pi/2 |
| neg | 0 | -pi/2 |
| +inf | +inf | +pi/4 |
| -inf | +inf | -pi/4 |
| +inf | -inf | 3pi/4 |
| -inf | -inf | -3pi/4 |
| fin | +inf | 0 |
| pos | -inf | +pi |
| neg | -inf | -pi |
| +inf | fin | +pi/2 |
| -inf | fin | -pi/2 |
pub fn hypot(x: anytype, y: anytype) @TypeOf(x, y)Returns sqrt(x * x + y * y), avoiding unnecessary overflow and underflow.
Special Cases:
| x | y | hypot |
|---|---|---|
| +-inf | any | +inf |
| any | +-inf | +inf |
| nan | fin | nan |
| fin | nan | nan |
pub fn hypot(x: anytype, y: anytype) @TypeOf(x, y) {
const T = @TypeOf(x, y);
switch (@typeInfo(T)) {
.float => {},
.comptime_float => return @sqrt(x * x + y * y),
else => @compileError("hypot not implemented for " ++ @typeName(T)),
}
const lower = @sqrt(floatMin(T));
const upper = @sqrt(floatMax(T) / 2);
const incre = @sqrt(floatEps(T) / 2);
const scale = floatEpsAt(T, incre);
const hypfn = if (emulateFma(T)) hypotUnfused else hypotFused;
var major: T = x;
var minor: T = y;
if (isInf(major) or isInf(minor)) return inf(T);
if (isNan(major) or isNan(minor)) return nan(T);
if (T == f16) return @floatCast(@sqrt(@mulAdd(f32, x, x, @as(f32, y) * y)));
if (T == f32) return @floatCast(@sqrt(@mulAdd(f64, x, x, @as(f64, y) * y)));
major = @abs(major);
minor = @abs(minor);
if (minor > major) {
const tempo = major;
major = minor;
minor = tempo;
}
if (major * incre >= minor) return major;
if (major > upper) return hypfn(T, major * scale, minor * scale) / scale;
if (minor < lower) return hypfn(T, major / scale, minor / scale) * scale;
return hypfn(T, major, minor);
}pub fn expm1(x: anytype) @TypeOf(x)Returns e raised to the power of x, minus 1 (e^x - 1). This is more accurate than exp(e, x) - 1 when x is near 0.
Special Cases:
pub fn ilogb(x: anytype) i32Returns the binary exponent of x as an integer.
Special Cases:
pub fn ilogb(x: anytype) i32 {
const T = @TypeOf(x);
return ilogbX(T, x);
}pub fn log(comptime T: type, base: T, x: T) TReturns the logarithm of x for the provided base.
T: typebase: Tx: Tpub fn log(comptime T: type, base: T, x: T) T {
if (base == 2) {
return math.log2(x);
} else if (base == 10) {
return math.log10(x);
} else if ((@typeInfo(T) == .float or @typeInfo(T) == .comptime_float) and base == math.e) {
return @log(x);
}
const float_base = math.lossyCast(f64, base);
switch (@typeInfo(T)) {
.comptime_float => {
return @as(comptime_float, @log(@as(f64, x)) / @log(float_base));
},
.comptime_int => {
return @as(comptime_int, math.log_int(comptime_int, base, x));
},
.int => |IntType| switch (IntType.signedness) {
.signed => @compileError("log not implemented for signed integers"),
.unsigned => return @as(T, math.log_int(T, base, x)),
},
.float => {
switch (T) {
f32 => return @as(f32, @floatCast(@log(@as(f64, x)) / @log(float_base))),
f64 => return @log(x) / @log(float_base),
else => @compileError("log not implemented for " ++ @typeName(T)),
}
},
else => {
@compileError("log expects integer or float, found '" ++ @typeName(T) ++ "'");
},
}
}pub fn log2(x: anytype) @TypeOf(x)Returns the base-2 logarithm of x.
Special Cases:
test log2 {
// https://github.com/ziglang/zig/issues/13703
if (builtin.cpu.arch == .aarch64 and builtin.os.tag == .windows) return error.SkipZigTest;
try expect(log2(@as(f32, 0.2)) == @log2(0.2));
try expect(log2(@as(f64, 0.2)) == @log2(0.2));
comptime {
try expect(log2(1) == 0);
try expect(log2(15) == 3);
try expect(log2(16) == 4);
try expect(log2(1 << 4073) == 4073);
}
}pub fn log2(x: anytype) @TypeOf(x) {
const T = @TypeOf(x);
switch (@typeInfo(T)) {
.comptime_float => {
return @as(comptime_float, @log2(x));
},
.float => return @log2(x),
.comptime_int => comptime {
var x_shifted = x;
// First, calculate floorPowerOfTwo(x)
var shift_amt = 1;
while (x_shifted >> (shift_amt << 1) != 0) shift_amt <<= 1;
// Answer is in the range [shift_amt, 2 * shift_amt - 1]
// We can find it in O(log(N)) using binary search.
var result = 0;
while (shift_amt != 0) : (shift_amt >>= 1) {
if (x_shifted >> shift_amt != 0) {
x_shifted >>= shift_amt;
result += shift_amt;
}
}
return result;
},
.int => |IntType| switch (IntType.signedness) {
.signed => @compileError("log2 not implemented for signed integers"),
.unsigned => return math.log2_int(T, x),
},
else => @compileError("log2 not implemented for " ++ @typeName(T)),
}
}pub fn log10(x: anytype) @TypeOf(x)Returns the base-10 logarithm of x.
Special Cases:
pub fn log10(x: anytype) @TypeOf(x) {
const T = @TypeOf(x);
switch (@typeInfo(T)) {
.comptime_float => {
return @as(comptime_float, @log10(x));
},
.float => return @log10(x),
.comptime_int => {
return @as(comptime_int, @floor(@log10(@as(f64, x))));
},
.int => |IntType| switch (IntType.signedness) {
.signed => @compileError("log10 not implemented for signed integers"),
.unsigned => return log10_int(x),
},
else => @compileError("log10 not implemented for " ++ @typeName(T)),
}
}Return the log base 10 of integer value x, rounding down to the nearest integer.
test log10_int {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_llvm and comptime builtin.target.cpu.arch.isWasm()) return error.SkipZigTest; // TODO
inline for (
.{ u8, u16, u32, u64, u128, u256, u512 },
.{ 2, 4, 9, 19, 38, 77, 154 },
) |T, max_exponent| {
for (0..max_exponent + 1) |exponent_usize| {
const exponent: std.math.Log2Int(T) = @intCast(exponent_usize);
const power_of_ten = try std.math.powi(T, 10, exponent);
if (exponent > 0) {
try testing.expectEqual(exponent - 1, log10_int(power_of_ten - 9));
try testing.expectEqual(exponent - 1, log10_int(power_of_ten - 1));
}
try testing.expectEqual(exponent, log10_int(power_of_ten));
try testing.expectEqual(exponent, log10_int(power_of_ten + 1));
try testing.expectEqual(exponent, log10_int(power_of_ten + 8));
}
try testing.expectEqual(max_exponent, log10_int(@as(T, std.math.maxInt(T))));
}
}pub fn log10_int(x: anytype) std.math.Log2Int(@TypeOf(x)) {
const T = @TypeOf(x);
const OutT = std.math.Log2Int(T);
if (@typeInfo(T) != .int or @typeInfo(T).int.signedness != .unsigned)
@compileError("log10_int requires an unsigned integer, found " ++ @typeName(T));
std.debug.assert(x != 0);
const bit_size = @typeInfo(T).int.bits;
if (bit_size <= 8) {
return @as(OutT, @intCast(log10_int_u8(x)));
} else if (bit_size <= 16) {
return @as(OutT, @intCast(less_than_5(x)));
}
var val = x;
var log: u32 = 0;
inline for (0..11) |i| {
// Unnecessary branches should be removed by the compiler
if (bit_size > (1 << (11 - i)) * 5 * @log2(10.0) and val >= pow10((1 << (11 - i)) * 5)) {
const num_digits = (1 << (11 - i)) * 5;
val /= pow10(num_digits);
log += num_digits;
}
}
if (val >= pow10(5)) {
val /= pow10(5);
log += 5;
}
return @as(OutT, @intCast(log + less_than_5(@as(u32, @intCast(val)))));
}pub fn log_int(comptime T: type, base: T, x: T) Log2Int(T)Returns the logarithm of x for the provided base, rounding down to the nearest integer.
Asserts that base > 1 and x > 0.
T: typebase: Tx: Tpub fn log_int(comptime T: type, base: T, x: T) Log2Int(T) {
const valid = switch (@typeInfo(T)) {
.comptime_int => true,
.int => |IntType| IntType.signedness == .unsigned,
else => false,
};
if (!valid) @compileError("log_int requires an unsigned integer, found " ++ @typeName(T));
assert(base > 1 and x > 0);
if (base == 2) return math.log2_int(T, x);
// Let's denote by [y] the integer part of y.
// Throughout the iteration the following invariant is preserved:
// power = base ^ exponent
// Safety and termination.
//
// We never overflow inside the loop because when we enter the loop we have
// power <= [maxInt(T) / base]
// therefore
// power * base <= maxInt(T)
// is a valid multiplication for type `T` and
// exponent + 1 <= log(base, maxInt(T)) <= log2(maxInt(T)) <= maxInt(Log2Int(T))
// is a valid addition for type `Log2Int(T)`.
//
// This implies also termination because power is strictly increasing,
// hence it must eventually surpass [x / base] < maxInt(T) and we then exit the loop.
var exponent: Log2Int(T) = 0;
var power: T = 1;
while (power <= x / base) {
power *= base;
exponent += 1;
}
// If we never entered the loop we must have
// [x / base] < 1
// hence
// x <= [x / base] * base < base
// thus the result is 0. We can then return exponent, which is still 0.
//
// Otherwise, if we entered the loop at least once,
// when we exit the loop we have that power is exactly divisible by base and
// power / base <= [x / base] < power
// hence
// power <= [x / base] * base <= x < power * base
// This means that
// base^exponent <= x < base^(exponent+1)
// hence the result is exponent.
return exponent;
}pub fn log1p(x: anytype) @TypeOf(x)Returns the natural logarithm of 1 + x with greater accuracy when x is near zero.
Special Cases:
pub fn asinh(x: anytype) @TypeOf(x)Returns the hyperbolic arc-sin of x.
Special Cases:
pub fn acosh(x: anytype) @TypeOf(x)Returns the hyperbolic arc-cosine of x.
Special cases:
pub fn atanh(x: anytype) @TypeOf(x)Returns the hyperbolic arc-tangent of x.
Special Cases:
pub fn sinh(x: anytype) @TypeOf(x)Returns the hyperbolic sine of x.
Special Cases:
pub fn cosh(x: anytype) @TypeOf(x)Returns the hyperbolic cosine of x.
Special Cases:
pub fn tanh(x: anytype) @TypeOf(x)Returns the hyperbolic tangent of x.
Special Cases:
pub fn gcd(a: anytype, b: anytype) @TypeOf(a, b)Returns the greatest common divisor (GCD) of two unsigned integers (a and b) which are not both zero.
For example, the GCD of 8 and 12 is 4, that is, gcd(8, 12) == 4.
test gcd {
const expectEqual = std.testing.expectEqual;
try expectEqual(gcd(0, 5), 5);
try expectEqual(gcd(5, 0), 5);
try expectEqual(gcd(8, 12), 4);
try expectEqual(gcd(12, 8), 4);
try expectEqual(gcd(33, 77), 11);
try expectEqual(gcd(77, 33), 11);
try expectEqual(gcd(49865, 69811), 9973);
try expectEqual(gcd(300_000, 2_300_000), 100_000);
try expectEqual(gcd(90000000_000_000_000_000_000, 2), 2);
try expectEqual(gcd(@as(u80, 90000000_000_000_000_000_000), 2), 2);
try expectEqual(gcd(300_000, @as(u32, 2_300_000)), 100_000);
}pub fn gcd(a: anytype, b: anytype) @TypeOf(a, b) {
const N = switch (@TypeOf(a, b)) {
// convert comptime_int to some sized int type for @ctz
comptime_int => std.math.IntFittingRange(@min(a, b), @max(a, b)),
else => |T| T,
};
if (@typeInfo(N) != .int or @typeInfo(N).int.signedness != .unsigned) {
@compileError("`a` and `b` must be usigned integers");
}
// using an optimised form of Stein's algorithm:
// https://en.wikipedia.org/wiki/Binary_GCD_algorithm
std.debug.assert(a != 0 or b != 0);
if (a == 0) return b;
if (b == 0) return a;
var x: N = a;
var y: N = b;
const xz = @ctz(x);
const yz = @ctz(y);
const shift = @min(xz, yz);
x >>= @intCast(xz);
y >>= @intCast(yz);
var diff = y -% x;
while (diff != 0) : (diff = y -% x) {
// ctz is invariant under negation, we
// put it here to ease data dependencies,
// makes the CPU happy.
const zeros = @ctz(diff);
if (x > y) diff = -%diff;
y = @min(x, y);
x = diff >> @intCast(zeros);
}
return y << @intCast(shift);
}pub fn gamma(comptime T: type, x: T) TReturns the gamma function of x, gamma(x) = factorial(x - 1) for integer x.
Special Cases:
T: typex: Ttest gamma {
inline for (&.{ f32, f64 }) |T| {
const eps = @sqrt(std.math.floatEps(T));
try expectApproxEqRel(@as(T, 120), gamma(T, 6), eps);
try expectApproxEqRel(@as(T, 362880), gamma(T, 10), eps);
try expectApproxEqRel(@as(T, 6402373705728000), gamma(T, 19), eps);
try expectApproxEqRel(@as(T, 332.7590766955334570), gamma(T, 0.003), eps);
try expectApproxEqRel(@as(T, 1.377260301981044573), gamma(T, 0.654), eps);
try expectApproxEqRel(@as(T, 1.025393882573518478), gamma(T, 0.959), eps);
try expectApproxEqRel(@as(T, 7.361898021467681690), gamma(T, 4.16), eps);
try expectApproxEqRel(@as(T, 198337.2940287730753), gamma(T, 9.73), eps);
try expectApproxEqRel(@as(T, 113718145797241.1666), gamma(T, 17.6), eps);
try expectApproxEqRel(@as(T, -1.13860211111081424930673), gamma(T, -2.80), eps);
try expectApproxEqRel(@as(T, 0.00018573407931875070158), gamma(T, -7.74), eps);
try expectApproxEqRel(@as(T, -0.00000001647990903942825), gamma(T, -12.1), eps);
}
}pub fn gamma(comptime T: type, x: T) T {
if (T != f32 and T != f64) {
@compileError("gamma not implemented for " ++ @typeName(T));
}
// common integer case first
if (x == @trunc(x)) {
// gamma(-inf) = nan
// gamma(n) = nan for negative integers
if (x < 0) {
return std.math.nan(T);
}
// gamma(-0.0) = -inf
// gamma(+0.0) = +inf
if (x == 0) {
return 1 / x;
}
if (x < integer_result_table.len) {
const i = @as(u8, @intFromFloat(x));
return @floatCast(integer_result_table[i]);
}
}
// below this, result underflows, but has a sign
// negative for (-1, 0)
// positive for (-2, -1)
// negative for (-3, -2)
// ...
const lower_bound = if (T == f64) -184 else -42;
if (x < lower_bound) {
return if (@mod(x, 2) > 1) -0.0 else 0.0;
}
// above this, result overflows
// gamma(+inf) = +inf
const upper_bound = if (T == f64) 172 else 36;
if (x > upper_bound) {
return std.math.inf(T);
}
const abs = @abs(x);
// perfect precision here
if (abs < 0x1p-54) {
return 1 / x;
}
const base = abs + lanczos_minus_half;
const exponent = abs - 0.5;
// error of y for correction, see
// https://github.com/python/cpython/blob/5dc79e3d7f26a6a871a89ce3efc9f1bcee7bb447/Modules/mathmodule.c#L286-L324
const e = if (abs > lanczos_minus_half)
base - abs - lanczos_minus_half
else
base - lanczos_minus_half - abs;
const correction = lanczos * e / base;
const initial = series(T, abs) * @exp(-base);
// use reflection formula for negatives
if (x < 0) {
const reflected = -std.math.pi / (abs * sinpi(T, abs) * initial);
const corrected = reflected - reflected * correction;
const half_pow = std.math.pow(T, base, -0.5 * exponent);
return corrected * half_pow * half_pow;
} else {
const corrected = initial + initial * correction;
const half_pow = std.math.pow(T, base, 0.5 * exponent);
return corrected * half_pow * half_pow;
}
}pub fn lgamma(comptime T: type, x: T) TReturns the natural logarithm of the absolute value of the gamma function.
Special Cases:
T: typex: Ttest lgamma {
inline for (&.{ f32, f64 }) |T| {
const eps = @sqrt(std.math.floatEps(T));
try expectApproxEqRel(@as(T, @log(24.0)), lgamma(T, 5), eps);
try expectApproxEqRel(@as(T, @log(20922789888000.0)), lgamma(T, 17), eps);
try expectApproxEqRel(@as(T, @log(2432902008176640000.0)), lgamma(T, 21), eps);
try expectApproxEqRel(@as(T, 2.201821590438859327), lgamma(T, 0.105), eps);
try expectApproxEqRel(@as(T, 1.275416975248413231), lgamma(T, 0.253), eps);
try expectApproxEqRel(@as(T, 0.130463884049976732), lgamma(T, 0.823), eps);
try expectApproxEqRel(@as(T, 43.24395772148497989), lgamma(T, 21.3), eps);
try expectApproxEqRel(@as(T, 110.6908958012102623), lgamma(T, 41.1), eps);
try expectApproxEqRel(@as(T, 215.2123266224689711), lgamma(T, 67.4), eps);
try expectApproxEqRel(@as(T, -122.605958469563489), lgamma(T, -43.6), eps);
try expectApproxEqRel(@as(T, -278.633885462703133), lgamma(T, -81.4), eps);
try expectApproxEqRel(@as(T, -333.247676253238363), lgamma(T, -93.6), eps);
}
}pub fn lgamma(comptime T: type, x: T) T {
if (T != f32 and T != f64) {
@compileError("gamma not implemented for " ++ @typeName(T));
}
// common integer case first
if (x == @trunc(x)) {
// lgamma(-inf) = +inf
// lgamma(n) = +inf for negative integers
// lgamma(+-0.0) = +inf
if (x <= 0) {
return std.math.inf(T);
}
// lgamma(1) = +0.0
// lgamma(2) = +0.0
if (x < integer_result_table.len) {
const i = @as(u8, @intFromFloat(x));
return @log(@as(T, @floatCast(integer_result_table[i])));
}
// lgamma(+inf) = +inf
if (std.math.isPositiveInf(x)) {
return x;
}
}
const abs = @abs(x);
// perfect precision here
if (abs < 0x1p-54) {
return -@log(abs);
}
// obvious approach when overflow is not a problem
const upper_bound = if (T == f64) 128 else 26;
if (abs < upper_bound) {
return @log(@abs(gamma(T, x)));
}
const log_base = @log(abs + lanczos_minus_half) - 1;
const exponent = abs - 0.5;
const log_series = @log(series(T, abs));
const initial = exponent * log_base + log_series - lanczos;
// use reflection formula for negatives
if (x < 0) {
const reflected = std.math.pi / (abs * sinpi(T, abs));
return @log(@abs(reflected)) - initial;
}
return initial;
}pub inline fn sin(value: anytype) @TypeOf(value)Sine trigonometric function on a floating point number. Uses a dedicated hardware instruction when available. This is the same as calling the builtin @sin
pub inline fn sin(value: anytype) @TypeOf(value) {
return @sin(value);
}pub inline fn cos(value: anytype) @TypeOf(value)Cosine trigonometric function on a floating point number. Uses a dedicated hardware instruction when available. This is the same as calling the builtin @cos
pub inline fn cos(value: anytype) @TypeOf(value) {
return @cos(value);
}pub inline fn tan(value: anytype) @TypeOf(value)Tangent trigonometric function on a floating point number. Uses a dedicated hardware instruction when available. This is the same as calling the builtin @tan
pub inline fn tan(value: anytype) @TypeOf(value) {
return @tan(value);
}pub fn radiansToDegrees(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_float else @TypeOf(ang)Converts an angle in radians to degrees. T must be a float or comptime number or a vector of floats.
test radiansToDegrees {
const zero: f32 = 0;
const half_pi: f32 = pi / 2.0;
const neg_quart_pi: f32 = -pi / 4.0;
const one_pi: f32 = pi;
const two_pi: f32 = 2.0 * pi;
try std.testing.expectApproxEqAbs(@as(f32, 0), radiansToDegrees(zero), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 90), radiansToDegrees(half_pi), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, -45), radiansToDegrees(neg_quart_pi), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 180), radiansToDegrees(one_pi), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 360), radiansToDegrees(two_pi), 1e-6);
const result = radiansToDegrees(@Vector(4, f32){
half_pi,
neg_quart_pi,
one_pi,
two_pi,
});
try std.testing.expectApproxEqAbs(@as(f32, 90), result[0], 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, -45), result[1], 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 180), result[2], 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 360), result[3], 1e-6);
}pub fn radiansToDegrees(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_float else @TypeOf(ang) {
const T = @TypeOf(ang);
switch (@typeInfo(T)) {
.float, .comptime_float, .comptime_int => return ang * deg_per_rad,
.vector => |V| if (@typeInfo(V.child) == .float) return ang * @as(T, @splat(deg_per_rad)),
else => {},
}
@compileError("Input must be float or a comptime number, or a vector of floats.");
}pub fn degreesToRadians(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_float else @TypeOf(ang)Converts an angle in degrees to radians. T must be a float or comptime number or a vector of floats.
test degreesToRadians {
const ninety: f32 = 90;
const neg_two_seventy: f32 = -270;
const three_sixty: f32 = 360;
try std.testing.expectApproxEqAbs(@as(f32, pi / 2.0), degreesToRadians(ninety), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, -3 * pi / 2.0), degreesToRadians(neg_two_seventy), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 2 * pi), degreesToRadians(three_sixty), 1e-6);
const result = degreesToRadians(@Vector(3, f32){
ninety,
neg_two_seventy,
three_sixty,
});
try std.testing.expectApproxEqAbs(@as(f32, pi / 2.0), result[0], 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, -3 * pi / 2.0), result[1], 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 2 * pi), result[2], 1e-6);
}pub fn degreesToRadians(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_float else @TypeOf(ang) {
const T = @TypeOf(ang);
switch (@typeInfo(T)) {
.float, .comptime_float, .comptime_int => return ang * rad_per_deg,
.vector => |V| if (@typeInfo(V.child) == .float) return ang * @as(T, @splat(rad_per_deg)),
else => {},
}
@compileError("Input must be float or a comptime number, or a vector of floats.");
}pub inline fn exp(value: anytype) @TypeOf(value)Base-e exponential function on a floating point number. Uses a dedicated hardware instruction when available. This is the same as calling the builtin @exp
pub inline fn exp(value: anytype) @TypeOf(value) {
return @exp(value);
}pub inline fn exp2(value: anytype) @TypeOf(value)Base-2 exponential function on a floating point number. Uses a dedicated hardware instruction when available. This is the same as calling the builtin @exp2
pub inline fn exp2(value: anytype) @TypeOf(value) {
return @exp2(value);
}pub fn wrap(x: anytype, r: anytype) @TypeOf(x)Odd sawtooth function
|
/ | / /
/ |/ /
--/----/----/--
/ /| /
/ / | /
|
Limit x to the half-open interval [-r, r).
test wrap {
// Within range
try testing.expect(wrap(@as(i32, -75), @as(i32, 180)) == -75);
try testing.expect(wrap(@as(i32, -75), @as(i32, -180)) == -75);
// Below
try testing.expect(wrap(@as(i32, -225), @as(i32, 180)) == 135);
try testing.expect(wrap(@as(i32, -225), @as(i32, -180)) == 135);
// Above
try testing.expect(wrap(@as(i32, 361), @as(i32, 180)) == 1);
try testing.expect(wrap(@as(i32, 361), @as(i32, -180)) == 1);
// One period, right limit, positive r
try testing.expect(wrap(@as(i32, 180), @as(i32, 180)) == -180);
// One period, left limit, positive r
try testing.expect(wrap(@as(i32, -180), @as(i32, 180)) == -180);
// One period, right limit, negative r
try testing.expect(wrap(@as(i32, 180), @as(i32, -180)) == 180);
// One period, left limit, negative r
try testing.expect(wrap(@as(i32, -180), @as(i32, -180)) == 180);
// Two periods, right limit, positive r
try testing.expect(wrap(@as(i32, 540), @as(i32, 180)) == -180);
// Two periods, left limit, positive r
try testing.expect(wrap(@as(i32, -540), @as(i32, 180)) == -180);
// Two periods, right limit, negative r
try testing.expect(wrap(@as(i32, 540), @as(i32, -180)) == 180);
// Two periods, left limit, negative r
try testing.expect(wrap(@as(i32, -540), @as(i32, -180)) == 180);
// Floating point
try testing.expect(wrap(@as(f32, 1.125), @as(f32, 1.0)) == -0.875);
try testing.expect(wrap(@as(f32, -127.5), @as(f32, 180)) == -127.5);
// Mix of comptime and non-comptime
var i: i32 = 1;
_ = &i;
try testing.expect(wrap(i, 10) == 1);
const limit: i32 = 180;
// Within range
try testing.expect(wrap(@as(i32, -75), limit) == -75);
// Below
try testing.expect(wrap(@as(i32, -225), limit) == 135);
// Above
try testing.expect(wrap(@as(i32, 361), limit) == 1);
}pub fn wrap(x: anytype, r: anytype) @TypeOf(x) {
const info_x = @typeInfo(@TypeOf(x));
const info_r = @typeInfo(@TypeOf(r));
if (info_x == .int and info_x.int.signedness != .signed) {
@compileError("x must be floating point, comptime integer, or signed integer.");
}
switch (info_r) {
.int => {
// in the rare usecase of r not being comptime_int or float,
// take the penalty of having an intermediary type conversion,
// otherwise the alternative is to unwind iteratively to avoid overflow
const R = comptime do: {
var info = info_r;
info.int.bits += 1;
info.int.signedness = .signed;
break :do @Type(info);
};
const radius: if (info_r.int.signedness == .signed) @TypeOf(r) else R = r;
return @intCast(@mod(x - radius, 2 * @as(R, r)) - r); // provably impossible to overflow
},
else => {
return @mod(x - r, 2 * r) - r;
},
}
}pub fn clamp(val: anytype, lower: anytype, upper: anytype) @TypeOf(val, lower, upper)Odd ramp function
| _____
| /
|/
-------/-------
/|
_____/ |
|
Limit val to the inclusive range [lower, upper].
test clamp {
// Within range
try testing.expect(std.math.clamp(@as(i32, -1), @as(i32, -4), @as(i32, 7)) == -1);
// Below
try testing.expect(std.math.clamp(@as(i32, -5), @as(i32, -4), @as(i32, 7)) == -4);
// Above
try testing.expect(std.math.clamp(@as(i32, 8), @as(i32, -4), @as(i32, 7)) == 7);
// Floating point
try testing.expect(std.math.clamp(@as(f32, 1.1), @as(f32, 0.0), @as(f32, 1.0)) == 1.0);
try testing.expect(std.math.clamp(@as(f32, -127.5), @as(f32, -200), @as(f32, -100)) == -127.5);
// Vector
try testing.expect(@reduce(.And, std.math.clamp(@as(@Vector(3, f32), .{ 1.4, 15.23, 28.3 }), @as(@Vector(3, f32), .{ 9.8, 13.2, 15.6 }), @as(@Vector(3, f32), .{ 15.2, 22.8, 26.3 })) == @as(@Vector(3, f32), .{ 9.8, 15.23, 26.3 })));
// Mix of comptime and non-comptime
var i: i32 = 1;
_ = &i;
try testing.expect(std.math.clamp(i, 0, 1) == 1);
}pub fn clamp(val: anytype, lower: anytype, upper: anytype) @TypeOf(val, lower, upper) {
const T = @TypeOf(val, lower, upper);
switch (@typeInfo(T)) {
.int, .float, .comptime_int, .comptime_float => assert(lower <= upper),
.vector => |vinfo| switch (@typeInfo(vinfo.child)) {
.int, .float => assert(@reduce(.And, lower <= upper)),
else => @compileError("Expected vector of ints or floats, found " ++ @typeName(T)),
},
else => @compileError("Expected an int, float or vector of one, found " ++ @typeName(T)),
}
return @max(lower, @min(val, upper));
}pub fn mul(comptime T: type, a: T, b: T) (error{Overflow}!T)Returns the product of a and b. Returns an error on overflow.
T: typea: Tb: Tpub fn mul(comptime T: type, a: T, b: T) (error{Overflow}!T) {
if (T == comptime_int) return a * b;
const ov = @mulWithOverflow(a, b);
if (ov[1] != 0) return error.Overflow;
return ov[0];
}pub fn add(comptime T: type, a: T, b: T) (error{Overflow}!T)Returns the sum of a and b. Returns an error on overflow.
T: typea: Tb: Tpub fn add(comptime T: type, a: T, b: T) (error{Overflow}!T) {
if (T == comptime_int) return a + b;
const ov = @addWithOverflow(a, b);
if (ov[1] != 0) return error.Overflow;
return ov[0];
}pub fn sub(comptime T: type, a: T, b: T) (error{Overflow}!T)Returns a - b, or an error on overflow.
T: typea: Tb: Tpub fn sub(comptime T: type, a: T, b: T) (error{Overflow}!T) {
if (T == comptime_int) return a - b;
const ov = @subWithOverflow(a, b);
if (ov[1] != 0) return error.Overflow;
return ov[0];
}pub fn negate(x: anytype) !@TypeOf(x)pub fn negate(x: anytype) !@TypeOf(x) {
return sub(@TypeOf(x), 0, x);
}pub fn shlExact(comptime T: type, a: T, shift_amt: Log2Int(T)) !TShifts a left by shift_amt. Returns an error on overflow. shift_amt is unsigned.
pub fn shlExact(comptime T: type, a: T, shift_amt: Log2Int(T)) !T {
if (T == comptime_int) return a << shift_amt;
const ov = @shlWithOverflow(a, shift_amt);
if (ov[1] != 0) return error.Overflow;
return ov[0];
}pub fn shl(comptime T: type, a: T, shift_amt: anytype) TShifts left. Overflowed bits are truncated. A negative shift amount results in a right shift.
T: typea: Ttest shl {
try testing.expect(shl(u8, 0b11111111, @as(usize, 3)) == 0b11111000);
try testing.expect(shl(u8, 0b11111111, @as(usize, 8)) == 0);
try testing.expect(shl(u8, 0b11111111, @as(usize, 9)) == 0);
try testing.expect(shl(u8, 0b11111111, @as(isize, -2)) == 0b00111111);
try testing.expect(shl(u8, 0b11111111, 3) == 0b11111000);
try testing.expect(shl(u8, 0b11111111, 8) == 0);
try testing.expect(shl(u8, 0b11111111, 9) == 0);
try testing.expect(shl(u8, 0b11111111, -2) == 0b00111111);
try testing.expect(shl(@Vector(1, u32), @Vector(1, u32){42}, @as(usize, 1))[0] == @as(u32, 42) << 1);
try testing.expect(shl(@Vector(1, u32), @Vector(1, u32){42}, @as(isize, -1))[0] == @as(u32, 42) >> 1);
try testing.expect(shl(@Vector(1, u32), @Vector(1, u32){42}, 33)[0] == 0);
}pub fn shl(comptime T: type, a: T, shift_amt: anytype) T {
const abs_shift_amt = @abs(shift_amt);
const casted_shift_amt = blk: {
if (@typeInfo(T) == .vector) {
const C = @typeInfo(T).vector.child;
const len = @typeInfo(T).vector.len;
if (abs_shift_amt >= @typeInfo(C).int.bits) return @splat(0);
break :blk @as(@Vector(len, Log2Int(C)), @splat(@as(Log2Int(C), @intCast(abs_shift_amt))));
} else {
if (abs_shift_amt >= @typeInfo(T).int.bits) return 0;
break :blk @as(Log2Int(T), @intCast(abs_shift_amt));
}
};
if (@TypeOf(shift_amt) == comptime_int or @typeInfo(@TypeOf(shift_amt)).int.signedness == .signed) {
if (shift_amt < 0) {
return a >> casted_shift_amt;
}
}
return a << casted_shift_amt;
}pub fn shr(comptime T: type, a: T, shift_amt: anytype) TShifts right. Overflowed bits are truncated. A negative shift amount results in a left shift.
T: typea: Ttest shr {
try testing.expect(shr(u8, 0b11111111, @as(usize, 3)) == 0b00011111);
try testing.expect(shr(u8, 0b11111111, @as(usize, 8)) == 0);
try testing.expect(shr(u8, 0b11111111, @as(usize, 9)) == 0);
try testing.expect(shr(u8, 0b11111111, @as(isize, -2)) == 0b11111100);
try testing.expect(shr(u8, 0b11111111, 3) == 0b00011111);
try testing.expect(shr(u8, 0b11111111, 8) == 0);
try testing.expect(shr(u8, 0b11111111, 9) == 0);
try testing.expect(shr(u8, 0b11111111, -2) == 0b11111100);
try testing.expect(shr(@Vector(1, u32), @Vector(1, u32){42}, @as(usize, 1))[0] == @as(u32, 42) >> 1);
try testing.expect(shr(@Vector(1, u32), @Vector(1, u32){42}, @as(isize, -1))[0] == @as(u32, 42) << 1);
try testing.expect(shr(@Vector(1, u32), @Vector(1, u32){42}, 33)[0] == 0);
}pub fn shr(comptime T: type, a: T, shift_amt: anytype) T {
const abs_shift_amt = @abs(shift_amt);
const casted_shift_amt = blk: {
if (@typeInfo(T) == .vector) {
const C = @typeInfo(T).vector.child;
const len = @typeInfo(T).vector.len;
if (abs_shift_amt >= @typeInfo(C).int.bits) return @splat(0);
break :blk @as(@Vector(len, Log2Int(C)), @splat(@as(Log2Int(C), @intCast(abs_shift_amt))));
} else {
if (abs_shift_amt >= @typeInfo(T).int.bits) return 0;
break :blk @as(Log2Int(T), @intCast(abs_shift_amt));
}
};
if (@TypeOf(shift_amt) == comptime_int or @typeInfo(@TypeOf(shift_amt)).int.signedness == .signed) {
if (shift_amt < 0) {
return a << casted_shift_amt;
}
}
return a >> casted_shift_amt;
}pub fn rotr(comptime T: type, x: T, r: anytype) TRotates right. Only unsigned values can be rotated. Negative shift values result in shift modulo the bit count.
T: typex: Ttest rotr {
try testing.expect(rotr(u0, 0b0, @as(usize, 3)) == 0b0);
try testing.expect(rotr(u5, 0b00001, @as(usize, 0)) == 0b00001);
try testing.expect(rotr(u6, 0b000001, @as(usize, 7)) == 0b100000);
try testing.expect(rotr(u8, 0b00000001, @as(usize, 0)) == 0b00000001);
try testing.expect(rotr(u8, 0b00000001, @as(usize, 9)) == 0b10000000);
try testing.expect(rotr(u8, 0b00000001, @as(usize, 8)) == 0b00000001);
try testing.expect(rotr(u8, 0b00000001, @as(usize, 4)) == 0b00010000);
try testing.expect(rotr(u8, 0b00000001, @as(isize, -1)) == 0b00000010);
try testing.expect(rotr(u12, 0o7777, 1) == 0o7777);
try testing.expect(rotr(@Vector(1, u32), @Vector(1, u32){1}, @as(usize, 1))[0] == @as(u32, 1) << 31);
try testing.expect(rotr(@Vector(1, u32), @Vector(1, u32){1}, @as(isize, -1))[0] == @as(u32, 1) << 1);
}pub fn rotr(comptime T: type, x: T, r: anytype) T {
if (@typeInfo(T) == .vector) {
const C = @typeInfo(T).vector.child;
if (C == u0) return 0;
if (@typeInfo(C).int.signedness == .signed) {
@compileError("cannot rotate signed integers");
}
const ar: Log2Int(C) = @intCast(@mod(r, @typeInfo(C).int.bits));
return (x >> @splat(ar)) | (x << @splat(1 + ~ar));
} else if (@typeInfo(T).int.signedness == .signed) {
@compileError("cannot rotate signed integer");
} else {
if (T == u0) return 0;
if (comptime isPowerOfTwo(@typeInfo(T).int.bits)) {
const ar: Log2Int(T) = @intCast(@mod(r, @typeInfo(T).int.bits));
return x >> ar | x << (1 +% ~ar);
} else {
const ar = @mod(r, @typeInfo(T).int.bits);
return shr(T, x, ar) | shl(T, x, @typeInfo(T).int.bits - ar);
}
}
}pub fn rotl(comptime T: type, x: T, r: anytype) TRotates left. Only unsigned values can be rotated. Negative shift values result in shift modulo the bit count.
T: typex: Ttest rotl {
try testing.expect(rotl(u0, 0b0, @as(usize, 3)) == 0b0);
try testing.expect(rotl(u5, 0b00001, @as(usize, 0)) == 0b00001);
try testing.expect(rotl(u6, 0b000001, @as(usize, 7)) == 0b000010);
try testing.expect(rotl(u8, 0b00000001, @as(usize, 0)) == 0b00000001);
try testing.expect(rotl(u8, 0b00000001, @as(usize, 9)) == 0b00000010);
try testing.expect(rotl(u8, 0b00000001, @as(usize, 8)) == 0b00000001);
try testing.expect(rotl(u8, 0b00000001, @as(usize, 4)) == 0b00010000);
try testing.expect(rotl(u8, 0b00000001, @as(isize, -1)) == 0b10000000);
try testing.expect(rotl(u12, 0o7777, 1) == 0o7777);
try testing.expect(rotl(@Vector(1, u32), @Vector(1, u32){1 << 31}, @as(usize, 1))[0] == 1);
try testing.expect(rotl(@Vector(1, u32), @Vector(1, u32){1 << 31}, @as(isize, -1))[0] == @as(u32, 1) << 30);
}pub fn rotl(comptime T: type, x: T, r: anytype) T {
if (@typeInfo(T) == .vector) {
const C = @typeInfo(T).vector.child;
if (C == u0) return 0;
if (@typeInfo(C).int.signedness == .signed) {
@compileError("cannot rotate signed integers");
}
const ar: Log2Int(C) = @intCast(@mod(r, @typeInfo(C).int.bits));
return (x << @splat(ar)) | (x >> @splat(1 +% ~ar));
} else if (@typeInfo(T).int.signedness == .signed) {
@compileError("cannot rotate signed integer");
} else {
if (T == u0) return 0;
if (comptime isPowerOfTwo(@typeInfo(T).int.bits)) {
const ar: Log2Int(T) = @intCast(@mod(r, @typeInfo(T).int.bits));
return x << ar | x >> 1 +% ~ar;
} else {
const ar = @mod(r, @typeInfo(T).int.bits);
return shl(T, x, ar) | shr(T, x, @typeInfo(T).int.bits - ar);
}
}
}pub fn divTrunc(comptime T: type, numerator: T, denominator: T) !TDivide numerator by denominator, rounding toward zero. Returns an error on overflow or when denominator is zero.
T: typenumerator: Tdenominator: Ttest divTrunc {
try testDivTrunc();
try comptime testDivTrunc();
}pub fn divTrunc(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed and numerator == minInt(T) and denominator == -1) return error.Overflow;
return @divTrunc(numerator, denominator);
}pub fn divFloor(comptime T: type, numerator: T, denominator: T) !TDivide numerator by denominator, rounding toward negative infinity. Returns an error on overflow or when denominator is zero.
T: typenumerator: Tdenominator: Ttest divFloor {
try testDivFloor();
try comptime testDivFloor();
}pub fn divFloor(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed and numerator == minInt(T) and denominator == -1) return error.Overflow;
return @divFloor(numerator, denominator);
}pub fn divCeil(comptime T: type, numerator: T, denominator: T) !TDivide numerator by denominator, rounding toward positive infinity. Returns an error on overflow or when denominator is zero.
T: typenumerator: Tdenominator: Ttest divCeil {
try testDivCeil();
try comptime testDivCeil();
}pub fn divCeil(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
const info = @typeInfo(T);
switch (info) {
.comptime_float, .float => return @ceil(numerator / denominator),
.comptime_int, .int => {
if (numerator < 0 and denominator < 0) {
if (info == .int and numerator == minInt(T) and denominator == -1)
return error.Overflow;
return @divFloor(numerator + 1, denominator) + 1;
}
if (numerator > 0 and denominator > 0)
return @divFloor(numerator - 1, denominator) + 1;
return @divTrunc(numerator, denominator);
},
else => @compileError("divCeil unsupported on " ++ @typeName(T)),
}
}pub fn divExact(comptime T: type, numerator: T, denominator: T) !TDivide numerator by denominator. Return an error if quotient is not an integer, denominator is zero, or on overflow.
T: typenumerator: Tdenominator: Ttest divExact {
try testDivExact();
try comptime testDivExact();
}pub fn divExact(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed and numerator == minInt(T) and denominator == -1) return error.Overflow;
const result = @divTrunc(numerator, denominator);
if (result * denominator != numerator) return error.UnexpectedRemainder;
return result;
}pub fn mod(comptime T: type, numerator: T, denominator: T) !TReturns numerator modulo denominator, or an error if denominator is zero or negative. Negative numerators never result in negative return values.
T: typenumerator: Tdenominator: Tpub fn mod(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
if (denominator < 0) return error.NegativeDenominator;
return @mod(numerator, denominator);
}pub fn rem(comptime T: type, numerator: T, denominator: T) !TReturns the remainder when numerator is divided by denominator, or an error if denominator is zero or negative. Negative numerators can give negative results.
T: typenumerator: Tdenominator: Tpub fn rem(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
if (denominator < 0) return error.NegativeDenominator;
return @rem(numerator, denominator);
}Returns the negation of the integer parameter. Result is a signed integer.
test negateCast {
try testing.expect((negateCast(@as(u32, 999)) catch unreachable) == -999);
try testing.expect(@TypeOf(negateCast(@as(u32, 999)) catch unreachable) == i32);
try testing.expect((negateCast(@as(u32, -minInt(i32))) catch unreachable) == minInt(i32));
try testing.expect(@TypeOf(negateCast(@as(u32, -minInt(i32))) catch unreachable) == i32);
try testing.expectError(error.Overflow, negateCast(@as(u32, maxInt(i32) + 10)));
}pub fn negateCast(x: anytype) !std.meta.Int(.signed, @bitSizeOf(@TypeOf(x))) {
if (@typeInfo(@TypeOf(x)).int.signedness == .signed) return negate(x);
const int = std.meta.Int(.signed, @bitSizeOf(@TypeOf(x)));
if (x > -minInt(int)) return error.Overflow;
if (x == -minInt(int)) return minInt(int);
return -@as(int, @intCast(x));
}pub fn cast(comptime T: type, x: anytype) ?TCast an integer to a different integer type. If the value doesn't fit, return null.
T: typetest cast {
try testing.expect(cast(u8, 300) == null);
try testing.expect(cast(u8, @as(u32, 300)) == null);
try testing.expect(cast(i8, -200) == null);
try testing.expect(cast(i8, @as(i32, -200)) == null);
try testing.expect(cast(u8, -1) == null);
try testing.expect(cast(u8, @as(i8, -1)) == null);
try testing.expect(cast(u64, -1) == null);
try testing.expect(cast(u64, @as(i8, -1)) == null);
try testing.expect(cast(u8, 255).? == @as(u8, 255));
try testing.expect(cast(u8, @as(u32, 255)).? == @as(u8, 255));
try testing.expect(@TypeOf(cast(u8, 255).?) == u8);
try testing.expect(@TypeOf(cast(u8, @as(u32, 255)).?) == u8);
}pub fn cast(comptime T: type, x: anytype) ?T {
comptime assert(@typeInfo(T) == .int); // must pass an integer
const is_comptime = @TypeOf(x) == comptime_int;
comptime assert(is_comptime or @typeInfo(@TypeOf(x)) == .int); // must pass an integer
if ((is_comptime or maxInt(@TypeOf(x)) > maxInt(T)) and x > maxInt(T)) {
return null;
} else if ((is_comptime or minInt(@TypeOf(x)) < minInt(T)) and x < minInt(T)) {
return null;
} else {
return @as(T, @intCast(x));
}
}pub fn alignCast(comptime alignment: u29, ptr: anytype) AlignCastError!AlignCastResult(alignment, @TypeOf(ptr))Align cast a pointer but return an error if it's the wrong alignment
alignment: u29pub fn alignCast(comptime alignment: u29, ptr: anytype) AlignCastError!AlignCastResult(alignment, @TypeOf(ptr)) {
const addr = @intFromPtr(ptr);
if (addr % alignment != 0) {
return error.UnalignedMemory;
}
return @alignCast(ptr);
}pub fn isPowerOfTwo(int: anytype) boolAsserts int > 0.
test isPowerOfTwo {
try testing.expect(isPowerOfTwo(@as(u8, 1)));
try testing.expect(isPowerOfTwo(2));
try testing.expect(!isPowerOfTwo(@as(i16, 3)));
try testing.expect(isPowerOfTwo(4));
try testing.expect(!isPowerOfTwo(@as(u32, 31)));
try testing.expect(isPowerOfTwo(32));
try testing.expect(!isPowerOfTwo(@as(i64, 63)));
try testing.expect(isPowerOfTwo(128));
try testing.expect(isPowerOfTwo(@as(u128, 256)));
}pub fn isPowerOfTwo(int: anytype) bool {
assert(int > 0);
return (int & (int - 1)) == 0;
}pub inline fn round(value: anytype) @TypeOf(value)Rounds the given floating point number to the nearest integer. If two integers are equally close, rounds away from zero. Uses a dedicated hardware instruction when available. This is the same as calling the builtin @round
pub inline fn round(value: anytype) @TypeOf(value) {
return @round(value);
}pub inline fn trunc(value: anytype) @TypeOf(value)Rounds the given floating point number to an integer, towards zero. Uses a dedicated hardware instruction when available. This is the same as calling the builtin @trunc
pub inline fn trunc(value: anytype) @TypeOf(value) {
return @trunc(value);
}pub inline fn floor(value: anytype) @TypeOf(value)Returns the largest integral value not greater than the given floating point number. Uses a dedicated hardware instruction when available. This is the same as calling the builtin @floor
pub inline fn floor(value: anytype) @TypeOf(value) {
return @floor(value);
}pub fn floorPowerOfTwo(comptime T: type, value: T) TReturns the nearest power of two less than or equal to value, or zero if value is less than or equal to zero.
T: typevalue: Ttest floorPowerOfTwo {
try testFloorPowerOfTwo();
try comptime testFloorPowerOfTwo();
}pub inline fn ceil(value: anytype) @TypeOf(value)Returns the smallest integral value not less than the given floating point number. Uses a dedicated hardware instruction when available. This is the same as calling the builtin @ceil
pub inline fn ceil(value: anytype) @TypeOf(value) {
return @ceil(value);
}pub fn ceilPowerOfTwoPromote(comptime T: type, value: T) std.meta.Int(@typeInfo(T).int.signedness, @typeInfo(T).int.bits + 1)Returns the next power of two (if the value is not already a power of two). Only unsigned integers can be used. Zero is not an allowed input. Result is a type with 1 more bit than the input type.
T: typevalue: Ttest ceilPowerOfTwoPromote {
try testCeilPowerOfTwoPromote();
try comptime testCeilPowerOfTwoPromote();
}pub fn ceilPowerOfTwoPromote(comptime T: type, value: T) std.meta.Int(@typeInfo(T).int.signedness, @typeInfo(T).int.bits + 1) {
comptime assert(@typeInfo(T) == .int);
comptime assert(@typeInfo(T).int.signedness == .unsigned);
assert(value != 0);
const PromotedType = std.meta.Int(@typeInfo(T).int.signedness, @typeInfo(T).int.bits + 1);
const ShiftType = std.math.Log2Int(PromotedType);
return @as(PromotedType, 1) << @as(ShiftType, @intCast(@typeInfo(T).int.bits - @clz(value - 1)));
}pub fn ceilPowerOfTwo(comptime T: type, value: T) (error{Overflow}!T)Returns the next power of two (if the value is not already a power of two). Only unsigned integers can be used. Zero is not an allowed input. If the value doesn't fit, returns an error.
T: typevalue: Ttest ceilPowerOfTwo {
try testCeilPowerOfTwo();
try comptime testCeilPowerOfTwo();
}pub fn ceilPowerOfTwo(comptime T: type, value: T) (error{Overflow}!T) {
comptime assert(@typeInfo(T) == .int);
const info = @typeInfo(T).int;
comptime assert(info.signedness == .unsigned);
const PromotedType = std.meta.Int(info.signedness, info.bits + 1);
const overflowBit = @as(PromotedType, 1) << info.bits;
const x = ceilPowerOfTwoPromote(T, value);
if (overflowBit & x != 0) {
return error.Overflow;
}
return @as(T, @intCast(x));
}pub fn ceilPowerOfTwoAssert(comptime T: type, value: T) TReturns the next power of two (if the value is not already a power of two). Only unsigned integers can be used. Zero is not an allowed input. Asserts that the value fits.
T: typevalue: Tpub fn ceilPowerOfTwoAssert(comptime T: type, value: T) T {
return ceilPowerOfTwo(T, value) catch unreachable;
}pub fn log2_int(comptime T: type, x: T) Log2Int(T)Return the log base 2 of integer value x, rounding down to the nearest integer.
T: typex: Tpub fn log2_int(comptime T: type, x: T) Log2Int(T) {
if (@typeInfo(T) != .int or @typeInfo(T).int.signedness != .unsigned)
@compileError("log2_int requires an unsigned integer, found " ++ @typeName(T));
assert(x != 0);
return @as(Log2Int(T), @intCast(@typeInfo(T).int.bits - 1 - @clz(x)));
}pub fn log2_int_ceil(comptime T: type, x: T) Log2IntCeil(T)Return the log base 2 of integer value x, rounding up to the nearest integer.
T: typex: Ttest log2_int_ceil {
try testing.expect(log2_int_ceil(u32, 1) == 0);
try testing.expect(log2_int_ceil(u32, 2) == 1);
try testing.expect(log2_int_ceil(u32, 3) == 2);
try testing.expect(log2_int_ceil(u32, 4) == 2);
try testing.expect(log2_int_ceil(u32, 5) == 3);
try testing.expect(log2_int_ceil(u32, 6) == 3);
try testing.expect(log2_int_ceil(u32, 7) == 3);
try testing.expect(log2_int_ceil(u32, 8) == 3);
try testing.expect(log2_int_ceil(u32, 9) == 4);
try testing.expect(log2_int_ceil(u32, 10) == 4);
}pub fn log2_int_ceil(comptime T: type, x: T) Log2IntCeil(T) {
if (@typeInfo(T) != .int or @typeInfo(T).int.signedness != .unsigned)
@compileError("log2_int_ceil requires an unsigned integer, found " ++ @typeName(T));
assert(x != 0);
if (x == 1) return 0;
const log2_val: Log2IntCeil(T) = log2_int(T, x - 1);
return log2_val + 1;
}pub fn lossyCast(comptime T: type, value: anytype) TCast a value to a different type. If the value doesn't fit in, or can't be perfectly represented by, the new type, it will be converted to the closest possible representation.
T: typetest lossyCast {
try testing.expect(lossyCast(i16, 70000.0) == @as(i16, 32767));
try testing.expect(lossyCast(u32, @as(i16, -255)) == @as(u32, 0));
try testing.expect(lossyCast(i9, @as(u32, 200)) == @as(i9, 200));
try testing.expect(lossyCast(u32, @as(f32, maxInt(u32))) == maxInt(u32));
try testing.expect(lossyCast(u32, nan(f32)) == 0);
}pub fn lossyCast(comptime T: type, value: anytype) T {
switch (@typeInfo(T)) {
.float => {
switch (@typeInfo(@TypeOf(value))) {
.int => return @floatFromInt(value),
.float => return @floatCast(value),
.comptime_int => return value,
.comptime_float => return value,
else => @compileError("bad type"),
}
},
.int => {
switch (@typeInfo(@TypeOf(value))) {
.int, .comptime_int => {
if (value >= maxInt(T)) {
return maxInt(T);
} else if (value <= minInt(T)) {
return minInt(T);
} else {
return @intCast(value);
}
},
.float, .comptime_float => {
if (isNan(value)) {
return 0;
} else if (value >= maxInt(T)) {
return maxInt(T);
} else if (value <= minInt(T)) {
return minInt(T);
} else {
return @intFromFloat(value);
}
},
else => @compileError("bad type"),
}
},
else => @compileError("bad result type"),
}
}pub fn lerp(a: anytype, b: anytype, t: anytype) @TypeOf(a, b, t)Performs linear interpolation between a and b based on t. t ranges from 0.0 to 1.0, but may exceed these bounds. Supports floats and vectors of floats.
This does not guarantee returning b if t is 1 due to floating-point errors. This is monotonic.
test lerp {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/17884
if (builtin.zig_backend == .stage2_x86_64 and
!comptime std.Target.x86.featureSetHas(builtin.cpu.features, .fma)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/17884
try testing.expectEqual(@as(f64, 75), lerp(50, 100, 0.5));
try testing.expectEqual(@as(f32, 43.75), lerp(50, 25, 0.25));
try testing.expectEqual(@as(f64, -31.25), lerp(-50, 25, 0.25));
try testing.expectEqual(@as(f64, 30), lerp(10, 20, 2.0));
try testing.expectEqual(@as(f64, 5), lerp(10, 20, -0.5));
try testing.expectApproxEqRel(@as(f32, -7.16067345e+03), lerp(-10000.12345, -5000.12345, 0.56789), 1e-19);
try testing.expectApproxEqRel(@as(f64, 7.010987590521e+62), lerp(0.123456789e-64, 0.123456789e64, 0.56789), 1e-33);
try testing.expectEqual(@as(f32, 0.0), lerp(@as(f32, 1.0e8), 1.0, 1.0));
try testing.expectEqual(@as(f64, 0.0), lerp(@as(f64, 1.0e16), 1.0, 1.0));
try testing.expectEqual(@as(f32, 1.0), lerp(@as(f32, 1.0e7), 1.0, 1.0));
try testing.expectEqual(@as(f64, 1.0), lerp(@as(f64, 1.0e15), 1.0, 1.0));
{
const a: @Vector(3, f32) = @splat(0);
const b: @Vector(3, f32) = @splat(50);
const t: @Vector(3, f32) = @splat(0.5);
try testing.expectEqual(
@Vector(3, f32){ 25, 25, 25 },
lerp(a, b, t),
);
}
{
const a: @Vector(3, f64) = @splat(50);
const b: @Vector(3, f64) = @splat(100);
const t: @Vector(3, f64) = @splat(0.5);
try testing.expectEqual(
@Vector(3, f64){ 75, 75, 75 },
lerp(a, b, t),
);
}
{
const a: @Vector(2, f32) = @splat(40);
const b: @Vector(2, f32) = @splat(80);
const t: @Vector(2, f32) = @Vector(2, f32){ 0.25, 0.75 };
try testing.expectEqual(
@Vector(2, f32){ 50, 70 },
lerp(a, b, t),
);
}
}pub fn lerp(a: anytype, b: anytype, t: anytype) @TypeOf(a, b, t) {
const Type = @TypeOf(a, b, t);
return @mulAdd(Type, b - a, t, a);
}pub fn maxInt(comptime T: type) comptime_intReturns the maximum value of integer type T.
T: typetest maxInt {
try testing.expect(maxInt(u0) == 0);
try testing.expect(maxInt(u1) == 1);
try testing.expect(maxInt(u8) == 255);
try testing.expect(maxInt(u16) == 65535);
try testing.expect(maxInt(u32) == 4294967295);
try testing.expect(maxInt(u64) == 18446744073709551615);
try testing.expect(maxInt(u128) == 340282366920938463463374607431768211455);
try testing.expect(maxInt(i0) == 0);
try testing.expect(maxInt(i1) == 0);
try testing.expect(maxInt(i8) == 127);
try testing.expect(maxInt(i16) == 32767);
try testing.expect(maxInt(i32) == 2147483647);
try testing.expect(maxInt(i63) == 4611686018427387903);
try testing.expect(maxInt(i64) == 9223372036854775807);
try testing.expect(maxInt(i128) == 170141183460469231731687303715884105727);
}pub fn maxInt(comptime T: type) comptime_int {
const info = @typeInfo(T);
const bit_count = info.int.bits;
if (bit_count == 0) return 0;
return (1 << (bit_count - @intFromBool(info.int.signedness == .signed))) - 1;
}pub fn minInt(comptime T: type) comptime_intReturns the minimum value of integer type T.
T: typetest minInt {
try testing.expect(minInt(u0) == 0);
try testing.expect(minInt(u1) == 0);
try testing.expect(minInt(u8) == 0);
try testing.expect(minInt(u16) == 0);
try testing.expect(minInt(u32) == 0);
try testing.expect(minInt(u63) == 0);
try testing.expect(minInt(u64) == 0);
try testing.expect(minInt(u128) == 0);
try testing.expect(minInt(i0) == 0);
try testing.expect(minInt(i1) == -1);
try testing.expect(minInt(i8) == -128);
try testing.expect(minInt(i16) == -32768);
try testing.expect(minInt(i32) == -2147483648);
try testing.expect(minInt(i63) == -4611686018427387904);
try testing.expect(minInt(i64) == -9223372036854775808);
try testing.expect(minInt(i128) == -170141183460469231731687303715884105728);
}pub fn minInt(comptime T: type) comptime_int {
const info = @typeInfo(T);
const bit_count = info.int.bits;
if (info.int.signedness == .unsigned) return 0;
if (bit_count == 0) return 0;
return -(1 << (bit_count - 1));
}pub fn mulWide(comptime T: type, a: T, b: T) std.meta.Int( @typeInfo(T).int.signedness, @typeInfo(T).int.bits * 2, )Multiply a and b. Return type is wide enough to guarantee no overflow.
T: typea: Tb: Tpub fn order(a: anytype, b: anytype) OrderGiven two numbers, this function returns the order they are with respect to each other.
test order {
try testing.expect(order(0, 0) == .eq);
try testing.expect(order(1, 0) == .gt);
try testing.expect(order(-1, 0) == .lt);
}pub fn order(a: anytype, b: anytype) Order {
if (a == b) {
return .eq;
} else if (a < b) {
return .lt;
} else if (a > b) {
return .gt;
} else {
unreachable;
}
}pub fn compare(a: anytype, op: CompareOperator, b: anytype) boolThis function does the same thing as comparison operators, however the operator is a runtime-known enum value. Works on any operands that support comparison operators.
op: CompareOperatortest compare {
try testing.expect(compare(@as(i8, -1), .lt, @as(u8, 255)));
try testing.expect(compare(@as(i8, 2), .gt, @as(u8, 1)));
try testing.expect(!compare(@as(i8, -1), .gte, @as(u8, 255)));
try testing.expect(compare(@as(u8, 255), .gt, @as(i8, -1)));
try testing.expect(!compare(@as(u8, 255), .lte, @as(i8, -1)));
try testing.expect(compare(@as(i8, -1), .lt, @as(u9, 255)));
try testing.expect(!compare(@as(i8, -1), .gte, @as(u9, 255)));
try testing.expect(compare(@as(u9, 255), .gt, @as(i8, -1)));
try testing.expect(!compare(@as(u9, 255), .lte, @as(i8, -1)));
try testing.expect(compare(@as(i9, -1), .lt, @as(u8, 255)));
try testing.expect(!compare(@as(i9, -1), .gte, @as(u8, 255)));
try testing.expect(compare(@as(u8, 255), .gt, @as(i9, -1)));
try testing.expect(!compare(@as(u8, 255), .lte, @as(i9, -1)));
try testing.expect(compare(@as(u8, 1), .lt, @as(u8, 2)));
try testing.expect(@as(u8, @bitCast(@as(i8, -1))) == @as(u8, 255));
try testing.expect(!compare(@as(u8, 255), .eq, @as(i8, -1)));
try testing.expect(compare(@as(u8, 1), .eq, @as(u8, 1)));
}pub fn compare(a: anytype, op: CompareOperator, b: anytype) bool {
return switch (op) {
.lt => a < b,
.lte => a <= b,
.eq => a == b,
.neq => a != b,
.gt => a > b,
.gte => a >= b,
};
}pub inline fn boolMask(comptime MaskInt: type, value: bool) MaskIntReturns a mask of all ones if value is true, and a mask of all zeroes if value is false. Compiles to one instruction for register sized integers.
MaskInt: typevalue: booltest boolMask {
const runTest = struct {
fn runTest() !void {
try testing.expectEqual(@as(u1, 0), boolMask(u1, false));
try testing.expectEqual(@as(u1, 1), boolMask(u1, true));
try testing.expectEqual(@as(i1, 0), boolMask(i1, false));
try testing.expectEqual(@as(i1, -1), boolMask(i1, true));
try testing.expectEqual(@as(u13, 0), boolMask(u13, false));
try testing.expectEqual(@as(u13, 0x1FFF), boolMask(u13, true));
try testing.expectEqual(@as(i13, 0), boolMask(i13, false));
try testing.expectEqual(@as(i13, -1), boolMask(i13, true));
try testing.expectEqual(@as(u32, 0), boolMask(u32, false));
try testing.expectEqual(@as(u32, 0xFFFF_FFFF), boolMask(u32, true));
try testing.expectEqual(@as(i32, 0), boolMask(i32, false));
try testing.expectEqual(@as(i32, -1), boolMask(i32, true));
}
}.runTest;
try runTest();
try comptime runTest();
}pub inline fn boolMask(comptime MaskInt: type, value: bool) MaskInt {
if (@typeInfo(MaskInt) != .int)
@compileError("boolMask requires an integer mask type.");
if (MaskInt == u0 or MaskInt == i0)
@compileError("boolMask cannot convert to u0 or i0, they are too small.");
// The u1 and i1 cases tend to overflow,
// so we special case them here.
if (MaskInt == u1) return @intFromBool(value);
if (MaskInt == i1) {
// The @as here is a workaround for #7950
return @as(i1, @bitCast(@as(u1, @intFromBool(value))));
}
return -%@as(MaskInt, @intCast(@intFromBool(value)));
}pub fn comptimeMod(num: anytype, comptime denom: comptime_int) IntFittingRange(0, denom - 1)Return the mod of num with the smallest integer type
denom: comptime_intpub fn comptimeMod(num: anytype, comptime denom: comptime_int) IntFittingRange(0, denom - 1) {
return @as(IntFittingRange(0, denom - 1), @intCast(@mod(num, denom)));
}pub inline fn sign(i: anytype) @TypeOf(i)Returns -1, 0, or 1. Supports integer and float types and vectors of integer and float types. Unsigned integer types will always return 0 or 1. Branchless.
pub inline fn sign(i: anytype) @TypeOf(i) {
const T = @TypeOf(i);
return switch (@typeInfo(T)) {
.int, .comptime_int => @as(T, @intFromBool(i > 0)) - @as(T, @intFromBool(i < 0)),
.float, .comptime_float => @as(T, @floatFromInt(@intFromBool(i > 0))) - @as(T, @floatFromInt(@intFromBool(i < 0))),
.vector => |vinfo| blk: {
switch (@typeInfo(vinfo.child)) {
.int, .float => {
const zero: T = @splat(0);
const one: T = @splat(1);
break :blk @select(vinfo.child, i > zero, one, zero) - @select(vinfo.child, i < zero, one, zero);
},
else => @compileError("Expected vector of ints or floats, found " ++ @typeName(T)),
}
},
else => @compileError("Expected an int, float or vector of one, found " ++ @typeName(T)),
};
}const builtin = @import("builtin");
const std = @import("std.zig");
const float = @import("math/float.zig");
const assert = std.debug.assert;
const mem = std.mem;
const testing = std.testing;
/// Euler's number (e)
pub const e = 2.71828182845904523536028747135266249775724709369995;
/// Archimedes' constant (π)
pub const pi = 3.14159265358979323846264338327950288419716939937510;
/// Phi or Golden ratio constant (Φ) = (1 + sqrt(5))/2
pub const phi = 1.6180339887498948482045868343656381177203091798057628621;
/// Circle constant (τ)
pub const tau = 2 * pi;
/// log2(e)
pub const log2e = 1.442695040888963407359924681001892137;
/// log10(e)
pub const log10e = 0.434294481903251827651128918916605082;
/// ln(2)
pub const ln2 = 0.693147180559945309417232121458176568;
/// ln(10)
pub const ln10 = 2.302585092994045684017991454684364208;
/// 2/sqrt(π)
pub const two_sqrtpi = 1.128379167095512573896158903121545172;
/// sqrt(2)
pub const sqrt2 = 1.414213562373095048801688724209698079;
/// 1/sqrt(2)
pub const sqrt1_2 = 0.707106781186547524400844362104849039;
/// pi/180.0
pub const rad_per_deg = 0.0174532925199432957692369076848861271344287188854172545609719144;
/// 180.0/pi
pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861;
pub const floatExponentBits = float.floatExponentBits;
pub const floatMantissaBits = float.floatMantissaBits;
pub const floatFractionalBits = float.floatFractionalBits;
pub const floatExponentMin = float.floatExponentMin;
pub const floatExponentMax = float.floatExponentMax;
pub const floatTrueMin = float.floatTrueMin;
pub const floatMin = float.floatMin;
pub const floatMax = float.floatMax;
pub const floatEps = float.floatEps;
pub const floatEpsAt = float.floatEpsAt;
pub const inf = float.inf;
pub const nan = float.nan;
pub const snan = float.snan;
/// Performs an approximate comparison of two floating point values `x` and `y`.
/// Returns true if the absolute difference between them is less or equal than
/// the specified tolerance.
///
/// The `tolerance` parameter is the absolute tolerance used when determining if
/// the two numbers are close enough; a good value for this parameter is a small
/// multiple of `floatEps(T)`.
///
/// Note that this function is recommended for comparing small numbers
/// around zero; using `approxEqRel` is suggested otherwise.
///
/// NaN values are never considered equal to any value.
pub fn approxEqAbs(comptime T: type, x: T, y: T, tolerance: T) bool {
assert(@typeInfo(T) == .float or @typeInfo(T) == .comptime_float);
assert(tolerance >= 0);
// Fast path for equal values (and signed zeros and infinites).
if (x == y)
return true;
if (isNan(x) or isNan(y))
return false;
return @abs(x - y) <= tolerance;
}
/// Performs an approximate comparison of two floating point values `x` and `y`.
/// Returns true if the absolute difference between them is less or equal than
/// `max(|x|, |y|) * tolerance`, where `tolerance` is a positive number greater
/// than zero.
///
/// The `tolerance` parameter is the relative tolerance used when determining if
/// the two numbers are close enough; a good value for this parameter is usually
/// `sqrt(floatEps(T))`, meaning that the two numbers are considered equal if at
/// least half of the digits are equal.
///
/// Note that for comparisons of small numbers around zero this function won't
/// give meaningful results, use `approxEqAbs` instead.
///
/// NaN values are never considered equal to any value.
pub fn approxEqRel(comptime T: type, x: T, y: T, tolerance: T) bool {
assert(@typeInfo(T) == .float or @typeInfo(T) == .comptime_float);
assert(tolerance > 0);
// Fast path for equal values (and signed zeros and infinites).
if (x == y)
return true;
if (isNan(x) or isNan(y))
return false;
return @abs(x - y) <= @max(@abs(x), @abs(y)) * tolerance;
}
test approxEqAbs {
inline for ([_]type{ f16, f32, f64, f128 }) |T| {
const eps_value = comptime floatEps(T);
const min_value = comptime floatMin(T);
try testing.expect(approxEqAbs(T, 0.0, 0.0, eps_value));
try testing.expect(approxEqAbs(T, -0.0, -0.0, eps_value));
try testing.expect(approxEqAbs(T, 0.0, -0.0, eps_value));
try testing.expect(!approxEqAbs(T, 1.0 + 2 * eps_value, 1.0, eps_value));
try testing.expect(approxEqAbs(T, 1.0 + 1 * eps_value, 1.0, eps_value));
try testing.expect(approxEqAbs(T, min_value, 0.0, eps_value * 2));
try testing.expect(approxEqAbs(T, -min_value, 0.0, eps_value * 2));
}
comptime {
// `comptime_float` is guaranteed to have the same precision and operations of
// the largest other floating point type, which is f128 but it doesn't have a
// defined layout so we can't rely on `@bitCast` to construct the smallest
// possible epsilon value like we do in the tests above. In the same vein, we
// also can't represent a max/min, `NaN` or `Inf` values.
const eps_value = 1e-4;
try testing.expect(approxEqAbs(comptime_float, 0.0, 0.0, eps_value));
try testing.expect(approxEqAbs(comptime_float, -0.0, -0.0, eps_value));
try testing.expect(approxEqAbs(comptime_float, 0.0, -0.0, eps_value));
try testing.expect(!approxEqAbs(comptime_float, 1.0 + 2 * eps_value, 1.0, eps_value));
try testing.expect(approxEqAbs(comptime_float, 1.0 + 1 * eps_value, 1.0, eps_value));
}
}
test approxEqRel {
inline for ([_]type{ f16, f32, f64, f128 }) |T| {
const eps_value = comptime floatEps(T);
const sqrt_eps_value = comptime sqrt(eps_value);
const nan_value = comptime nan(T);
const inf_value = comptime inf(T);
const min_value = comptime floatMin(T);
try testing.expect(approxEqRel(T, 1.0, 1.0, sqrt_eps_value));
try testing.expect(!approxEqRel(T, 1.0, 0.0, sqrt_eps_value));
try testing.expect(!approxEqRel(T, 1.0, nan_value, sqrt_eps_value));
try testing.expect(!approxEqRel(T, nan_value, nan_value, sqrt_eps_value));
try testing.expect(approxEqRel(T, inf_value, inf_value, sqrt_eps_value));
try testing.expect(approxEqRel(T, min_value, min_value, sqrt_eps_value));
try testing.expect(approxEqRel(T, -min_value, -min_value, sqrt_eps_value));
}
comptime {
// `comptime_float` is guaranteed to have the same precision and operations of
// the largest other floating point type, which is f128 but it doesn't have a
// defined layout so we can't rely on `@bitCast` to construct the smallest
// possible epsilon value like we do in the tests above. In the same vein, we
// also can't represent a max/min, `NaN` or `Inf` values.
const eps_value = 1e-4;
const sqrt_eps_value = sqrt(eps_value);
try testing.expect(approxEqRel(comptime_float, 1.0, 1.0, sqrt_eps_value));
try testing.expect(!approxEqRel(comptime_float, 1.0, 0.0, sqrt_eps_value));
}
}
pub fn raiseInvalid() void {
// Raise INVALID fpu exception
}
pub fn raiseUnderflow() void {
// Raise UNDERFLOW fpu exception
}
pub fn raiseOverflow() void {
// Raise OVERFLOW fpu exception
}
pub fn raiseInexact() void {
// Raise INEXACT fpu exception
}
pub fn raiseDivByZero() void {
// Raise INEXACT fpu exception
}
pub const isNan = @import("math/isnan.zig").isNan;
pub const isSignalNan = @import("math/isnan.zig").isSignalNan;
pub const frexp = @import("math/frexp.zig").frexp;
pub const Frexp = @import("math/frexp.zig").Frexp;
pub const modf = @import("math/modf.zig").modf;
pub const Modf = @import("math/modf.zig").Modf;
pub const copysign = @import("math/copysign.zig").copysign;
pub const isFinite = @import("math/isfinite.zig").isFinite;
pub const isInf = @import("math/isinf.zig").isInf;
pub const isPositiveInf = @import("math/isinf.zig").isPositiveInf;
pub const isNegativeInf = @import("math/isinf.zig").isNegativeInf;
pub const isPositiveZero = @import("math/iszero.zig").isPositiveZero;
pub const isNegativeZero = @import("math/iszero.zig").isNegativeZero;
pub const isNormal = @import("math/isnormal.zig").isNormal;
pub const nextAfter = @import("math/nextafter.zig").nextAfter;
pub const signbit = @import("math/signbit.zig").signbit;
pub const scalbn = @import("math/scalbn.zig").scalbn;
pub const ldexp = @import("math/ldexp.zig").ldexp;
pub const pow = @import("math/pow.zig").pow;
pub const powi = @import("math/powi.zig").powi;
pub const sqrt = @import("math/sqrt.zig").sqrt;
pub const cbrt = @import("math/cbrt.zig").cbrt;
pub const acos = @import("math/acos.zig").acos;
pub const asin = @import("math/asin.zig").asin;
pub const atan = @import("math/atan.zig").atan;
pub const atan2 = @import("math/atan2.zig").atan2;
pub const hypot = @import("math/hypot.zig").hypot;
pub const expm1 = @import("math/expm1.zig").expm1;
pub const ilogb = @import("math/ilogb.zig").ilogb;
pub const log = @import("math/log.zig").log;
pub const log2 = @import("math/log2.zig").log2;
pub const log10 = @import("math/log10.zig").log10;
pub const log10_int = @import("math/log10.zig").log10_int;
pub const log_int = @import("math/log_int.zig").log_int;
pub const log1p = @import("math/log1p.zig").log1p;
pub const asinh = @import("math/asinh.zig").asinh;
pub const acosh = @import("math/acosh.zig").acosh;
pub const atanh = @import("math/atanh.zig").atanh;
pub const sinh = @import("math/sinh.zig").sinh;
pub const cosh = @import("math/cosh.zig").cosh;
pub const tanh = @import("math/tanh.zig").tanh;
pub const gcd = @import("math/gcd.zig").gcd;
pub const gamma = @import("math/gamma.zig").gamma;
pub const lgamma = @import("math/gamma.zig").lgamma;
/// Sine trigonometric function on a floating point number.
/// Uses a dedicated hardware instruction when available.
/// This is the same as calling the builtin @sin
pub inline fn sin(value: anytype) @TypeOf(value) {
return @sin(value);
}
/// Cosine trigonometric function on a floating point number.
/// Uses a dedicated hardware instruction when available.
/// This is the same as calling the builtin @cos
pub inline fn cos(value: anytype) @TypeOf(value) {
return @cos(value);
}
/// Tangent trigonometric function on a floating point number.
/// Uses a dedicated hardware instruction when available.
/// This is the same as calling the builtin @tan
pub inline fn tan(value: anytype) @TypeOf(value) {
return @tan(value);
}
/// Converts an angle in radians to degrees. T must be a float or comptime number or a vector of floats.
pub fn radiansToDegrees(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_float else @TypeOf(ang) {
const T = @TypeOf(ang);
switch (@typeInfo(T)) {
.float, .comptime_float, .comptime_int => return ang * deg_per_rad,
.vector => |V| if (@typeInfo(V.child) == .float) return ang * @as(T, @splat(deg_per_rad)),
else => {},
}
@compileError("Input must be float or a comptime number, or a vector of floats.");
}
test radiansToDegrees {
const zero: f32 = 0;
const half_pi: f32 = pi / 2.0;
const neg_quart_pi: f32 = -pi / 4.0;
const one_pi: f32 = pi;
const two_pi: f32 = 2.0 * pi;
try std.testing.expectApproxEqAbs(@as(f32, 0), radiansToDegrees(zero), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 90), radiansToDegrees(half_pi), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, -45), radiansToDegrees(neg_quart_pi), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 180), radiansToDegrees(one_pi), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 360), radiansToDegrees(two_pi), 1e-6);
const result = radiansToDegrees(@Vector(4, f32){
half_pi,
neg_quart_pi,
one_pi,
two_pi,
});
try std.testing.expectApproxEqAbs(@as(f32, 90), result[0], 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, -45), result[1], 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 180), result[2], 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 360), result[3], 1e-6);
}
/// Converts an angle in degrees to radians. T must be a float or comptime number or a vector of floats.
pub fn degreesToRadians(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_float else @TypeOf(ang) {
const T = @TypeOf(ang);
switch (@typeInfo(T)) {
.float, .comptime_float, .comptime_int => return ang * rad_per_deg,
.vector => |V| if (@typeInfo(V.child) == .float) return ang * @as(T, @splat(rad_per_deg)),
else => {},
}
@compileError("Input must be float or a comptime number, or a vector of floats.");
}
test degreesToRadians {
const ninety: f32 = 90;
const neg_two_seventy: f32 = -270;
const three_sixty: f32 = 360;
try std.testing.expectApproxEqAbs(@as(f32, pi / 2.0), degreesToRadians(ninety), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, -3 * pi / 2.0), degreesToRadians(neg_two_seventy), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 2 * pi), degreesToRadians(three_sixty), 1e-6);
const result = degreesToRadians(@Vector(3, f32){
ninety,
neg_two_seventy,
three_sixty,
});
try std.testing.expectApproxEqAbs(@as(f32, pi / 2.0), result[0], 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, -3 * pi / 2.0), result[1], 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 2 * pi), result[2], 1e-6);
}
/// Base-e exponential function on a floating point number.
/// Uses a dedicated hardware instruction when available.
/// This is the same as calling the builtin @exp
pub inline fn exp(value: anytype) @TypeOf(value) {
return @exp(value);
}
/// Base-2 exponential function on a floating point number.
/// Uses a dedicated hardware instruction when available.
/// This is the same as calling the builtin @exp2
pub inline fn exp2(value: anytype) @TypeOf(value) {
return @exp2(value);
}
pub const complex = @import("math/complex.zig");
pub const Complex = complex.Complex;
pub const big = @import("math/big.zig");
test {
_ = floatExponentBits;
_ = floatMantissaBits;
_ = floatFractionalBits;
_ = floatExponentMin;
_ = floatExponentMax;
_ = floatTrueMin;
_ = floatMin;
_ = floatMax;
_ = floatEps;
_ = inf;
_ = nan;
_ = snan;
_ = isNan;
_ = isSignalNan;
_ = frexp;
_ = Frexp;
_ = modf;
_ = Modf;
_ = copysign;
_ = isFinite;
_ = isInf;
_ = isPositiveInf;
_ = isNegativeInf;
_ = isNormal;
_ = nextAfter;
_ = signbit;
_ = scalbn;
_ = ldexp;
_ = pow;
_ = powi;
_ = sqrt;
_ = cbrt;
_ = acos;
_ = asin;
_ = atan;
_ = atan2;
_ = hypot;
_ = expm1;
_ = ilogb;
_ = log;
_ = log2;
_ = log10;
_ = log10_int;
_ = log_int;
_ = log1p;
_ = asinh;
_ = acosh;
_ = atanh;
_ = sinh;
_ = cosh;
_ = tanh;
_ = gcd;
_ = gamma;
_ = lgamma;
_ = complex;
_ = Complex;
_ = big;
}
/// Given two types, returns the smallest one which is capable of holding the
/// full range of the minimum value.
pub fn Min(comptime A: type, comptime B: type) type {
switch (@typeInfo(A)) {
.int => |a_info| switch (@typeInfo(B)) {
.int => |b_info| if (a_info.signedness == .unsigned and b_info.signedness == .unsigned) {
if (a_info.bits < b_info.bits) {
return A;
} else {
return B;
}
},
else => {},
},
else => {},
}
return @TypeOf(@as(A, 0) + @as(B, 0));
}
/// Odd sawtooth function
/// ```
/// |
/// / | / /
/// / |/ /
/// --/----/----/--
/// / /| /
/// / / | /
/// |
/// ```
/// Limit x to the half-open interval [-r, r).
pub fn wrap(x: anytype, r: anytype) @TypeOf(x) {
const info_x = @typeInfo(@TypeOf(x));
const info_r = @typeInfo(@TypeOf(r));
if (info_x == .int and info_x.int.signedness != .signed) {
@compileError("x must be floating point, comptime integer, or signed integer.");
}
switch (info_r) {
.int => {
// in the rare usecase of r not being comptime_int or float,
// take the penalty of having an intermediary type conversion,
// otherwise the alternative is to unwind iteratively to avoid overflow
const R = comptime do: {
var info = info_r;
info.int.bits += 1;
info.int.signedness = .signed;
break :do @Type(info);
};
const radius: if (info_r.int.signedness == .signed) @TypeOf(r) else R = r;
return @intCast(@mod(x - radius, 2 * @as(R, r)) - r); // provably impossible to overflow
},
else => {
return @mod(x - r, 2 * r) - r;
},
}
}
test wrap {
// Within range
try testing.expect(wrap(@as(i32, -75), @as(i32, 180)) == -75);
try testing.expect(wrap(@as(i32, -75), @as(i32, -180)) == -75);
// Below
try testing.expect(wrap(@as(i32, -225), @as(i32, 180)) == 135);
try testing.expect(wrap(@as(i32, -225), @as(i32, -180)) == 135);
// Above
try testing.expect(wrap(@as(i32, 361), @as(i32, 180)) == 1);
try testing.expect(wrap(@as(i32, 361), @as(i32, -180)) == 1);
// One period, right limit, positive r
try testing.expect(wrap(@as(i32, 180), @as(i32, 180)) == -180);
// One period, left limit, positive r
try testing.expect(wrap(@as(i32, -180), @as(i32, 180)) == -180);
// One period, right limit, negative r
try testing.expect(wrap(@as(i32, 180), @as(i32, -180)) == 180);
// One period, left limit, negative r
try testing.expect(wrap(@as(i32, -180), @as(i32, -180)) == 180);
// Two periods, right limit, positive r
try testing.expect(wrap(@as(i32, 540), @as(i32, 180)) == -180);
// Two periods, left limit, positive r
try testing.expect(wrap(@as(i32, -540), @as(i32, 180)) == -180);
// Two periods, right limit, negative r
try testing.expect(wrap(@as(i32, 540), @as(i32, -180)) == 180);
// Two periods, left limit, negative r
try testing.expect(wrap(@as(i32, -540), @as(i32, -180)) == 180);
// Floating point
try testing.expect(wrap(@as(f32, 1.125), @as(f32, 1.0)) == -0.875);
try testing.expect(wrap(@as(f32, -127.5), @as(f32, 180)) == -127.5);
// Mix of comptime and non-comptime
var i: i32 = 1;
_ = &i;
try testing.expect(wrap(i, 10) == 1);
const limit: i32 = 180;
// Within range
try testing.expect(wrap(@as(i32, -75), limit) == -75);
// Below
try testing.expect(wrap(@as(i32, -225), limit) == 135);
// Above
try testing.expect(wrap(@as(i32, 361), limit) == 1);
}
/// Odd ramp function
/// ```
/// | _____
/// | /
/// |/
/// -------/-------
/// /|
/// _____/ |
/// |
/// ```
/// Limit val to the inclusive range [lower, upper].
pub fn clamp(val: anytype, lower: anytype, upper: anytype) @TypeOf(val, lower, upper) {
const T = @TypeOf(val, lower, upper);
switch (@typeInfo(T)) {
.int, .float, .comptime_int, .comptime_float => assert(lower <= upper),
.vector => |vinfo| switch (@typeInfo(vinfo.child)) {
.int, .float => assert(@reduce(.And, lower <= upper)),
else => @compileError("Expected vector of ints or floats, found " ++ @typeName(T)),
},
else => @compileError("Expected an int, float or vector of one, found " ++ @typeName(T)),
}
return @max(lower, @min(val, upper));
}
test clamp {
// Within range
try testing.expect(std.math.clamp(@as(i32, -1), @as(i32, -4), @as(i32, 7)) == -1);
// Below
try testing.expect(std.math.clamp(@as(i32, -5), @as(i32, -4), @as(i32, 7)) == -4);
// Above
try testing.expect(std.math.clamp(@as(i32, 8), @as(i32, -4), @as(i32, 7)) == 7);
// Floating point
try testing.expect(std.math.clamp(@as(f32, 1.1), @as(f32, 0.0), @as(f32, 1.0)) == 1.0);
try testing.expect(std.math.clamp(@as(f32, -127.5), @as(f32, -200), @as(f32, -100)) == -127.5);
// Vector
try testing.expect(@reduce(.And, std.math.clamp(@as(@Vector(3, f32), .{ 1.4, 15.23, 28.3 }), @as(@Vector(3, f32), .{ 9.8, 13.2, 15.6 }), @as(@Vector(3, f32), .{ 15.2, 22.8, 26.3 })) == @as(@Vector(3, f32), .{ 9.8, 15.23, 26.3 })));
// Mix of comptime and non-comptime
var i: i32 = 1;
_ = &i;
try testing.expect(std.math.clamp(i, 0, 1) == 1);
}
/// Returns the product of a and b. Returns an error on overflow.
pub fn mul(comptime T: type, a: T, b: T) (error{Overflow}!T) {
if (T == comptime_int) return a * b;
const ov = @mulWithOverflow(a, b);
if (ov[1] != 0) return error.Overflow;
return ov[0];
}
/// Returns the sum of a and b. Returns an error on overflow.
pub fn add(comptime T: type, a: T, b: T) (error{Overflow}!T) {
if (T == comptime_int) return a + b;
const ov = @addWithOverflow(a, b);
if (ov[1] != 0) return error.Overflow;
return ov[0];
}
/// Returns a - b, or an error on overflow.
pub fn sub(comptime T: type, a: T, b: T) (error{Overflow}!T) {
if (T == comptime_int) return a - b;
const ov = @subWithOverflow(a, b);
if (ov[1] != 0) return error.Overflow;
return ov[0];
}
pub fn negate(x: anytype) !@TypeOf(x) {
return sub(@TypeOf(x), 0, x);
}
/// Shifts a left by shift_amt. Returns an error on overflow. shift_amt
/// is unsigned.
pub fn shlExact(comptime T: type, a: T, shift_amt: Log2Int(T)) !T {
if (T == comptime_int) return a << shift_amt;
const ov = @shlWithOverflow(a, shift_amt);
if (ov[1] != 0) return error.Overflow;
return ov[0];
}
/// Shifts left. Overflowed bits are truncated.
/// A negative shift amount results in a right shift.
pub fn shl(comptime T: type, a: T, shift_amt: anytype) T {
const abs_shift_amt = @abs(shift_amt);
const casted_shift_amt = blk: {
if (@typeInfo(T) == .vector) {
const C = @typeInfo(T).vector.child;
const len = @typeInfo(T).vector.len;
if (abs_shift_amt >= @typeInfo(C).int.bits) return @splat(0);
break :blk @as(@Vector(len, Log2Int(C)), @splat(@as(Log2Int(C), @intCast(abs_shift_amt))));
} else {
if (abs_shift_amt >= @typeInfo(T).int.bits) return 0;
break :blk @as(Log2Int(T), @intCast(abs_shift_amt));
}
};
if (@TypeOf(shift_amt) == comptime_int or @typeInfo(@TypeOf(shift_amt)).int.signedness == .signed) {
if (shift_amt < 0) {
return a >> casted_shift_amt;
}
}
return a << casted_shift_amt;
}
test shl {
try testing.expect(shl(u8, 0b11111111, @as(usize, 3)) == 0b11111000);
try testing.expect(shl(u8, 0b11111111, @as(usize, 8)) == 0);
try testing.expect(shl(u8, 0b11111111, @as(usize, 9)) == 0);
try testing.expect(shl(u8, 0b11111111, @as(isize, -2)) == 0b00111111);
try testing.expect(shl(u8, 0b11111111, 3) == 0b11111000);
try testing.expect(shl(u8, 0b11111111, 8) == 0);
try testing.expect(shl(u8, 0b11111111, 9) == 0);
try testing.expect(shl(u8, 0b11111111, -2) == 0b00111111);
try testing.expect(shl(@Vector(1, u32), @Vector(1, u32){42}, @as(usize, 1))[0] == @as(u32, 42) << 1);
try testing.expect(shl(@Vector(1, u32), @Vector(1, u32){42}, @as(isize, -1))[0] == @as(u32, 42) >> 1);
try testing.expect(shl(@Vector(1, u32), @Vector(1, u32){42}, 33)[0] == 0);
}
/// Shifts right. Overflowed bits are truncated.
/// A negative shift amount results in a left shift.
pub fn shr(comptime T: type, a: T, shift_amt: anytype) T {
const abs_shift_amt = @abs(shift_amt);
const casted_shift_amt = blk: {
if (@typeInfo(T) == .vector) {
const C = @typeInfo(T).vector.child;
const len = @typeInfo(T).vector.len;
if (abs_shift_amt >= @typeInfo(C).int.bits) return @splat(0);
break :blk @as(@Vector(len, Log2Int(C)), @splat(@as(Log2Int(C), @intCast(abs_shift_amt))));
} else {
if (abs_shift_amt >= @typeInfo(T).int.bits) return 0;
break :blk @as(Log2Int(T), @intCast(abs_shift_amt));
}
};
if (@TypeOf(shift_amt) == comptime_int or @typeInfo(@TypeOf(shift_amt)).int.signedness == .signed) {
if (shift_amt < 0) {
return a << casted_shift_amt;
}
}
return a >> casted_shift_amt;
}
test shr {
try testing.expect(shr(u8, 0b11111111, @as(usize, 3)) == 0b00011111);
try testing.expect(shr(u8, 0b11111111, @as(usize, 8)) == 0);
try testing.expect(shr(u8, 0b11111111, @as(usize, 9)) == 0);
try testing.expect(shr(u8, 0b11111111, @as(isize, -2)) == 0b11111100);
try testing.expect(shr(u8, 0b11111111, 3) == 0b00011111);
try testing.expect(shr(u8, 0b11111111, 8) == 0);
try testing.expect(shr(u8, 0b11111111, 9) == 0);
try testing.expect(shr(u8, 0b11111111, -2) == 0b11111100);
try testing.expect(shr(@Vector(1, u32), @Vector(1, u32){42}, @as(usize, 1))[0] == @as(u32, 42) >> 1);
try testing.expect(shr(@Vector(1, u32), @Vector(1, u32){42}, @as(isize, -1))[0] == @as(u32, 42) << 1);
try testing.expect(shr(@Vector(1, u32), @Vector(1, u32){42}, 33)[0] == 0);
}
/// Rotates right. Only unsigned values can be rotated. Negative shift
/// values result in shift modulo the bit count.
pub fn rotr(comptime T: type, x: T, r: anytype) T {
if (@typeInfo(T) == .vector) {
const C = @typeInfo(T).vector.child;
if (C == u0) return 0;
if (@typeInfo(C).int.signedness == .signed) {
@compileError("cannot rotate signed integers");
}
const ar: Log2Int(C) = @intCast(@mod(r, @typeInfo(C).int.bits));
return (x >> @splat(ar)) | (x << @splat(1 + ~ar));
} else if (@typeInfo(T).int.signedness == .signed) {
@compileError("cannot rotate signed integer");
} else {
if (T == u0) return 0;
if (comptime isPowerOfTwo(@typeInfo(T).int.bits)) {
const ar: Log2Int(T) = @intCast(@mod(r, @typeInfo(T).int.bits));
return x >> ar | x << (1 +% ~ar);
} else {
const ar = @mod(r, @typeInfo(T).int.bits);
return shr(T, x, ar) | shl(T, x, @typeInfo(T).int.bits - ar);
}
}
}
test rotr {
try testing.expect(rotr(u0, 0b0, @as(usize, 3)) == 0b0);
try testing.expect(rotr(u5, 0b00001, @as(usize, 0)) == 0b00001);
try testing.expect(rotr(u6, 0b000001, @as(usize, 7)) == 0b100000);
try testing.expect(rotr(u8, 0b00000001, @as(usize, 0)) == 0b00000001);
try testing.expect(rotr(u8, 0b00000001, @as(usize, 9)) == 0b10000000);
try testing.expect(rotr(u8, 0b00000001, @as(usize, 8)) == 0b00000001);
try testing.expect(rotr(u8, 0b00000001, @as(usize, 4)) == 0b00010000);
try testing.expect(rotr(u8, 0b00000001, @as(isize, -1)) == 0b00000010);
try testing.expect(rotr(u12, 0o7777, 1) == 0o7777);
try testing.expect(rotr(@Vector(1, u32), @Vector(1, u32){1}, @as(usize, 1))[0] == @as(u32, 1) << 31);
try testing.expect(rotr(@Vector(1, u32), @Vector(1, u32){1}, @as(isize, -1))[0] == @as(u32, 1) << 1);
}
/// Rotates left. Only unsigned values can be rotated. Negative shift
/// values result in shift modulo the bit count.
pub fn rotl(comptime T: type, x: T, r: anytype) T {
if (@typeInfo(T) == .vector) {
const C = @typeInfo(T).vector.child;
if (C == u0) return 0;
if (@typeInfo(C).int.signedness == .signed) {
@compileError("cannot rotate signed integers");
}
const ar: Log2Int(C) = @intCast(@mod(r, @typeInfo(C).int.bits));
return (x << @splat(ar)) | (x >> @splat(1 +% ~ar));
} else if (@typeInfo(T).int.signedness == .signed) {
@compileError("cannot rotate signed integer");
} else {
if (T == u0) return 0;
if (comptime isPowerOfTwo(@typeInfo(T).int.bits)) {
const ar: Log2Int(T) = @intCast(@mod(r, @typeInfo(T).int.bits));
return x << ar | x >> 1 +% ~ar;
} else {
const ar = @mod(r, @typeInfo(T).int.bits);
return shl(T, x, ar) | shr(T, x, @typeInfo(T).int.bits - ar);
}
}
}
test rotl {
try testing.expect(rotl(u0, 0b0, @as(usize, 3)) == 0b0);
try testing.expect(rotl(u5, 0b00001, @as(usize, 0)) == 0b00001);
try testing.expect(rotl(u6, 0b000001, @as(usize, 7)) == 0b000010);
try testing.expect(rotl(u8, 0b00000001, @as(usize, 0)) == 0b00000001);
try testing.expect(rotl(u8, 0b00000001, @as(usize, 9)) == 0b00000010);
try testing.expect(rotl(u8, 0b00000001, @as(usize, 8)) == 0b00000001);
try testing.expect(rotl(u8, 0b00000001, @as(usize, 4)) == 0b00010000);
try testing.expect(rotl(u8, 0b00000001, @as(isize, -1)) == 0b10000000);
try testing.expect(rotl(u12, 0o7777, 1) == 0o7777);
try testing.expect(rotl(@Vector(1, u32), @Vector(1, u32){1 << 31}, @as(usize, 1))[0] == 1);
try testing.expect(rotl(@Vector(1, u32), @Vector(1, u32){1 << 31}, @as(isize, -1))[0] == @as(u32, 1) << 30);
}
/// Returns an unsigned int type that can hold the number of bits in T - 1.
/// Suitable for 0-based bit indices of T.
pub fn Log2Int(comptime T: type) type {
// comptime ceil log2
if (T == comptime_int) return comptime_int;
const bits: u16 = @typeInfo(T).int.bits;
const log2_bits = 16 - @clz(bits - 1);
return std.meta.Int(.unsigned, log2_bits);
}
/// Returns an unsigned int type that can hold the number of bits in T.
pub fn Log2IntCeil(comptime T: type) type {
// comptime ceil log2
if (T == comptime_int) return comptime_int;
const bits: u16 = @typeInfo(T).int.bits;
const log2_bits = 16 - @clz(bits);
return std.meta.Int(.unsigned, log2_bits);
}
/// Returns the smallest integer type that can hold both from and to.
pub fn IntFittingRange(comptime from: comptime_int, comptime to: comptime_int) type {
assert(from <= to);
if (from == 0 and to == 0) {
return u0;
}
const signedness: std.builtin.Signedness = if (from < 0) .signed else .unsigned;
const largest_positive_integer = @max(if (from < 0) (-from) - 1 else from, to); // two's complement
const base = log2(largest_positive_integer);
const upper = (1 << base) - 1;
var magnitude_bits = if (upper >= largest_positive_integer) base else base + 1;
if (signedness == .signed) {
magnitude_bits += 1;
}
return std.meta.Int(signedness, magnitude_bits);
}
test IntFittingRange {
try testing.expect(IntFittingRange(0, 0) == u0);
try testing.expect(IntFittingRange(0, 1) == u1);
try testing.expect(IntFittingRange(0, 2) == u2);
try testing.expect(IntFittingRange(0, 3) == u2);
try testing.expect(IntFittingRange(0, 4) == u3);
try testing.expect(IntFittingRange(0, 7) == u3);
try testing.expect(IntFittingRange(0, 8) == u4);
try testing.expect(IntFittingRange(0, 9) == u4);
try testing.expect(IntFittingRange(0, 15) == u4);
try testing.expect(IntFittingRange(0, 16) == u5);
try testing.expect(IntFittingRange(0, 17) == u5);
try testing.expect(IntFittingRange(0, 4095) == u12);
try testing.expect(IntFittingRange(2000, 4095) == u12);
try testing.expect(IntFittingRange(0, 4096) == u13);
try testing.expect(IntFittingRange(2000, 4096) == u13);
try testing.expect(IntFittingRange(0, 4097) == u13);
try testing.expect(IntFittingRange(2000, 4097) == u13);
try testing.expect(IntFittingRange(0, 123456789123456798123456789) == u87);
try testing.expect(IntFittingRange(0, 123456789123456798123456789123456789123456798123456789) == u177);
try testing.expect(IntFittingRange(-1, -1) == i1);
try testing.expect(IntFittingRange(-1, 0) == i1);
try testing.expect(IntFittingRange(-1, 1) == i2);
try testing.expect(IntFittingRange(-2, -2) == i2);
try testing.expect(IntFittingRange(-2, -1) == i2);
try testing.expect(IntFittingRange(-2, 0) == i2);
try testing.expect(IntFittingRange(-2, 1) == i2);
try testing.expect(IntFittingRange(-2, 2) == i3);
try testing.expect(IntFittingRange(-1, 2) == i3);
try testing.expect(IntFittingRange(-1, 3) == i3);
try testing.expect(IntFittingRange(-1, 4) == i4);
try testing.expect(IntFittingRange(-1, 7) == i4);
try testing.expect(IntFittingRange(-1, 8) == i5);
try testing.expect(IntFittingRange(-1, 9) == i5);
try testing.expect(IntFittingRange(-1, 15) == i5);
try testing.expect(IntFittingRange(-1, 16) == i6);
try testing.expect(IntFittingRange(-1, 17) == i6);
try testing.expect(IntFittingRange(-1, 4095) == i13);
try testing.expect(IntFittingRange(-4096, 4095) == i13);
try testing.expect(IntFittingRange(-1, 4096) == i14);
try testing.expect(IntFittingRange(-4097, 4095) == i14);
try testing.expect(IntFittingRange(-1, 4097) == i14);
try testing.expect(IntFittingRange(-1, 123456789123456798123456789) == i88);
try testing.expect(IntFittingRange(-1, 123456789123456798123456789123456789123456798123456789) == i178);
}
test "overflow functions" {
try testOverflow();
try comptime testOverflow();
}
fn testOverflow() !void {
try testing.expect((mul(i32, 3, 4) catch unreachable) == 12);
try testing.expect((add(i32, 3, 4) catch unreachable) == 7);
try testing.expect((sub(i32, 3, 4) catch unreachable) == -1);
try testing.expect((shlExact(i32, 0b11, 4) catch unreachable) == 0b110000);
}
/// Divide numerator by denominator, rounding toward zero. Returns an
/// error on overflow or when denominator is zero.
pub fn divTrunc(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed and numerator == minInt(T) and denominator == -1) return error.Overflow;
return @divTrunc(numerator, denominator);
}
test divTrunc {
try testDivTrunc();
try comptime testDivTrunc();
}
fn testDivTrunc() !void {
try testing.expect((divTrunc(i32, 5, 3) catch unreachable) == 1);
try testing.expect((divTrunc(i32, -5, 3) catch unreachable) == -1);
try testing.expectError(error.DivisionByZero, divTrunc(i8, -5, 0));
try testing.expectError(error.Overflow, divTrunc(i8, -128, -1));
try testing.expect((divTrunc(f32, 5.0, 3.0) catch unreachable) == 1.0);
try testing.expect((divTrunc(f32, -5.0, 3.0) catch unreachable) == -1.0);
}
/// Divide numerator by denominator, rounding toward negative
/// infinity. Returns an error on overflow or when denominator is
/// zero.
pub fn divFloor(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed and numerator == minInt(T) and denominator == -1) return error.Overflow;
return @divFloor(numerator, denominator);
}
test divFloor {
try testDivFloor();
try comptime testDivFloor();
}
fn testDivFloor() !void {
try testing.expect((divFloor(i32, 5, 3) catch unreachable) == 1);
try testing.expect((divFloor(i32, -5, 3) catch unreachable) == -2);
try testing.expectError(error.DivisionByZero, divFloor(i8, -5, 0));
try testing.expectError(error.Overflow, divFloor(i8, -128, -1));
try testing.expect((divFloor(f32, 5.0, 3.0) catch unreachable) == 1.0);
try testing.expect((divFloor(f32, -5.0, 3.0) catch unreachable) == -2.0);
}
/// Divide numerator by denominator, rounding toward positive
/// infinity. Returns an error on overflow or when denominator is
/// zero.
pub fn divCeil(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
const info = @typeInfo(T);
switch (info) {
.comptime_float, .float => return @ceil(numerator / denominator),
.comptime_int, .int => {
if (numerator < 0 and denominator < 0) {
if (info == .int and numerator == minInt(T) and denominator == -1)
return error.Overflow;
return @divFloor(numerator + 1, denominator) + 1;
}
if (numerator > 0 and denominator > 0)
return @divFloor(numerator - 1, denominator) + 1;
return @divTrunc(numerator, denominator);
},
else => @compileError("divCeil unsupported on " ++ @typeName(T)),
}
}
test divCeil {
try testDivCeil();
try comptime testDivCeil();
}
fn testDivCeil() !void {
try testing.expectEqual(@as(i32, 2), divCeil(i32, 5, 3) catch unreachable);
try testing.expectEqual(@as(i32, -1), divCeil(i32, -5, 3) catch unreachable);
try testing.expectEqual(@as(i32, -1), divCeil(i32, 5, -3) catch unreachable);
try testing.expectEqual(@as(i32, 2), divCeil(i32, -5, -3) catch unreachable);
try testing.expectEqual(@as(i32, 0), divCeil(i32, 0, 5) catch unreachable);
try testing.expectEqual(@as(u32, 0), divCeil(u32, 0, 5) catch unreachable);
try testing.expectError(error.DivisionByZero, divCeil(i8, -5, 0));
try testing.expectError(error.Overflow, divCeil(i8, -128, -1));
try testing.expectEqual(@as(f32, 0.0), divCeil(f32, 0.0, 5.0) catch unreachable);
try testing.expectEqual(@as(f32, 2.0), divCeil(f32, 5.0, 3.0) catch unreachable);
try testing.expectEqual(@as(f32, -1.0), divCeil(f32, -5.0, 3.0) catch unreachable);
try testing.expectEqual(@as(f32, -1.0), divCeil(f32, 5.0, -3.0) catch unreachable);
try testing.expectEqual(@as(f32, 2.0), divCeil(f32, -5.0, -3.0) catch unreachable);
try testing.expectEqual(6, divCeil(comptime_int, 23, 4) catch unreachable);
try testing.expectEqual(-5, divCeil(comptime_int, -23, 4) catch unreachable);
try testing.expectEqual(-5, divCeil(comptime_int, 23, -4) catch unreachable);
try testing.expectEqual(6, divCeil(comptime_int, -23, -4) catch unreachable);
try testing.expectError(error.DivisionByZero, divCeil(comptime_int, 23, 0));
try testing.expectEqual(6.0, divCeil(comptime_float, 23.0, 4.0) catch unreachable);
try testing.expectEqual(-5.0, divCeil(comptime_float, -23.0, 4.0) catch unreachable);
try testing.expectEqual(-5.0, divCeil(comptime_float, 23.0, -4.0) catch unreachable);
try testing.expectEqual(6.0, divCeil(comptime_float, -23.0, -4.0) catch unreachable);
try testing.expectError(error.DivisionByZero, divCeil(comptime_float, 23.0, 0.0));
}
/// Divide numerator by denominator. Return an error if quotient is
/// not an integer, denominator is zero, or on overflow.
pub fn divExact(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed and numerator == minInt(T) and denominator == -1) return error.Overflow;
const result = @divTrunc(numerator, denominator);
if (result * denominator != numerator) return error.UnexpectedRemainder;
return result;
}
test divExact {
try testDivExact();
try comptime testDivExact();
}
fn testDivExact() !void {
try testing.expect((divExact(i32, 10, 5) catch unreachable) == 2);
try testing.expect((divExact(i32, -10, 5) catch unreachable) == -2);
try testing.expectError(error.DivisionByZero, divExact(i8, -5, 0));
try testing.expectError(error.Overflow, divExact(i8, -128, -1));
try testing.expectError(error.UnexpectedRemainder, divExact(i32, 5, 2));
try testing.expect((divExact(f32, 10.0, 5.0) catch unreachable) == 2.0);
try testing.expect((divExact(f32, -10.0, 5.0) catch unreachable) == -2.0);
try testing.expectError(error.UnexpectedRemainder, divExact(f32, 5.0, 2.0));
}
/// Returns numerator modulo denominator, or an error if denominator is
/// zero or negative. Negative numerators never result in negative
/// return values.
pub fn mod(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
if (denominator < 0) return error.NegativeDenominator;
return @mod(numerator, denominator);
}
test mod {
try testMod();
try comptime testMod();
}
fn testMod() !void {
try testing.expect((mod(i32, -5, 3) catch unreachable) == 1);
try testing.expect((mod(i32, 5, 3) catch unreachable) == 2);
try testing.expectError(error.NegativeDenominator, mod(i32, 10, -1));
try testing.expectError(error.DivisionByZero, mod(i32, 10, 0));
try testing.expect((mod(f32, -5, 3) catch unreachable) == 1);
try testing.expect((mod(f32, 5, 3) catch unreachable) == 2);
try testing.expectError(error.NegativeDenominator, mod(f32, 10, -1));
try testing.expectError(error.DivisionByZero, mod(f32, 10, 0));
}
/// Returns the remainder when numerator is divided by denominator, or
/// an error if denominator is zero or negative. Negative numerators
/// can give negative results.
pub fn rem(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
if (denominator < 0) return error.NegativeDenominator;
return @rem(numerator, denominator);
}
test rem {
try testRem();
try comptime testRem();
}
fn testRem() !void {
try testing.expect((rem(i32, -5, 3) catch unreachable) == -2);
try testing.expect((rem(i32, 5, 3) catch unreachable) == 2);
try testing.expectError(error.NegativeDenominator, rem(i32, 10, -1));
try testing.expectError(error.DivisionByZero, rem(i32, 10, 0));
try testing.expect((rem(f32, -5, 3) catch unreachable) == -2);
try testing.expect((rem(f32, 5, 3) catch unreachable) == 2);
try testing.expectError(error.NegativeDenominator, rem(f32, 10, -1));
try testing.expectError(error.DivisionByZero, rem(f32, 10, 0));
}
/// Returns the negation of the integer parameter.
/// Result is a signed integer.
pub fn negateCast(x: anytype) !std.meta.Int(.signed, @bitSizeOf(@TypeOf(x))) {
if (@typeInfo(@TypeOf(x)).int.signedness == .signed) return negate(x);
const int = std.meta.Int(.signed, @bitSizeOf(@TypeOf(x)));
if (x > -minInt(int)) return error.Overflow;
if (x == -minInt(int)) return minInt(int);
return -@as(int, @intCast(x));
}
test negateCast {
try testing.expect((negateCast(@as(u32, 999)) catch unreachable) == -999);
try testing.expect(@TypeOf(negateCast(@as(u32, 999)) catch unreachable) == i32);
try testing.expect((negateCast(@as(u32, -minInt(i32))) catch unreachable) == minInt(i32));
try testing.expect(@TypeOf(negateCast(@as(u32, -minInt(i32))) catch unreachable) == i32);
try testing.expectError(error.Overflow, negateCast(@as(u32, maxInt(i32) + 10)));
}
/// Cast an integer to a different integer type. If the value doesn't fit,
/// return null.
pub fn cast(comptime T: type, x: anytype) ?T {
comptime assert(@typeInfo(T) == .int); // must pass an integer
const is_comptime = @TypeOf(x) == comptime_int;
comptime assert(is_comptime or @typeInfo(@TypeOf(x)) == .int); // must pass an integer
if ((is_comptime or maxInt(@TypeOf(x)) > maxInt(T)) and x > maxInt(T)) {
return null;
} else if ((is_comptime or minInt(@TypeOf(x)) < minInt(T)) and x < minInt(T)) {
return null;
} else {
return @as(T, @intCast(x));
}
}
test cast {
try testing.expect(cast(u8, 300) == null);
try testing.expect(cast(u8, @as(u32, 300)) == null);
try testing.expect(cast(i8, -200) == null);
try testing.expect(cast(i8, @as(i32, -200)) == null);
try testing.expect(cast(u8, -1) == null);
try testing.expect(cast(u8, @as(i8, -1)) == null);
try testing.expect(cast(u64, -1) == null);
try testing.expect(cast(u64, @as(i8, -1)) == null);
try testing.expect(cast(u8, 255).? == @as(u8, 255));
try testing.expect(cast(u8, @as(u32, 255)).? == @as(u8, 255));
try testing.expect(@TypeOf(cast(u8, 255).?) == u8);
try testing.expect(@TypeOf(cast(u8, @as(u32, 255)).?) == u8);
}
pub const AlignCastError = error{UnalignedMemory};
fn AlignCastResult(comptime alignment: u29, comptime Ptr: type) type {
var ptr_info = @typeInfo(Ptr);
ptr_info.pointer.alignment = alignment;
return @Type(ptr_info);
}
/// Align cast a pointer but return an error if it's the wrong alignment
pub fn alignCast(comptime alignment: u29, ptr: anytype) AlignCastError!AlignCastResult(alignment, @TypeOf(ptr)) {
const addr = @intFromPtr(ptr);
if (addr % alignment != 0) {
return error.UnalignedMemory;
}
return @alignCast(ptr);
}
/// Asserts `int > 0`.
pub fn isPowerOfTwo(int: anytype) bool {
assert(int > 0);
return (int & (int - 1)) == 0;
}
test isPowerOfTwo {
try testing.expect(isPowerOfTwo(@as(u8, 1)));
try testing.expect(isPowerOfTwo(2));
try testing.expect(!isPowerOfTwo(@as(i16, 3)));
try testing.expect(isPowerOfTwo(4));
try testing.expect(!isPowerOfTwo(@as(u32, 31)));
try testing.expect(isPowerOfTwo(32));
try testing.expect(!isPowerOfTwo(@as(i64, 63)));
try testing.expect(isPowerOfTwo(128));
try testing.expect(isPowerOfTwo(@as(u128, 256)));
}
/// Aligns the given integer type bit width to a width divisible by 8.
pub fn ByteAlignedInt(comptime T: type) type {
const info = @typeInfo(T).int;
const bits = (info.bits + 7) / 8 * 8;
const extended_type = std.meta.Int(info.signedness, bits);
return extended_type;
}
test ByteAlignedInt {
try testing.expect(ByteAlignedInt(u0) == u0);
try testing.expect(ByteAlignedInt(i0) == i0);
try testing.expect(ByteAlignedInt(u3) == u8);
try testing.expect(ByteAlignedInt(u8) == u8);
try testing.expect(ByteAlignedInt(i111) == i112);
try testing.expect(ByteAlignedInt(u129) == u136);
}
/// Rounds the given floating point number to the nearest integer.
/// If two integers are equally close, rounds away from zero.
/// Uses a dedicated hardware instruction when available.
/// This is the same as calling the builtin @round
pub inline fn round(value: anytype) @TypeOf(value) {
return @round(value);
}
/// Rounds the given floating point number to an integer, towards zero.
/// Uses a dedicated hardware instruction when available.
/// This is the same as calling the builtin @trunc
pub inline fn trunc(value: anytype) @TypeOf(value) {
return @trunc(value);
}
/// Returns the largest integral value not greater than the given floating point number.
/// Uses a dedicated hardware instruction when available.
/// This is the same as calling the builtin @floor
pub inline fn floor(value: anytype) @TypeOf(value) {
return @floor(value);
}
/// Returns the nearest power of two less than or equal to value, or
/// zero if value is less than or equal to zero.
pub fn floorPowerOfTwo(comptime T: type, value: T) T {
const uT = std.meta.Int(.unsigned, @typeInfo(T).int.bits);
if (value <= 0) return 0;
return @as(T, 1) << log2_int(uT, @as(uT, @intCast(value)));
}
test floorPowerOfTwo {
try testFloorPowerOfTwo();
try comptime testFloorPowerOfTwo();
}
fn testFloorPowerOfTwo() !void {
try testing.expect(floorPowerOfTwo(u32, 63) == 32);
try testing.expect(floorPowerOfTwo(u32, 64) == 64);
try testing.expect(floorPowerOfTwo(u32, 65) == 64);
try testing.expect(floorPowerOfTwo(u32, 0) == 0);
try testing.expect(floorPowerOfTwo(u4, 7) == 4);
try testing.expect(floorPowerOfTwo(u4, 8) == 8);
try testing.expect(floorPowerOfTwo(u4, 9) == 8);
try testing.expect(floorPowerOfTwo(u4, 0) == 0);
try testing.expect(floorPowerOfTwo(i4, 7) == 4);
try testing.expect(floorPowerOfTwo(i4, -8) == 0);
try testing.expect(floorPowerOfTwo(i4, -1) == 0);
try testing.expect(floorPowerOfTwo(i4, 0) == 0);
}
/// Returns the smallest integral value not less than the given floating point number.
/// Uses a dedicated hardware instruction when available.
/// This is the same as calling the builtin @ceil
pub inline fn ceil(value: anytype) @TypeOf(value) {
return @ceil(value);
}
/// Returns the next power of two (if the value is not already a power of two).
/// Only unsigned integers can be used. Zero is not an allowed input.
/// Result is a type with 1 more bit than the input type.
pub fn ceilPowerOfTwoPromote(comptime T: type, value: T) std.meta.Int(@typeInfo(T).int.signedness, @typeInfo(T).int.bits + 1) {
comptime assert(@typeInfo(T) == .int);
comptime assert(@typeInfo(T).int.signedness == .unsigned);
assert(value != 0);
const PromotedType = std.meta.Int(@typeInfo(T).int.signedness, @typeInfo(T).int.bits + 1);
const ShiftType = std.math.Log2Int(PromotedType);
return @as(PromotedType, 1) << @as(ShiftType, @intCast(@typeInfo(T).int.bits - @clz(value - 1)));
}
/// Returns the next power of two (if the value is not already a power of two).
/// Only unsigned integers can be used. Zero is not an allowed input.
/// If the value doesn't fit, returns an error.
pub fn ceilPowerOfTwo(comptime T: type, value: T) (error{Overflow}!T) {
comptime assert(@typeInfo(T) == .int);
const info = @typeInfo(T).int;
comptime assert(info.signedness == .unsigned);
const PromotedType = std.meta.Int(info.signedness, info.bits + 1);
const overflowBit = @as(PromotedType, 1) << info.bits;
const x = ceilPowerOfTwoPromote(T, value);
if (overflowBit & x != 0) {
return error.Overflow;
}
return @as(T, @intCast(x));
}
/// Returns the next power of two (if the value is not already a power
/// of two). Only unsigned integers can be used. Zero is not an
/// allowed input. Asserts that the value fits.
pub fn ceilPowerOfTwoAssert(comptime T: type, value: T) T {
return ceilPowerOfTwo(T, value) catch unreachable;
}
test ceilPowerOfTwoPromote {
try testCeilPowerOfTwoPromote();
try comptime testCeilPowerOfTwoPromote();
}
fn testCeilPowerOfTwoPromote() !void {
try testing.expectEqual(@as(u33, 1), ceilPowerOfTwoPromote(u32, 1));
try testing.expectEqual(@as(u33, 2), ceilPowerOfTwoPromote(u32, 2));
try testing.expectEqual(@as(u33, 64), ceilPowerOfTwoPromote(u32, 63));
try testing.expectEqual(@as(u33, 64), ceilPowerOfTwoPromote(u32, 64));
try testing.expectEqual(@as(u33, 128), ceilPowerOfTwoPromote(u32, 65));
try testing.expectEqual(@as(u6, 8), ceilPowerOfTwoPromote(u5, 7));
try testing.expectEqual(@as(u6, 8), ceilPowerOfTwoPromote(u5, 8));
try testing.expectEqual(@as(u6, 16), ceilPowerOfTwoPromote(u5, 9));
try testing.expectEqual(@as(u5, 16), ceilPowerOfTwoPromote(u4, 9));
}
test ceilPowerOfTwo {
try testCeilPowerOfTwo();
try comptime testCeilPowerOfTwo();
}
fn testCeilPowerOfTwo() !void {
try testing.expectEqual(@as(u32, 1), try ceilPowerOfTwo(u32, 1));
try testing.expectEqual(@as(u32, 2), try ceilPowerOfTwo(u32, 2));
try testing.expectEqual(@as(u32, 64), try ceilPowerOfTwo(u32, 63));
try testing.expectEqual(@as(u32, 64), try ceilPowerOfTwo(u32, 64));
try testing.expectEqual(@as(u32, 128), try ceilPowerOfTwo(u32, 65));
try testing.expectEqual(@as(u5, 8), try ceilPowerOfTwo(u5, 7));
try testing.expectEqual(@as(u5, 8), try ceilPowerOfTwo(u5, 8));
try testing.expectEqual(@as(u5, 16), try ceilPowerOfTwo(u5, 9));
try testing.expectError(error.Overflow, ceilPowerOfTwo(u4, 9));
}
/// Return the log base 2 of integer value x, rounding down to the
/// nearest integer.
pub fn log2_int(comptime T: type, x: T) Log2Int(T) {
if (@typeInfo(T) != .int or @typeInfo(T).int.signedness != .unsigned)
@compileError("log2_int requires an unsigned integer, found " ++ @typeName(T));
assert(x != 0);
return @as(Log2Int(T), @intCast(@typeInfo(T).int.bits - 1 - @clz(x)));
}
/// Return the log base 2 of integer value x, rounding up to the
/// nearest integer.
pub fn log2_int_ceil(comptime T: type, x: T) Log2IntCeil(T) {
if (@typeInfo(T) != .int or @typeInfo(T).int.signedness != .unsigned)
@compileError("log2_int_ceil requires an unsigned integer, found " ++ @typeName(T));
assert(x != 0);
if (x == 1) return 0;
const log2_val: Log2IntCeil(T) = log2_int(T, x - 1);
return log2_val + 1;
}
test log2_int_ceil {
try testing.expect(log2_int_ceil(u32, 1) == 0);
try testing.expect(log2_int_ceil(u32, 2) == 1);
try testing.expect(log2_int_ceil(u32, 3) == 2);
try testing.expect(log2_int_ceil(u32, 4) == 2);
try testing.expect(log2_int_ceil(u32, 5) == 3);
try testing.expect(log2_int_ceil(u32, 6) == 3);
try testing.expect(log2_int_ceil(u32, 7) == 3);
try testing.expect(log2_int_ceil(u32, 8) == 3);
try testing.expect(log2_int_ceil(u32, 9) == 4);
try testing.expect(log2_int_ceil(u32, 10) == 4);
}
/// Cast a value to a different type. If the value doesn't fit in, or
/// can't be perfectly represented by, the new type, it will be
/// converted to the closest possible representation.
pub fn lossyCast(comptime T: type, value: anytype) T {
switch (@typeInfo(T)) {
.float => {
switch (@typeInfo(@TypeOf(value))) {
.int => return @floatFromInt(value),
.float => return @floatCast(value),
.comptime_int => return value,
.comptime_float => return value,
else => @compileError("bad type"),
}
},
.int => {
switch (@typeInfo(@TypeOf(value))) {
.int, .comptime_int => {
if (value >= maxInt(T)) {
return maxInt(T);
} else if (value <= minInt(T)) {
return minInt(T);
} else {
return @intCast(value);
}
},
.float, .comptime_float => {
if (isNan(value)) {
return 0;
} else if (value >= maxInt(T)) {
return maxInt(T);
} else if (value <= minInt(T)) {
return minInt(T);
} else {
return @intFromFloat(value);
}
},
else => @compileError("bad type"),
}
},
else => @compileError("bad result type"),
}
}
test lossyCast {
try testing.expect(lossyCast(i16, 70000.0) == @as(i16, 32767));
try testing.expect(lossyCast(u32, @as(i16, -255)) == @as(u32, 0));
try testing.expect(lossyCast(i9, @as(u32, 200)) == @as(i9, 200));
try testing.expect(lossyCast(u32, @as(f32, maxInt(u32))) == maxInt(u32));
try testing.expect(lossyCast(u32, nan(f32)) == 0);
}
/// Performs linear interpolation between *a* and *b* based on *t*.
/// *t* ranges from 0.0 to 1.0, but may exceed these bounds.
/// Supports floats and vectors of floats.
///
/// This does not guarantee returning *b* if *t* is 1 due to floating-point errors.
/// This is monotonic.
pub fn lerp(a: anytype, b: anytype, t: anytype) @TypeOf(a, b, t) {
const Type = @TypeOf(a, b, t);
return @mulAdd(Type, b - a, t, a);
}
test lerp {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/17884
if (builtin.zig_backend == .stage2_x86_64 and
!comptime std.Target.x86.featureSetHas(builtin.cpu.features, .fma)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/17884
try testing.expectEqual(@as(f64, 75), lerp(50, 100, 0.5));
try testing.expectEqual(@as(f32, 43.75), lerp(50, 25, 0.25));
try testing.expectEqual(@as(f64, -31.25), lerp(-50, 25, 0.25));
try testing.expectEqual(@as(f64, 30), lerp(10, 20, 2.0));
try testing.expectEqual(@as(f64, 5), lerp(10, 20, -0.5));
try testing.expectApproxEqRel(@as(f32, -7.16067345e+03), lerp(-10000.12345, -5000.12345, 0.56789), 1e-19);
try testing.expectApproxEqRel(@as(f64, 7.010987590521e+62), lerp(0.123456789e-64, 0.123456789e64, 0.56789), 1e-33);
try testing.expectEqual(@as(f32, 0.0), lerp(@as(f32, 1.0e8), 1.0, 1.0));
try testing.expectEqual(@as(f64, 0.0), lerp(@as(f64, 1.0e16), 1.0, 1.0));
try testing.expectEqual(@as(f32, 1.0), lerp(@as(f32, 1.0e7), 1.0, 1.0));
try testing.expectEqual(@as(f64, 1.0), lerp(@as(f64, 1.0e15), 1.0, 1.0));
{
const a: @Vector(3, f32) = @splat(0);
const b: @Vector(3, f32) = @splat(50);
const t: @Vector(3, f32) = @splat(0.5);
try testing.expectEqual(
@Vector(3, f32){ 25, 25, 25 },
lerp(a, b, t),
);
}
{
const a: @Vector(3, f64) = @splat(50);
const b: @Vector(3, f64) = @splat(100);
const t: @Vector(3, f64) = @splat(0.5);
try testing.expectEqual(
@Vector(3, f64){ 75, 75, 75 },
lerp(a, b, t),
);
}
{
const a: @Vector(2, f32) = @splat(40);
const b: @Vector(2, f32) = @splat(80);
const t: @Vector(2, f32) = @Vector(2, f32){ 0.25, 0.75 };
try testing.expectEqual(
@Vector(2, f32){ 50, 70 },
lerp(a, b, t),
);
}
}
/// Returns the maximum value of integer type T.
pub fn maxInt(comptime T: type) comptime_int {
const info = @typeInfo(T);
const bit_count = info.int.bits;
if (bit_count == 0) return 0;
return (1 << (bit_count - @intFromBool(info.int.signedness == .signed))) - 1;
}
/// Returns the minimum value of integer type T.
pub fn minInt(comptime T: type) comptime_int {
const info = @typeInfo(T);
const bit_count = info.int.bits;
if (info.int.signedness == .unsigned) return 0;
if (bit_count == 0) return 0;
return -(1 << (bit_count - 1));
}
test maxInt {
try testing.expect(maxInt(u0) == 0);
try testing.expect(maxInt(u1) == 1);
try testing.expect(maxInt(u8) == 255);
try testing.expect(maxInt(u16) == 65535);
try testing.expect(maxInt(u32) == 4294967295);
try testing.expect(maxInt(u64) == 18446744073709551615);
try testing.expect(maxInt(u128) == 340282366920938463463374607431768211455);
try testing.expect(maxInt(i0) == 0);
try testing.expect(maxInt(i1) == 0);
try testing.expect(maxInt(i8) == 127);
try testing.expect(maxInt(i16) == 32767);
try testing.expect(maxInt(i32) == 2147483647);
try testing.expect(maxInt(i63) == 4611686018427387903);
try testing.expect(maxInt(i64) == 9223372036854775807);
try testing.expect(maxInt(i128) == 170141183460469231731687303715884105727);
}
test minInt {
try testing.expect(minInt(u0) == 0);
try testing.expect(minInt(u1) == 0);
try testing.expect(minInt(u8) == 0);
try testing.expect(minInt(u16) == 0);
try testing.expect(minInt(u32) == 0);
try testing.expect(minInt(u63) == 0);
try testing.expect(minInt(u64) == 0);
try testing.expect(minInt(u128) == 0);
try testing.expect(minInt(i0) == 0);
try testing.expect(minInt(i1) == -1);
try testing.expect(minInt(i8) == -128);
try testing.expect(minInt(i16) == -32768);
try testing.expect(minInt(i32) == -2147483648);
try testing.expect(minInt(i63) == -4611686018427387904);
try testing.expect(minInt(i64) == -9223372036854775808);
try testing.expect(minInt(i128) == -170141183460469231731687303715884105728);
}
test "max value type" {
const x: u32 = maxInt(i32);
try testing.expect(x == 2147483647);
}
/// Multiply a and b. Return type is wide enough to guarantee no
/// overflow.
pub fn mulWide(comptime T: type, a: T, b: T) std.meta.Int(
@typeInfo(T).int.signedness,
@typeInfo(T).int.bits * 2,
) {
const ResultInt = std.meta.Int(
@typeInfo(T).int.signedness,
@typeInfo(T).int.bits * 2,
);
return @as(ResultInt, a) * @as(ResultInt, b);
}
test mulWide {
try testing.expect(mulWide(u8, 5, 5) == 25);
try testing.expect(mulWide(i8, 5, -5) == -25);
try testing.expect(mulWide(u8, 100, 100) == 10000);
}
/// See also `CompareOperator`.
pub const Order = enum {
/// Greater than (`>`)
gt,
/// Less than (`<`)
lt,
/// Equal (`==`)
eq,
pub fn invert(self: Order) Order {
return switch (self) {
.lt => .gt,
.eq => .eq,
.gt => .lt,
};
}
test invert {
try testing.expect(Order.invert(order(0, 0)) == .eq);
try testing.expect(Order.invert(order(1, 0)) == .lt);
try testing.expect(Order.invert(order(-1, 0)) == .gt);
}
pub fn differ(self: Order) ?Order {
return if (self != .eq) self else null;
}
test differ {
const neg: i32 = -1;
const zero: i32 = 0;
const pos: i32 = 1;
try testing.expect(order(zero, neg).differ() orelse
order(pos, zero) == .gt);
try testing.expect(order(zero, zero).differ() orelse
order(zero, zero) == .eq);
try testing.expect(order(pos, pos).differ() orelse
order(neg, zero) == .lt);
try testing.expect(order(zero, zero).differ() orelse
order(pos, neg).differ() orelse
order(neg, zero) == .gt);
try testing.expect(order(pos, pos).differ() orelse
order(pos, pos).differ() orelse
order(neg, neg) == .eq);
try testing.expect(order(zero, pos).differ() orelse
order(neg, pos).differ() orelse
order(pos, neg) == .lt);
}
pub fn compare(self: Order, op: CompareOperator) bool {
return switch (self) {
.lt => switch (op) {
.lt => true,
.lte => true,
.eq => false,
.gte => false,
.gt => false,
.neq => true,
},
.eq => switch (op) {
.lt => false,
.lte => true,
.eq => true,
.gte => true,
.gt => false,
.neq => false,
},
.gt => switch (op) {
.lt => false,
.lte => false,
.eq => false,
.gte => true,
.gt => true,
.neq => true,
},
};
}
// https://github.com/ziglang/zig/issues/19295
test "compare" {
try testing.expect(order(-1, 0).compare(.lt));
try testing.expect(order(-1, 0).compare(.lte));
try testing.expect(order(0, 0).compare(.lte));
try testing.expect(order(0, 0).compare(.eq));
try testing.expect(order(0, 0).compare(.gte));
try testing.expect(order(1, 0).compare(.gte));
try testing.expect(order(1, 0).compare(.gt));
try testing.expect(order(1, 0).compare(.neq));
}
};
/// Given two numbers, this function returns the order they are with respect to each other.
pub fn order(a: anytype, b: anytype) Order {
if (a == b) {
return .eq;
} else if (a < b) {
return .lt;
} else if (a > b) {
return .gt;
} else {
unreachable;
}
}
/// See also `Order`.
pub const CompareOperator = enum {
/// Less than (`<`)
lt,
/// Less than or equal (`<=`)
lte,
/// Equal (`==`)
eq,
/// Greater than or equal (`>=`)
gte,
/// Greater than (`>`)
gt,
/// Not equal (`!=`)
neq,
/// Reverse the direction of the comparison.
/// Use when swapping the left and right hand operands.
pub fn reverse(op: CompareOperator) CompareOperator {
return switch (op) {
.lt => .gt,
.lte => .gte,
.gt => .lt,
.gte => .lte,
.eq => .eq,
.neq => .neq,
};
}
test reverse {
inline for (@typeInfo(CompareOperator).@"enum".fields) |op_field| {
const op = @as(CompareOperator, @enumFromInt(op_field.value));
try testing.expect(compare(2, op, 3) == compare(3, op.reverse(), 2));
try testing.expect(compare(3, op, 3) == compare(3, op.reverse(), 3));
try testing.expect(compare(4, op, 3) == compare(3, op.reverse(), 4));
}
}
};
/// This function does the same thing as comparison operators, however the
/// operator is a runtime-known enum value. Works on any operands that
/// support comparison operators.
pub fn compare(a: anytype, op: CompareOperator, b: anytype) bool {
return switch (op) {
.lt => a < b,
.lte => a <= b,
.eq => a == b,
.neq => a != b,
.gt => a > b,
.gte => a >= b,
};
}
test compare {
try testing.expect(compare(@as(i8, -1), .lt, @as(u8, 255)));
try testing.expect(compare(@as(i8, 2), .gt, @as(u8, 1)));
try testing.expect(!compare(@as(i8, -1), .gte, @as(u8, 255)));
try testing.expect(compare(@as(u8, 255), .gt, @as(i8, -1)));
try testing.expect(!compare(@as(u8, 255), .lte, @as(i8, -1)));
try testing.expect(compare(@as(i8, -1), .lt, @as(u9, 255)));
try testing.expect(!compare(@as(i8, -1), .gte, @as(u9, 255)));
try testing.expect(compare(@as(u9, 255), .gt, @as(i8, -1)));
try testing.expect(!compare(@as(u9, 255), .lte, @as(i8, -1)));
try testing.expect(compare(@as(i9, -1), .lt, @as(u8, 255)));
try testing.expect(!compare(@as(i9, -1), .gte, @as(u8, 255)));
try testing.expect(compare(@as(u8, 255), .gt, @as(i9, -1)));
try testing.expect(!compare(@as(u8, 255), .lte, @as(i9, -1)));
try testing.expect(compare(@as(u8, 1), .lt, @as(u8, 2)));
try testing.expect(@as(u8, @bitCast(@as(i8, -1))) == @as(u8, 255));
try testing.expect(!compare(@as(u8, 255), .eq, @as(i8, -1)));
try testing.expect(compare(@as(u8, 1), .eq, @as(u8, 1)));
}
test order {
try testing.expect(order(0, 0) == .eq);
try testing.expect(order(1, 0) == .gt);
try testing.expect(order(-1, 0) == .lt);
}
/// Returns a mask of all ones if value is true,
/// and a mask of all zeroes if value is false.
/// Compiles to one instruction for register sized integers.
pub inline fn boolMask(comptime MaskInt: type, value: bool) MaskInt {
if (@typeInfo(MaskInt) != .int)
@compileError("boolMask requires an integer mask type.");
if (MaskInt == u0 or MaskInt == i0)
@compileError("boolMask cannot convert to u0 or i0, they are too small.");
// The u1 and i1 cases tend to overflow,
// so we special case them here.
if (MaskInt == u1) return @intFromBool(value);
if (MaskInt == i1) {
// The @as here is a workaround for #7950
return @as(i1, @bitCast(@as(u1, @intFromBool(value))));
}
return -%@as(MaskInt, @intCast(@intFromBool(value)));
}
test boolMask {
const runTest = struct {
fn runTest() !void {
try testing.expectEqual(@as(u1, 0), boolMask(u1, false));
try testing.expectEqual(@as(u1, 1), boolMask(u1, true));
try testing.expectEqual(@as(i1, 0), boolMask(i1, false));
try testing.expectEqual(@as(i1, -1), boolMask(i1, true));
try testing.expectEqual(@as(u13, 0), boolMask(u13, false));
try testing.expectEqual(@as(u13, 0x1FFF), boolMask(u13, true));
try testing.expectEqual(@as(i13, 0), boolMask(i13, false));
try testing.expectEqual(@as(i13, -1), boolMask(i13, true));
try testing.expectEqual(@as(u32, 0), boolMask(u32, false));
try testing.expectEqual(@as(u32, 0xFFFF_FFFF), boolMask(u32, true));
try testing.expectEqual(@as(i32, 0), boolMask(i32, false));
try testing.expectEqual(@as(i32, -1), boolMask(i32, true));
}
}.runTest;
try runTest();
try comptime runTest();
}
/// Return the mod of `num` with the smallest integer type
pub fn comptimeMod(num: anytype, comptime denom: comptime_int) IntFittingRange(0, denom - 1) {
return @as(IntFittingRange(0, denom - 1), @intCast(@mod(num, denom)));
}
pub const F80 = struct {
fraction: u64,
exp: u16,
pub fn toFloat(self: F80) f80 {
const int = (@as(u80, self.exp) << 64) | self.fraction;
return @as(f80, @bitCast(int));
}
pub fn fromFloat(x: f80) F80 {
const int = @as(u80, @bitCast(x));
return .{
.fraction = @as(u64, @truncate(int)),
.exp = @as(u16, @truncate(int >> 64)),
};
}
};
/// Returns -1, 0, or 1.
/// Supports integer and float types and vectors of integer and float types.
/// Unsigned integer types will always return 0 or 1.
/// Branchless.
pub inline fn sign(i: anytype) @TypeOf(i) {
const T = @TypeOf(i);
return switch (@typeInfo(T)) {
.int, .comptime_int => @as(T, @intFromBool(i > 0)) - @as(T, @intFromBool(i < 0)),
.float, .comptime_float => @as(T, @floatFromInt(@intFromBool(i > 0))) - @as(T, @floatFromInt(@intFromBool(i < 0))),
.vector => |vinfo| blk: {
switch (@typeInfo(vinfo.child)) {
.int, .float => {
const zero: T = @splat(0);
const one: T = @splat(1);
break :blk @select(vinfo.child, i > zero, one, zero) - @select(vinfo.child, i < zero, one, zero);
},
else => @compileError("Expected vector of ints or floats, found " ++ @typeName(T)),
}
},
else => @compileError("Expected an int, float or vector of one, found " ++ @typeName(T)),
};
}
fn testSign() !void {
// each of the following blocks checks the inputs
// 2, -2, 0, { 2, -2, 0 } provide expected output
// 1, -1, 0, { 1, -1, 0 } for the given T
// (negative values omitted for unsigned types)
{
const T = i8;
try std.testing.expectEqual(@as(T, 1), sign(@as(T, 2)));
try std.testing.expectEqual(@as(T, -1), sign(@as(T, -2)));
try std.testing.expectEqual(@as(T, 0), sign(@as(T, 0)));
try std.testing.expectEqual(@Vector(3, T){ 1, -1, 0 }, sign(@Vector(3, T){ 2, -2, 0 }));
}
{
const T = i32;
try std.testing.expectEqual(@as(T, 1), sign(@as(T, 2)));
try std.testing.expectEqual(@as(T, -1), sign(@as(T, -2)));
try std.testing.expectEqual(@as(T, 0), sign(@as(T, 0)));
try std.testing.expectEqual(@Vector(3, T){ 1, -1, 0 }, sign(@Vector(3, T){ 2, -2, 0 }));
}
{
const T = i64;
try std.testing.expectEqual(@as(T, 1), sign(@as(T, 2)));
try std.testing.expectEqual(@as(T, -1), sign(@as(T, -2)));
try std.testing.expectEqual(@as(T, 0), sign(@as(T, 0)));
try std.testing.expectEqual(@Vector(3, T){ 1, -1, 0 }, sign(@Vector(3, T){ 2, -2, 0 }));
}
{
const T = u8;
try std.testing.expectEqual(@as(T, 1), sign(@as(T, 2)));
try std.testing.expectEqual(@as(T, 0), sign(@as(T, 0)));
try std.testing.expectEqual(@Vector(2, T){ 1, 0 }, sign(@Vector(2, T){ 2, 0 }));
}
{
const T = u32;
try std.testing.expectEqual(@as(T, 1), sign(@as(T, 2)));
try std.testing.expectEqual(@as(T, 0), sign(@as(T, 0)));
try std.testing.expectEqual(@Vector(2, T){ 1, 0 }, sign(@Vector(2, T){ 2, 0 }));
}
{
const T = u64;
try std.testing.expectEqual(@as(T, 1), sign(@as(T, 2)));
try std.testing.expectEqual(@as(T, 0), sign(@as(T, 0)));
try std.testing.expectEqual(@Vector(2, T){ 1, 0 }, sign(@Vector(2, T){ 2, 0 }));
}
{
const T = f16;
try std.testing.expectEqual(@as(T, 1), sign(@as(T, 2)));
try std.testing.expectEqual(@as(T, -1), sign(@as(T, -2)));
try std.testing.expectEqual(@as(T, 0), sign(@as(T, 0)));
try std.testing.expectEqual(@Vector(3, T){ 1, -1, 0 }, sign(@Vector(3, T){ 2, -2, 0 }));
}
{
const T = f32;
try std.testing.expectEqual(@as(T, 1), sign(@as(T, 2)));
try std.testing.expectEqual(@as(T, -1), sign(@as(T, -2)));
try std.testing.expectEqual(@as(T, 0), sign(@as(T, 0)));
try std.testing.expectEqual(@Vector(3, T){ 1, -1, 0 }, sign(@Vector(3, T){ 2, -2, 0 }));
}
{
const T = f64;
try std.testing.expectEqual(@as(T, 1), sign(@as(T, 2)));
try std.testing.expectEqual(@as(T, -1), sign(@as(T, -2)));
try std.testing.expectEqual(@as(T, 0), sign(@as(T, 0)));
try std.testing.expectEqual(@Vector(3, T){ 1, -1, 0 }, sign(@Vector(3, T){ 2, -2, 0 }));
}
// comptime_int
try std.testing.expectEqual(-1, sign(-10));
try std.testing.expectEqual(1, sign(10));
try std.testing.expectEqual(0, sign(0));
// comptime_float
try std.testing.expectEqual(-1.0, sign(-10.0));
try std.testing.expectEqual(1.0, sign(10.0));
try std.testing.expectEqual(0.0, sign(0.0));
}
test sign {
try testSign();
try comptime testSign();
}