authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2025-06-14 14:32:11-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2025-06-15 14:15:18-04:00
log6b41beb370ce62b4381396e0543e6eb7f70b18cf
tree62ab8274799668635dc2f2fe236f081aea731eef
parent13392ad33f25649159048757d54fd6d8cf6d1887

big.int: implement float conversions

These conversion routines accept a `round` argument to control how the result is rounded and return whether the result is exact. Most callers wanted this functionality and had hacks around it being missing. Also delete `std.math.big.rational` because it was only being used for float conversion, and using rationals for that is a lot more complex than necessary. It also required an allocator, whereas the new integer routines only need to be passed enough memory to store the result.

11 files changed, 779 insertions(+), 997 deletions(-)

lib/compiler/aro/aro/Value.zig+16-26
...@@ -148,35 +148,25 @@ pub fn floatToInt(v: *Value, dest_ty: Type, comp: *Compilation) !FloatToIntChang...@@ -148,35 +148,25 @@ pub fn floatToInt(v: *Value, dest_ty: Type, comp: *Compilation) !FloatToIntChang
148 return .out_of_range;148 return .out_of_range;
149 }149 }
150150
151 const had_fraction = @rem(float_val, 1) != 0;
152 const is_negative = std.math.signbit(float_val);
153 const floored = @floor(@abs(float_val));
154
155 var rational = try std.math.big.Rational.init(comp.gpa);
156 defer rational.deinit();
157 rational.setFloat(f128, floored) catch |err| switch (err) {
158 error.NonFiniteFloat => {
159 v.* = .{};
160 return .overflow;
161 },
162 error.OutOfMemory => return error.OutOfMemory,
163 };
164
165 // The float is reduced in rational.setFloat, so we assert that denominator is equal to one
166 const big_one = BigIntConst{ .limbs = &.{1}, .positive = true };
167 assert(rational.q.toConst().eqlAbs(big_one));
168
169 if (is_negative) {
170 rational.negate();
171 }
172
173 const signedness = dest_ty.signedness(comp);151 const signedness = dest_ty.signedness(comp);
174 const bits: usize = @intCast(dest_ty.bitSizeof(comp).?);152 const bits: usize = @intCast(dest_ty.bitSizeof(comp).?);
175153
176 // rational.p.truncate(rational.p.toConst(), signedness: Signedness, bit_count: usize)154 var big_int: std.math.big.int.Mutable = .{
177 const fits = rational.p.fitsInTwosComp(signedness, bits);155 .limbs = try comp.gpa.alloc(std.math.big.Limb, @max(
178 v.* = try intern(comp, .{ .int = .{ .big_int = rational.p.toConst() } });156 std.math.big.int.calcLimbLen(float_val),
179 try rational.p.truncate(&rational.p, signedness, bits);157 std.math.big.int.calcTwosCompLimbCount(bits),
158 )),
159 .len = undefined,
160 .positive = undefined,
161 };
162 const had_fraction = switch (big_int.setFloat(float_val, .trunc)) {
163 .inexact => true,
164 .exact => false,
165 };
166
167 const fits = big_int.toConst().fitsInTwosComp(signedness, bits);
168 v.* = try intern(comp, .{ .int = .{ .big_int = big_int.toConst() } });
169 big_int.truncate(big_int.toConst(), signedness, bits);
180170
181 if (!was_zero and v.isZero(comp)) return .nonzero_to_zero;171 if (!was_zero and v.isZero(comp)) return .nonzero_to_zero;
182 if (!fits) return .out_of_range;172 if (!fits) return .out_of_range;
lib/std/math.zig+1
...@@ -45,6 +45,7 @@ pub const rad_per_deg = 0.017453292519943295769236907684886127134428718885417254...@@ -45,6 +45,7 @@ pub const rad_per_deg = 0.017453292519943295769236907684886127134428718885417254
45/// 180.0/pi45/// 180.0/pi
46pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861;46pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861;
4747
48pub const FloatRepr = float.FloatRepr;
48pub const floatExponentBits = float.floatExponentBits;49pub const floatExponentBits = float.floatExponentBits;
49pub const floatMantissaBits = float.floatMantissaBits;50pub const floatMantissaBits = float.floatMantissaBits;
50pub const floatFractionalBits = float.floatFractionalBits;51pub const floatFractionalBits = float.floatFractionalBits;
lib/std/math/big.zig-2
...@@ -1,7 +1,6 @@...@@ -1,7 +1,6 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const assert = std.debug.assert;2const assert = std.debug.assert;
33
4pub const Rational = @import("big/rational.zig").Rational;
5pub const int = @import("big/int.zig");4pub const int = @import("big/int.zig");
6pub const Limb = usize;5pub const Limb = usize;
7const limb_info = @typeInfo(Limb).int;6const limb_info = @typeInfo(Limb).int;
...@@ -18,7 +17,6 @@ comptime {...@@ -18,7 +17,6 @@ comptime {
1817
19test {18test {
20 _ = int;19 _ = int;
21 _ = Rational;
22 _ = Limb;20 _ = Limb;
23 _ = SignedLimb;21 _ = SignedLimb;
24 _ = DoubleLimb;22 _ = DoubleLimb;
lib/std/math/big/int.zig+182-46
...@@ -18,17 +18,28 @@ const Signedness = std.builtin.Signedness;...@@ -18,17 +18,28 @@ const Signedness = std.builtin.Signedness;
18const native_endian = builtin.cpu.arch.endian();18const native_endian = builtin.cpu.arch.endian();
1919
20/// Returns the number of limbs needed to store `scalar`, which must be a20/// Returns the number of limbs needed to store `scalar`, which must be a
21/// primitive integer value.21/// primitive integer or float value.
22/// Note: A comptime-known upper bound of this value that may be used22/// Note: A comptime-known upper bound of this value that may be used
23/// instead if `scalar` is not already comptime-known is23/// instead if `scalar` is not already comptime-known is
24/// `calcTwosCompLimbCount(@typeInfo(@TypeOf(scalar)).int.bits)`24/// `calcTwosCompLimbCount(@typeInfo(@TypeOf(scalar)).int.bits)`
25pub fn calcLimbLen(scalar: anytype) usize {25pub fn calcLimbLen(scalar: anytype) usize {
26 if (scalar == 0) {26 switch (@typeInfo(@TypeOf(scalar))) {
27 return 1;27 .int, .comptime_int => {
28 if (scalar == 0) return 1;
29 const w_value = @abs(scalar);
30 return @as(usize, @intCast(@divFloor(@as(Limb, @intCast(math.log2(w_value))), limb_bits) + 1));
31 },
32 .float => {
33 const repr: std.math.FloatRepr(@TypeOf(scalar)) = @bitCast(scalar);
34 return switch (repr.exponent) {
35 .denormal => 1,
36 else => return calcNonZeroTwosCompLimbCount(@as(usize, 2) + @max(repr.exponent.unbias(), 0)),
37 .infinite => 0,
38 };
39 },
40 .comptime_float => return calcLimbLen(@as(f128, scalar)),
41 else => @compileError("expected float or int, got " ++ @typeName(@TypeOf(scalar))),
28 }42 }
29
30 const w_value = @abs(scalar);
31 return @as(usize, @intCast(@divFloor(@as(Limb, @intCast(math.log2(w_value))), limb_bits) + 1));
32}43}
3344
34pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize {45pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize {
...@@ -134,6 +145,22 @@ pub const TwosCompIntLimit = enum {...@@ -134,6 +145,22 @@ pub const TwosCompIntLimit = enum {
134 max,145 max,
135};146};
136147
148pub const Round = enum {
149 /// Round to the nearest representable value, with ties broken by the representation
150 /// that ends with a 0 bit.
151 nearest_even,
152 /// Round away from zero.
153 away,
154 /// Round towards zero.
155 trunc,
156 /// Round towards negative infinity.
157 floor,
158 /// Round towards positive infinity.
159 ceil,
160};
161
162pub const Exactness = enum { inexact, exact };
163
137/// A arbitrary-precision big integer, with a fixed set of mutable limbs.164/// A arbitrary-precision big integer, with a fixed set of mutable limbs.
138pub const Mutable = struct {165pub const Mutable = struct {
139 /// Raw digits. These are:166 /// Raw digits. These are:
...@@ -155,6 +182,20 @@ pub const Mutable = struct {...@@ -155,6 +182,20 @@ pub const Mutable = struct {
155 };182 };
156 }183 }
157184
185 pub const ConvertError = Const.ConvertError;
186
187 /// Convert `self` to `Int`.
188 ///
189 /// Returns an error if self cannot be narrowed into the requested type without truncation.
190 pub fn toInt(self: Mutable, comptime Int: type) ConvertError!Int {
191 return self.toConst().toInt(Int);
192 }
193
194 /// Convert `self` to `Float`.
195 pub fn toFloat(self: Mutable, comptime Float: type, round: Round) struct { Float, Exactness } {
196 return self.toConst().toFloat(Float, round);
197 }
198
158 /// Returns true if `a == 0`.199 /// Returns true if `a == 0`.
159 pub fn eqlZero(self: Mutable) bool {200 pub fn eqlZero(self: Mutable) bool {
160 return self.toConst().eqlZero();201 return self.toConst().eqlZero();
...@@ -401,6 +442,65 @@ pub const Mutable = struct {...@@ -401,6 +442,65 @@ pub const Mutable = struct {
401 }442 }
402 }443 }
403444
445 /// Sets the Mutable to a float value rounded according to `round`.
446 /// Returns whether the conversion was exact (`round` had no effect on the result).
447 pub fn setFloat(self: *Mutable, value: anytype, round: Round) Exactness {
448 const Float = @TypeOf(value);
449 if (Float == comptime_float) return self.setFloat(@as(f128, value), round);
450 const abs_value = @abs(value);
451 if (abs_value < 1.0) {
452 if (abs_value == 0.0) {
453 self.set(0);
454 return .exact;
455 }
456 self.set(@as(i2, round: switch (round) {
457 .nearest_even => if (abs_value <= 0.5) 0 else continue :round .away,
458 .away => if (value < 0.0) -1 else 1,
459 .trunc => 0,
460 .floor => -@as(i2, @intFromBool(value < 0.0)),
461 .ceil => @intFromBool(value > 0.0),
462 }));
463 return .inexact;
464 }
465 const Repr = std.math.FloatRepr(Float);
466 const repr: Repr = @bitCast(value);
467 const exponent = repr.exponent.unbias();
468 assert(exponent >= 0);
469 const int_bit: Repr.Mantissa = 1 << (@bitSizeOf(Repr.Mantissa) - 1);
470 const mantissa = int_bit | repr.mantissa;
471 if (exponent >= @bitSizeOf(Repr.Normalized.Fraction)) {
472 self.set(mantissa);
473 self.shiftLeft(self.toConst(), @intCast(exponent - @bitSizeOf(Repr.Normalized.Fraction)));
474 self.positive = repr.sign == .positive;
475 return .exact;
476 }
477 self.set(mantissa >> @intCast(@bitSizeOf(Repr.Normalized.Fraction) - exponent));
478 const round_bits: Repr.Normalized.Fraction = @truncate(mantissa << @intCast(exponent));
479 if (round_bits == 0) {
480 self.positive = repr.sign == .positive;
481 return .exact;
482 }
483 round: switch (round) {
484 .nearest_even => {
485 const half: Repr.Normalized.Fraction = 1 << (@bitSizeOf(Repr.Normalized.Fraction) - 1);
486 if (round_bits >= half) self.addScalar(self.toConst(), 1);
487 if (round_bits == half) self.limbs[0] &= ~@as(Limb, 1);
488 },
489 .away => self.addScalar(self.toConst(), 1),
490 .trunc => {},
491 .floor => switch (repr.sign) {
492 .positive => {},
493 .negative => continue :round .away,
494 },
495 .ceil => switch (repr.sign) {
496 .positive => continue :round .away,
497 .negative => {},
498 },
499 }
500 self.positive = repr.sign == .positive;
501 return .inexact;
502 }
503
404 /// r = a + scalar504 /// r = a + scalar
405 ///505 ///
406 /// r and a may be aliases.506 /// r and a may be aliases.
...@@ -2117,25 +2217,25 @@ pub const Const = struct {...@@ -2117,25 +2217,25 @@ pub const Const = struct {
2117 /// Deprecated; use `toInt`.2217 /// Deprecated; use `toInt`.
2118 pub const to = toInt;2218 pub const to = toInt;
21192219
2120 /// Convert self to integer type T.2220 /// Convert `self` to `Int`.
2121 ///2221 ///
2122 /// Returns an error if self cannot be narrowed into the requested type without truncation.2222 /// Returns an error if self cannot be narrowed into the requested type without truncation.
2123 pub fn toInt(self: Const, comptime T: type) ConvertError!T {2223 pub fn toInt(self: Const, comptime Int: type) ConvertError!Int {
2124 switch (@typeInfo(T)) {2224 switch (@typeInfo(Int)) {
2125 .int => |info| {2225 .int => |info| {
2126 // Make sure -0 is handled correctly.2226 // Make sure -0 is handled correctly.
2127 if (self.eqlZero()) return 0;2227 if (self.eqlZero()) return 0;
21282228
2129 const UT = std.meta.Int(.unsigned, info.bits);2229 const Unsigned = std.meta.Int(.unsigned, info.bits);
21302230
2131 if (!self.fitsInTwosComp(info.signedness, info.bits)) {2231 if (!self.fitsInTwosComp(info.signedness, info.bits)) {
2132 return error.TargetTooSmall;2232 return error.TargetTooSmall;
2133 }2233 }
21342234
2135 var r: UT = 0;2235 var r: Unsigned = 0;
21362236
2137 if (@sizeOf(UT) <= @sizeOf(Limb)) {2237 if (@sizeOf(Unsigned) <= @sizeOf(Limb)) {
2138 r = @as(UT, @intCast(self.limbs[0]));2238 r = @intCast(self.limbs[0]);
2139 } else {2239 } else {
2140 for (self.limbs[0..self.limbs.len], 0..) |_, ri| {2240 for (self.limbs[0..self.limbs.len], 0..) |_, ri| {
2141 const limb = self.limbs[self.limbs.len - ri - 1];2241 const limb = self.limbs[self.limbs.len - ri - 1];
...@@ -2145,40 +2245,76 @@ pub const Const = struct {...@@ -2145,40 +2245,76 @@ pub const Const = struct {
2145 }2245 }
21462246
2147 if (info.signedness == .unsigned) {2247 if (info.signedness == .unsigned) {
2148 return if (self.positive) @as(T, @intCast(r)) else error.NegativeIntoUnsigned;2248 return if (self.positive) @intCast(r) else error.NegativeIntoUnsigned;
2149 } else {2249 } else {
2150 if (self.positive) {2250 if (self.positive) {
2151 return @intCast(r);2251 return @intCast(r);
2152 } else {2252 } else {
2153 if (math.cast(T, r)) |ok| {2253 if (math.cast(Int, r)) |ok| {
2154 return -ok;2254 return -ok;
2155 } else {2255 } else {
2156 return minInt(T);2256 return minInt(Int);
2157 }2257 }
2158 }2258 }
2159 }2259 }
2160 },2260 },
2161 else => @compileError("expected int type, found '" ++ @typeName(T) ++ "'"),2261 else => @compileError("expected int type, found '" ++ @typeName(Int) ++ "'"),
2162 }2262 }
2163 }2263 }
21642264
2165 /// Convert self to float type T.2265 /// Convert self to `Float`.
2166 pub fn toFloat(self: Const, comptime T: type) T {2266 pub fn toFloat(self: Const, comptime Float: type, round: Round) struct { Float, Exactness } {
2167 if (self.limbs.len == 0) return 0;2267 if (Float == comptime_float) return self.toFloat(f128, round);
21682268 const normalized_abs: Const = .{
2169 const base = std.math.maxInt(std.math.big.Limb) + 1;2269 .limbs = self.limbs[0..llnormalize(self.limbs)],
2170 var result: f128 = 0;2270 .positive = true,
2171 var i: usize = self.limbs.len;2271 };
2172 while (i != 0) {2272 if (normalized_abs.eqlZero()) return .{ if (self.positive) 0.0 else -0.0, .exact };
2173 i -= 1;2273
2174 const limb: f128 = @floatFromInt(self.limbs[i]);2274 const Repr = std.math.FloatRepr(Float);
2175 result = @mulAdd(f128, base, result, limb);2275 var mantissa_limbs: [calcNonZeroTwosCompLimbCount(1 + @bitSizeOf(Repr.Mantissa))]Limb = undefined;
2176 }2276 var mantissa: Mutable = .{
2177 if (self.positive) {2277 .limbs = &mantissa_limbs,
2178 return @floatCast(result);2278 .positive = undefined,
2179 } else {2279 .len = undefined,
2180 return @floatCast(-result);2280 };
2181 }2281 var exponent = normalized_abs.bitCountAbs() - 1;
2282 const exactness: Exactness = exactness: {
2283 if (exponent <= @bitSizeOf(Repr.Normalized.Fraction)) {
2284 mantissa.shiftLeft(normalized_abs, @intCast(@bitSizeOf(Repr.Normalized.Fraction) - exponent));
2285 break :exactness .exact;
2286 }
2287 const shift: usize = @intCast(exponent - @bitSizeOf(Repr.Normalized.Fraction));
2288 mantissa.shiftRight(normalized_abs, shift);
2289 const final_limb_index = (shift - 1) / limb_bits;
2290 const round_bits = normalized_abs.limbs[final_limb_index] << @truncate(-%shift) |
2291 @intFromBool(!std.mem.allEqual(Limb, normalized_abs.limbs[0..final_limb_index], 0));
2292 if (round_bits == 0) break :exactness .exact;
2293 round: switch (round) {
2294 .nearest_even => {
2295 const half: Limb = 1 << (limb_bits - 1);
2296 if (round_bits >= half) mantissa.addScalar(mantissa.toConst(), 1);
2297 if (round_bits == half) mantissa.limbs[0] &= ~@as(Limb, 1);
2298 },
2299 .away => mantissa.addScalar(mantissa.toConst(), 1),
2300 .trunc => {},
2301 .floor => if (!self.positive) continue :round .away,
2302 .ceil => if (self.positive) continue :round .away,
2303 }
2304 break :exactness .inexact;
2305 };
2306 const normalized_res: Repr.Normalized = .{
2307 .fraction = @truncate(mantissa.toInt(Repr.Mantissa) catch |err| switch (err) {
2308 error.NegativeIntoUnsigned => unreachable,
2309 error.TargetTooSmall => fraction: {
2310 assert(mantissa.toConst().orderAgainstScalar(1 << @bitSizeOf(Repr.Mantissa)).compare(.eq));
2311 exponent += 1;
2312 break :fraction 1 << (@bitSizeOf(Repr.Mantissa) - 1);
2313 },
2314 }),
2315 .exponent = std.math.lossyCast(Repr.Normalized.Exponent, exponent),
2316 };
2317 return .{ normalized_res.reconstruct(if (self.positive) .positive else .negative), exactness };
2182 }2318 }
21832319
2184 /// To allow `std.fmt.format` to work with this type.2320 /// To allow `std.fmt.format` to work with this type.
...@@ -2739,16 +2875,16 @@ pub const Managed = struct {...@@ -2739,16 +2875,16 @@ pub const Managed = struct {
2739 /// Deprecated; use `toInt`.2875 /// Deprecated; use `toInt`.
2740 pub const to = toInt;2876 pub const to = toInt;
27412877
2742 /// Convert self to integer type T.2878 /// Convert `self` to `Int`.
2743 ///2879 ///
2744 /// Returns an error if self cannot be narrowed into the requested type without truncation.2880 /// Returns an error if self cannot be narrowed into the requested type without truncation.
2745 pub fn toInt(self: Managed, comptime T: type) ConvertError!T {2881 pub fn toInt(self: Managed, comptime Int: type) ConvertError!Int {
2746 return self.toConst().toInt(T);2882 return self.toConst().toInt(Int);
2747 }2883 }
27482884
2749 /// Convert self to float type T.2885 /// Convert `self` to `Float`.
2750 pub fn toFloat(self: Managed, comptime T: type) T {2886 pub fn toFloat(self: Managed, comptime Float: type, round: Round) struct { Float, Exactness } {
2751 return self.toConst().toFloat(T);2887 return self.toConst().toFloat(Float, round);
2752 }2888 }
27532889
2754 /// Set self from the string representation `value`.2890 /// Set self from the string representation `value`.
...@@ -3807,7 +3943,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) usize {...@@ -3807,7 +3943,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) usize {
38073943
3808 // if the most significant limb becomes 0 after the shift3944 // if the most significant limb becomes 0 after the shift
3809 const shrink = a[a.len - 1] >> bit_shift == 0;3945 const shrink = a[a.len - 1] >> bit_shift == 0;
3810 std.debug.assert(r.len >= a.len - @intFromBool(!shrink));3946 std.debug.assert(r.len >= a.len - @intFromBool(shrink));
38113947
3812 var i: usize = 0;3948 var i: usize = 0;
3813 while (i < a.len - 1) : (i += 1) {3949 while (i < a.len - 1) : (i += 1) {
...@@ -4240,7 +4376,7 @@ test {...@@ -4240,7 +4376,7 @@ test {
42404376
4241const testing_allocator = std.testing.allocator;4377const testing_allocator = std.testing.allocator;
4242test "llshl shift by whole number of limb" {4378test "llshl shift by whole number of limb" {
4243 const padding = std.math.maxInt(Limb);4379 const padding = maxInt(Limb);
42444380
4245 var r: [10]Limb = @splat(padding);4381 var r: [10]Limb = @splat(padding);
42464382
...@@ -4390,8 +4526,8 @@ test "llshr to 0" {...@@ -4390,8 +4526,8 @@ test "llshr to 0" {
4390 try testOneShiftCase(.llshr, .{1, &.{0}, &.{1}});4526 try testOneShiftCase(.llshr, .{1, &.{0}, &.{1}});
4391 try testOneShiftCase(.llshr, .{5, &.{0}, &.{1}});4527 try testOneShiftCase(.llshr, .{5, &.{0}, &.{1}});
4392 try testOneShiftCase(.llshr, .{65, &.{0}, &.{0, 1}});4528 try testOneShiftCase(.llshr, .{65, &.{0}, &.{0, 1}});
4393 try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, std.math.maxInt(Limb)}});4529 try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, maxInt(Limb)}});
4394 try testOneShiftCase(.llshr, .{193, &.{0}, &.{std.math.maxInt(Limb), 1, std.math.maxInt(Limb)}});4530 try testOneShiftCase(.llshr, .{193, &.{0}, &.{maxInt(Limb), 1, maxInt(Limb)}});
4395 try testOneShiftCase(.llshr, .{193, &.{0}, &.{0xdeadbeef, 0xabcdefab, 0x1234}});4531 try testOneShiftCase(.llshr, .{193, &.{0}, &.{0xdeadbeef, 0xabcdefab, 0x1234}});
4396 // zig fmt: on4532 // zig fmt: on
4397}4533}
...@@ -4475,7 +4611,7 @@ fn testOneShiftCase(comptime function: enum { llshr, llshl }, case: Case) !void...@@ -4475,7 +4611,7 @@ fn testOneShiftCase(comptime function: enum { llshr, llshl }, case: Case) !void
4475}4611}
44764612
4477fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case) !void {4613fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case) !void {
4478 const padding = std.math.maxInt(Limb);4614 const padding = maxInt(Limb);
4479 var r: [20]Limb = @splat(padding);4615 var r: [20]Limb = @splat(padding);
44804616
4481 const shift = case[0];4617 const shift = case[0];
...@@ -4492,7 +4628,7 @@ fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case...@@ -4492,7 +4628,7 @@ fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case
4492}4628}
44934629
4494fn testOneShiftCaseAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case, shift_direction: isize) !void {4630fn testOneShiftCaseAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case, shift_direction: isize) !void {
4495 const padding = std.math.maxInt(Limb);4631 const padding = maxInt(Limb);
4496 var r: [60]Limb = @splat(padding);4632 var r: [60]Limb = @splat(padding);
4497 const base = 20;4633 const base = 20;
44984634
lib/std/math/big/int_test.zig+407
...@@ -17,6 +17,12 @@ const minInt = std.math.minInt;...@@ -17,6 +17,12 @@ const minInt = std.math.minInt;
17// They will still run on larger than this and should pass, but the multi-limb code-paths17// They will still run on larger than this and should pass, but the multi-limb code-paths
18// may be untested in some cases.18// may be untested in some cases.
1919
20fn expectNormalized(expected: comptime_int, actual: std.math.big.int.Const) !void {
21 try testing.expectEqual(expected >= 0, actual.positive);
22 try testing.expectEqual(std.math.big.int.calcLimbLen(expected), actual.limbs.len);
23 try testing.expect(actual.orderAgainstScalar(expected).compare(.eq));
24}
25
20test "comptime_int set" {26test "comptime_int set" {
21 comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab;27 comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab;
22 var a = try Managed.initSet(testing.allocator, s);28 var a = try Managed.initSet(testing.allocator, s);
...@@ -85,6 +91,407 @@ test "to target too small error" {...@@ -85,6 +91,407 @@ test "to target too small error" {
85 try testing.expectError(error.TargetTooSmall, a.toInt(u8));91 try testing.expectError(error.TargetTooSmall, a.toInt(u8));
86}92}
8793
94fn setFloat(comptime Float: type) !void {
95 var res_limbs: [std.math.big.int.calcNonZeroTwosCompLimbCount(11)]Limb = undefined;
96 var res: Mutable = .{
97 .limbs = &res_limbs,
98 .len = undefined,
99 .positive = undefined,
100 };
101
102 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .nearest_even));
103 try expectNormalized(-1 << 10, res.toConst());
104 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .away));
105 try expectNormalized(-1 << 10, res.toConst());
106 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .trunc));
107 try expectNormalized(-1 << 10, res.toConst());
108 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .floor));
109 try expectNormalized(-1 << 10, res.toConst());
110 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .ceil));
111 try expectNormalized(-1 << 10, res.toConst());
112
113 try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .nearest_even));
114 try expectNormalized(-2, res.toConst());
115 try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .away));
116 try expectNormalized(-2, res.toConst());
117 try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .trunc));
118 try expectNormalized(-2, res.toConst());
119 try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .floor));
120 try expectNormalized(-2, res.toConst());
121 try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .ceil));
122 try expectNormalized(-2, res.toConst());
123
124 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .nearest_even));
125 try expectNormalized(-2, res.toConst());
126 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .away));
127 try expectNormalized(-2, res.toConst());
128 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .trunc));
129 try expectNormalized(-1, res.toConst());
130 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .floor));
131 try expectNormalized(-2, res.toConst());
132 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .ceil));
133 try expectNormalized(-1, res.toConst());
134
135 try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .nearest_even));
136 try expectNormalized(-1, res.toConst());
137 try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .away));
138 try expectNormalized(-1, res.toConst());
139 try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .trunc));
140 try expectNormalized(-1, res.toConst());
141 try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .floor));
142 try expectNormalized(-1, res.toConst());
143 try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .ceil));
144 try expectNormalized(-1, res.toConst());
145
146 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .nearest_even));
147 try expectNormalized(-1, res.toConst());
148 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .away));
149 try expectNormalized(-1, res.toConst());
150 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .trunc));
151 try expectNormalized(0, res.toConst());
152 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .floor));
153 try expectNormalized(-1, res.toConst());
154 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .ceil));
155 try expectNormalized(0, res.toConst());
156
157 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .nearest_even));
158 try expectNormalized(0, res.toConst());
159 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .away));
160 try expectNormalized(-1, res.toConst());
161 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .trunc));
162 try expectNormalized(0, res.toConst());
163 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .floor));
164 try expectNormalized(-1, res.toConst());
165 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .ceil));
166 try expectNormalized(0, res.toConst());
167
168 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .nearest_even));
169 try expectNormalized(0, res.toConst());
170 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .away));
171 try expectNormalized(-1, res.toConst());
172 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .trunc));
173 try expectNormalized(0, res.toConst());
174 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .floor));
175 try expectNormalized(-1, res.toConst());
176 try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .ceil));
177 try expectNormalized(0, res.toConst());
178
179 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .nearest_even));
180 try expectNormalized(0, res.toConst());
181 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .away));
182 try expectNormalized(0, res.toConst());
183 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .trunc));
184 try expectNormalized(0, res.toConst());
185 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .floor));
186 try expectNormalized(0, res.toConst());
187 try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .ceil));
188 try expectNormalized(0, res.toConst());
189
190 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .nearest_even));
191 try expectNormalized(0, res.toConst());
192 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .away));
193 try expectNormalized(0, res.toConst());
194 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .trunc));
195 try expectNormalized(0, res.toConst());
196 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .floor));
197 try expectNormalized(0, res.toConst());
198 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .ceil));
199 try expectNormalized(0, res.toConst());
200
201 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .nearest_even));
202 try expectNormalized(0, res.toConst());
203 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .away));
204 try expectNormalized(1, res.toConst());
205 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .trunc));
206 try expectNormalized(0, res.toConst());
207 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .floor));
208 try expectNormalized(0, res.toConst());
209 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .ceil));
210 try expectNormalized(1, res.toConst());
211
212 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .nearest_even));
213 try expectNormalized(0, res.toConst());
214 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .away));
215 try expectNormalized(1, res.toConst());
216 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .trunc));
217 try expectNormalized(0, res.toConst());
218 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .floor));
219 try expectNormalized(0, res.toConst());
220 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .ceil));
221 try expectNormalized(1, res.toConst());
222
223 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .nearest_even));
224 try expectNormalized(1, res.toConst());
225 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .away));
226 try expectNormalized(1, res.toConst());
227 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .trunc));
228 try expectNormalized(0, res.toConst());
229 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .floor));
230 try expectNormalized(0, res.toConst());
231 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .ceil));
232 try expectNormalized(1, res.toConst());
233
234 try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .nearest_even));
235 try expectNormalized(1, res.toConst());
236 try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .away));
237 try expectNormalized(1, res.toConst());
238 try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .trunc));
239 try expectNormalized(1, res.toConst());
240 try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .floor));
241 try expectNormalized(1, res.toConst());
242 try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .ceil));
243 try expectNormalized(1, res.toConst());
244
245 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .nearest_even));
246 try expectNormalized(2, res.toConst());
247 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .away));
248 try expectNormalized(2, res.toConst());
249 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .trunc));
250 try expectNormalized(1, res.toConst());
251 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .floor));
252 try expectNormalized(1, res.toConst());
253 try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .ceil));
254 try expectNormalized(2, res.toConst());
255
256 try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .nearest_even));
257 try expectNormalized(2, res.toConst());
258 try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .away));
259 try expectNormalized(2, res.toConst());
260 try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .trunc));
261 try expectNormalized(2, res.toConst());
262 try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .floor));
263 try expectNormalized(2, res.toConst());
264 try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .ceil));
265 try expectNormalized(2, res.toConst());
266
267 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .nearest_even));
268 try expectNormalized(1 << 10, res.toConst());
269 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .away));
270 try expectNormalized(1 << 10, res.toConst());
271 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .trunc));
272 try expectNormalized(1 << 10, res.toConst());
273 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .floor));
274 try expectNormalized(1 << 10, res.toConst());
275 try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .ceil));
276 try expectNormalized(1 << 10, res.toConst());
277}
278test setFloat {
279 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
280
281 try setFloat(f16);
282 try setFloat(f32);
283 try setFloat(f64);
284 try setFloat(f80);
285 try setFloat(f128);
286 try setFloat(c_longdouble);
287 try setFloat(comptime_float);
288}
289
290fn toFloat(comptime Float: type) !void {
291 const Result = struct { Float, std.math.big.int.Exactness };
292 const fractional_bits = std.math.floatFractionalBits(Float);
293
294 var int_limbs: [
295 std.math.big.int.calcNonZeroTwosCompLimbCount(2 + fractional_bits)
296 ]Limb = undefined;
297 var int: Mutable = .{
298 .limbs = &int_limbs,
299 .len = undefined,
300 .positive = undefined,
301 };
302
303 int.set(-(1 << (fractional_bits + 1)) - 1);
304 try testing.expectEqual(
305 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
306 int.toFloat(Float, .nearest_even),
307 );
308 try testing.expectEqual(
309 Result{ comptime std.math.nextAfter(
310 Float,
311 -std.math.ldexp(@as(Float, 1), fractional_bits + 1),
312 -std.math.inf(Float),
313 ), .inexact },
314 int.toFloat(Float, .away),
315 );
316 try testing.expectEqual(
317 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
318 int.toFloat(Float, .trunc),
319 );
320 try testing.expectEqual(
321 Result{ comptime std.math.nextAfter(
322 Float,
323 -std.math.ldexp(@as(Float, 1), fractional_bits + 1),
324 -std.math.inf(Float),
325 ), .inexact },
326 int.toFloat(Float, .floor),
327 );
328 try testing.expectEqual(
329 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
330 int.toFloat(Float, .ceil),
331 );
332
333 int.set(-1 << (fractional_bits + 1));
334 try testing.expectEqual(
335 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
336 int.toFloat(Float, .nearest_even),
337 );
338 try testing.expectEqual(
339 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
340 int.toFloat(Float, .away),
341 );
342 try testing.expectEqual(
343 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
344 int.toFloat(Float, .trunc),
345 );
346 try testing.expectEqual(
347 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
348 int.toFloat(Float, .floor),
349 );
350 try testing.expectEqual(
351 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
352 int.toFloat(Float, .ceil),
353 );
354
355 int.set(-(1 << (fractional_bits + 1)) + 1);
356 try testing.expectEqual(
357 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
358 int.toFloat(Float, .nearest_even),
359 );
360 try testing.expectEqual(
361 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
362 int.toFloat(Float, .away),
363 );
364 try testing.expectEqual(
365 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
366 int.toFloat(Float, .trunc),
367 );
368 try testing.expectEqual(
369 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
370 int.toFloat(Float, .floor),
371 );
372 try testing.expectEqual(
373 Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
374 int.toFloat(Float, .ceil),
375 );
376
377 int.set(-1 << 10);
378 try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .nearest_even));
379 try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .away));
380 try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .trunc));
381 try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .floor));
382 try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .ceil));
383
384 int.set(-1);
385 try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .nearest_even));
386 try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .away));
387 try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .trunc));
388 try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .floor));
389 try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .ceil));
390
391 int.set(0);
392 try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .nearest_even));
393 try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .away));
394 try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .trunc));
395 try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .floor));
396 try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .ceil));
397
398 int.set(1);
399 try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .nearest_even));
400 try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .away));
401 try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .trunc));
402 try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .floor));
403 try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .ceil));
404
405 int.set(1 << 10);
406 try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .nearest_even));
407 try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .away));
408 try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .trunc));
409 try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .floor));
410 try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .ceil));
411
412 int.set((1 << (fractional_bits + 1)) - 1);
413 try testing.expectEqual(
414 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
415 int.toFloat(Float, .nearest_even),
416 );
417 try testing.expectEqual(
418 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
419 int.toFloat(Float, .away),
420 );
421 try testing.expectEqual(
422 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
423 int.toFloat(Float, .trunc),
424 );
425 try testing.expectEqual(
426 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
427 int.toFloat(Float, .floor),
428 );
429 try testing.expectEqual(
430 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
431 int.toFloat(Float, .ceil),
432 );
433
434 int.set(1 << (fractional_bits + 1));
435 try testing.expectEqual(
436 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
437 int.toFloat(Float, .nearest_even),
438 );
439 try testing.expectEqual(
440 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
441 int.toFloat(Float, .away),
442 );
443 try testing.expectEqual(
444 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
445 int.toFloat(Float, .trunc),
446 );
447 try testing.expectEqual(
448 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
449 int.toFloat(Float, .floor),
450 );
451 try testing.expectEqual(
452 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
453 int.toFloat(Float, .ceil),
454 );
455
456 int.set((1 << (fractional_bits + 1)) + 1);
457 try testing.expectEqual(
458 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
459 int.toFloat(Float, .nearest_even),
460 );
461 try testing.expectEqual(
462 Result{ comptime std.math.nextAfter(
463 Float,
464 std.math.ldexp(@as(Float, 1), fractional_bits + 1),
465 std.math.inf(Float),
466 ), .inexact },
467 int.toFloat(Float, .away),
468 );
469 try testing.expectEqual(
470 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
471 int.toFloat(Float, .trunc),
472 );
473 try testing.expectEqual(
474 Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
475 int.toFloat(Float, .floor),
476 );
477 try testing.expectEqual(
478 Result{ comptime std.math.nextAfter(
479 Float,
480 std.math.ldexp(@as(Float, 1), fractional_bits + 1),
481 std.math.inf(Float),
482 ), .inexact },
483 int.toFloat(Float, .ceil),
484 );
485}
486test toFloat {
487 try toFloat(f16);
488 try toFloat(f32);
489 try toFloat(f64);
490 try toFloat(f80);
491 try toFloat(f128);
492 try toFloat(c_longdouble);
493}
494
88test "normalize" {495test "normalize" {
89 var a = try Managed.init(testing.allocator);496 var a = try Managed.init(testing.allocator);
90 defer a.deinit();497 defer a.deinit();
lib/std/math/big/rational.zig deleted-820
...@@ -1,820 +0,0 @@
1const std = @import("../../std.zig");
2const builtin = @import("builtin");
3const debug = std.debug;
4const math = std.math;
5const mem = std.mem;
6const testing = std.testing;
7const Allocator = mem.Allocator;
8
9const Limb = std.math.big.Limb;
10const DoubleLimb = std.math.big.DoubleLimb;
11const Int = std.math.big.int.Managed;
12const IntConst = std.math.big.int.Const;
13
14/// An arbitrary-precision rational number.
15///
16/// Memory is allocated as needed for operations to ensure full precision is kept. The precision
17/// of a Rational is only bounded by memory.
18///
19/// Rational's are always normalized. That is, for a Rational r = p/q where p and q are integers,
20/// gcd(p, q) = 1 always.
21///
22/// TODO rework this to store its own allocator and use a non-managed big int, to avoid double
23/// allocator storage.
24pub const Rational = struct {
25 /// Numerator. Determines the sign of the Rational.
26 p: Int,
27
28 /// Denominator. Sign is ignored.
29 q: Int,
30
31 /// Create a new Rational. A small amount of memory will be allocated on initialization.
32 /// This will be 2 * Int.default_capacity.
33 pub fn init(a: Allocator) !Rational {
34 var p = try Int.init(a);
35 errdefer p.deinit();
36 return Rational{
37 .p = p,
38 .q = try Int.initSet(a, 1),
39 };
40 }
41
42 /// Frees all memory associated with a Rational.
43 pub fn deinit(self: *Rational) void {
44 self.p.deinit();
45 self.q.deinit();
46 }
47
48 /// Set a Rational from a primitive integer type.
49 pub fn setInt(self: *Rational, a: anytype) !void {
50 try self.p.set(a);
51 try self.q.set(1);
52 }
53
54 /// Set a Rational from a string of the form `A/B` where A and B are base-10 integers.
55 pub fn setFloatString(self: *Rational, str: []const u8) !void {
56 // TODO: Accept a/b fractions and exponent form
57 if (str.len == 0) {
58 return error.InvalidFloatString;
59 }
60
61 const State = enum {
62 Integer,
63 Fractional,
64 };
65
66 var state = State.Integer;
67 var point: ?usize = null;
68
69 var start: usize = 0;
70 if (str[0] == '-') {
71 start += 1;
72 }
73
74 for (str, 0..) |c, i| {
75 switch (state) {
76 State.Integer => {
77 switch (c) {
78 '.' => {
79 state = State.Fractional;
80 point = i;
81 },
82 '0'...'9' => {
83 // okay
84 },
85 else => {
86 return error.InvalidFloatString;
87 },
88 }
89 },
90 State.Fractional => {
91 switch (c) {
92 '0'...'9' => {
93 // okay
94 },
95 else => {
96 return error.InvalidFloatString;
97 },
98 }
99 },
100 }
101 }
102
103 // TODO: batch the multiplies by 10
104 if (point) |i| {
105 try self.p.setString(10, str[0..i]);
106
107 const base = IntConst{ .limbs = &[_]Limb{10}, .positive = true };
108 var local_buf: [@sizeOf(Limb) * Int.default_capacity]u8 align(@alignOf(Limb)) = undefined;
109 var fba = std.heap.FixedBufferAllocator.init(&local_buf);
110 const base_managed = try base.toManaged(fba.allocator());
111
112 var j: usize = start;
113 while (j < str.len - i - 1) : (j += 1) {
114 try self.p.ensureMulCapacity(self.p.toConst(), base);
115 try self.p.mul(&self.p, &base_managed);
116 }
117
118 try self.q.setString(10, str[i + 1 ..]);
119 try self.p.add(&self.p, &self.q);
120
121 try self.q.set(1);
122 var k: usize = i + 1;
123 while (k < str.len) : (k += 1) {
124 try self.q.mul(&self.q, &base_managed);
125 }
126
127 try self.reduce();
128 } else {
129 try self.p.setString(10, str[0..]);
130 try self.q.set(1);
131 }
132 }
133
134 /// Set a Rational from a floating-point value. The rational will have enough precision to
135 /// completely represent the provided float.
136 pub fn setFloat(self: *Rational, comptime T: type, f: T) !void {
137 // Translated from golang.go/src/math/big/rat.go.
138 debug.assert(@typeInfo(T) == .float);
139
140 const UnsignedInt = std.meta.Int(.unsigned, @typeInfo(T).float.bits);
141 const f_bits = @as(UnsignedInt, @bitCast(f));
142
143 const exponent_bits = math.floatExponentBits(T);
144 const exponent_bias = (1 << (exponent_bits - 1)) - 1;
145 const mantissa_bits = math.floatMantissaBits(T);
146
147 const exponent_mask = (1 << exponent_bits) - 1;
148 const mantissa_mask = (1 << mantissa_bits) - 1;
149
150 var exponent = @as(i16, @intCast((f_bits >> mantissa_bits) & exponent_mask));
151 var mantissa = f_bits & mantissa_mask;
152
153 switch (exponent) {
154 exponent_mask => {
155 return error.NonFiniteFloat;
156 },
157 0 => {
158 // denormal
159 exponent -= exponent_bias - 1;
160 },
161 else => {
162 // normal
163 mantissa |= 1 << mantissa_bits;
164 exponent -= exponent_bias;
165 },
166 }
167
168 var shift: i16 = mantissa_bits - exponent;
169
170 // factor out powers of two early from rational
171 while (mantissa & 1 == 0 and shift > 0) {
172 mantissa >>= 1;
173 shift -= 1;
174 }
175
176 try self.p.set(mantissa);
177 self.p.setSign(f >= 0);
178
179 try self.q.set(1);
180 if (shift >= 0) {
181 try self.q.shiftLeft(&self.q, @as(usize, @intCast(shift)));
182 } else {
183 try self.p.shiftLeft(&self.p, @as(usize, @intCast(-shift)));
184 }
185
186 try self.reduce();
187 }
188
189 /// Return a floating-point value that is the closest value to a Rational.
190 ///
191 /// The result may not be exact if the Rational is too precise or too large for the
192 /// target type.
193 pub fn toFloat(self: Rational, comptime T: type) !T {
194 // Translated from golang.go/src/math/big/rat.go.
195 // TODO: Indicate whether the result is not exact.
196 debug.assert(@typeInfo(T) == .float);
197
198 const fsize = @typeInfo(T).float.bits;
199 const BitReprType = std.meta.Int(.unsigned, fsize);
200
201 const msize = math.floatMantissaBits(T);
202 const msize1 = msize + 1;
203 const msize2 = msize1 + 1;
204
205 const esize = math.floatExponentBits(T);
206 const ebias = (1 << (esize - 1)) - 1;
207 const emin = 1 - ebias;
208
209 if (self.p.eqlZero()) {
210 return 0;
211 }
212
213 // 1. left-shift a or sub so that a/b is in [1 << msize1, 1 << (msize2 + 1)]
214 var exp = @as(isize, @intCast(self.p.bitCountTwosComp())) - @as(isize, @intCast(self.q.bitCountTwosComp()));
215
216 var a2 = try self.p.clone();
217 defer a2.deinit();
218
219 var b2 = try self.q.clone();
220 defer b2.deinit();
221
222 const shift = msize2 - exp;
223 if (shift >= 0) {
224 try a2.shiftLeft(&a2, @as(usize, @intCast(shift)));
225 } else {
226 try b2.shiftLeft(&b2, @as(usize, @intCast(-shift)));
227 }
228
229 // 2. compute quotient and remainder
230 var q = try Int.init(self.p.allocator);
231 defer q.deinit();
232
233 // unused
234 var r = try Int.init(self.p.allocator);
235 defer r.deinit();
236
237 try Int.divTrunc(&q, &r, &a2, &b2);
238
239 var mantissa = extractLowBits(q, BitReprType);
240 var have_rem = r.len() > 0;
241
242 // 3. q didn't fit in msize2 bits, redo division b2 << 1
243 if (mantissa >> msize2 == 1) {
244 if (mantissa & 1 == 1) {
245 have_rem = true;
246 }
247 mantissa >>= 1;
248 exp += 1;
249 }
250 if (mantissa >> msize1 != 1) {
251 // NOTE: This can be hit if the limb size is small (u8/16).
252 @panic("unexpected bits in result");
253 }
254
255 // 4. Rounding
256 if (emin - msize <= exp and exp <= emin) {
257 // denormal
258 const shift1 = @as(math.Log2Int(BitReprType), @intCast(emin - (exp - 1)));
259 const lost_bits = mantissa & ((@as(BitReprType, @intCast(1)) << shift1) - 1);
260 have_rem = have_rem or lost_bits != 0;
261 mantissa >>= shift1;
262 exp = 2 - ebias;
263 }
264
265 // round q using round-half-to-even
266 var exact = !have_rem;
267 if (mantissa & 1 != 0) {
268 exact = false;
269 if (have_rem or (mantissa & 2 != 0)) {
270 mantissa += 1;
271 if (mantissa >= 1 << msize2) {
272 // 11...1 => 100...0
273 mantissa >>= 1;
274 exp += 1;
275 }
276 }
277 }
278 mantissa >>= 1;
279
280 const f = math.scalbn(@as(T, @floatFromInt(mantissa)), @as(i32, @intCast(exp - msize1)));
281 if (math.isInf(f)) {
282 exact = false;
283 }
284
285 return if (self.p.isPositive()) f else -f;
286 }
287
288 /// Set a rational from an integer ratio.
289 pub fn setRatio(self: *Rational, p: anytype, q: anytype) !void {
290 try self.p.set(p);
291 try self.q.set(q);
292
293 self.p.setSign(@intFromBool(self.p.isPositive()) ^ @intFromBool(self.q.isPositive()) == 0);
294 self.q.setSign(true);
295
296 try self.reduce();
297
298 if (self.q.eqlZero()) {
299 @panic("cannot set rational with denominator = 0");
300 }
301 }
302
303 /// Set a Rational directly from an Int.
304 pub fn copyInt(self: *Rational, a: Int) !void {
305 try self.p.copy(a.toConst());
306 try self.q.set(1);
307 }
308
309 /// Set a Rational directly from a ratio of two Int's.
310 pub fn copyRatio(self: *Rational, a: Int, b: Int) !void {
311 try self.p.copy(a.toConst());
312 try self.q.copy(b.toConst());
313
314 self.p.setSign(@intFromBool(self.p.isPositive()) ^ @intFromBool(self.q.isPositive()) == 0);
315 self.q.setSign(true);
316
317 try self.reduce();
318 }
319
320 /// Make a Rational positive.
321 pub fn abs(r: *Rational) void {
322 r.p.abs();
323 }
324
325 /// Negate the sign of a Rational.
326 pub fn negate(r: *Rational) void {
327 r.p.negate();
328 }
329
330 /// Efficiently swap a Rational with another. This swaps the limb pointers and a full copy is not
331 /// performed. The address of the limbs field will not be the same after this function.
332 pub fn swap(r: *Rational, other: *Rational) void {
333 r.p.swap(&other.p);
334 r.q.swap(&other.q);
335 }
336
337 /// Returns math.Order.lt, math.Order.eq, math.Order.gt if a < b, a == b or
338 /// a > b respectively.
339 pub fn order(a: Rational, b: Rational) !math.Order {
340 return cmpInternal(a, b, false);
341 }
342
343 /// Returns math.Order.lt, math.Order.eq, math.Order.gt if |a| < |b|, |a| ==
344 /// |b| or |a| > |b| respectively.
345 pub fn orderAbs(a: Rational, b: Rational) !math.Order {
346 return cmpInternal(a, b, true);
347 }
348
349 // p/q > x/y iff p*y > x*q
350 fn cmpInternal(a: Rational, b: Rational, is_abs: bool) !math.Order {
351 // TODO: Would a div compare algorithm of sorts be viable and quicker? Can we avoid
352 // the memory allocations here?
353 var q = try Int.init(a.p.allocator);
354 defer q.deinit();
355
356 var p = try Int.init(b.p.allocator);
357 defer p.deinit();
358
359 try q.mul(&a.p, &b.q);
360 try p.mul(&b.p, &a.q);
361
362 return if (is_abs) q.orderAbs(p) else q.order(p);
363 }
364
365 /// rma = a + b.
366 ///
367 /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
368 ///
369 /// Returns an error if memory could not be allocated.
370 pub fn add(rma: *Rational, a: Rational, b: Rational) !void {
371 var r = rma;
372 var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr;
373
374 var sr: Rational = undefined;
375 if (aliased) {
376 sr = try Rational.init(rma.p.allocator);
377 r = &sr;
378 aliased = true;
379 }
380 defer if (aliased) {
381 rma.swap(r);
382 r.deinit();
383 };
384
385 try r.p.mul(&a.p, &b.q);
386 try r.q.mul(&b.p, &a.q);
387 try r.p.add(&r.p, &r.q);
388
389 try r.q.mul(&a.q, &b.q);
390 try r.reduce();
391 }
392
393 /// rma = a - b.
394 ///
395 /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
396 ///
397 /// Returns an error if memory could not be allocated.
398 pub fn sub(rma: *Rational, a: Rational, b: Rational) !void {
399 var r = rma;
400 var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr;
401
402 var sr: Rational = undefined;
403 if (aliased) {
404 sr = try Rational.init(rma.p.allocator);
405 r = &sr;
406 aliased = true;
407 }
408 defer if (aliased) {
409 rma.swap(r);
410 r.deinit();
411 };
412
413 try r.p.mul(&a.p, &b.q);
414 try r.q.mul(&b.p, &a.q);
415 try r.p.sub(&r.p, &r.q);
416
417 try r.q.mul(&a.q, &b.q);
418 try r.reduce();
419 }
420
421 /// rma = a * b.
422 ///
423 /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
424 ///
425 /// Returns an error if memory could not be allocated.
426 pub fn mul(r: *Rational, a: Rational, b: Rational) !void {
427 try r.p.mul(&a.p, &b.p);
428 try r.q.mul(&a.q, &b.q);
429 try r.reduce();
430 }
431
432 /// rma = a / b.
433 ///
434 /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
435 ///
436 /// Returns an error if memory could not be allocated.
437 pub fn div(r: *Rational, a: Rational, b: Rational) !void {
438 if (b.p.eqlZero()) {
439 @panic("division by zero");
440 }
441
442 try r.p.mul(&a.p, &b.q);
443 try r.q.mul(&b.p, &a.q);
444 try r.reduce();
445 }
446
447 /// Invert the numerator and denominator fields of a Rational. p/q => q/p.
448 pub fn invert(r: *Rational) void {
449 Int.swap(&r.p, &r.q);
450 }
451
452 // reduce r/q such that gcd(r, q) = 1
453 fn reduce(r: *Rational) !void {
454 var a = try Int.init(r.p.allocator);
455 defer a.deinit();
456
457 const sign = r.p.isPositive();
458 r.p.abs();
459 try a.gcd(&r.p, &r.q);
460 r.p.setSign(sign);
461
462 const one = IntConst{ .limbs = &[_]Limb{1}, .positive = true };
463 if (a.toConst().order(one) != .eq) {
464 var unused = try Int.init(r.p.allocator);
465 defer unused.deinit();
466
467 // TODO: divexact would be useful here
468 // TODO: don't copy r.q for div
469 try Int.divTrunc(&r.p, &unused, &r.p, &a);
470 try Int.divTrunc(&r.q, &unused, &r.q, &a);
471 }
472 }
473};
474
475fn extractLowBits(a: Int, comptime T: type) T {
476 debug.assert(@typeInfo(T) == .int);
477
478 const t_bits = @typeInfo(T).int.bits;
479 const limb_bits = @typeInfo(Limb).int.bits;
480 if (t_bits <= limb_bits) {
481 return @as(T, @truncate(a.limbs[0]));
482 } else {
483 var r: T = 0;
484 comptime var i: usize = 0;
485
486 // Remainder is always 0 since if t_bits >= limb_bits -> Limb | T and both
487 // are powers of two.
488 inline while (i < t_bits / limb_bits) : (i += 1) {
489 r |= math.shl(T, a.limbs[i], i * limb_bits);
490 }
491
492 return r;
493 }
494}
495
496test extractLowBits {
497 var a = try Int.initSet(testing.allocator, 0x11112222333344441234567887654321);
498 defer a.deinit();
499
500 const a1 = extractLowBits(a, u8);
501 try testing.expect(a1 == 0x21);
502
503 const a2 = extractLowBits(a, u16);
504 try testing.expect(a2 == 0x4321);
505
506 const a3 = extractLowBits(a, u32);
507 try testing.expect(a3 == 0x87654321);
508
509 const a4 = extractLowBits(a, u64);
510 try testing.expect(a4 == 0x1234567887654321);
511
512 const a5 = extractLowBits(a, u128);
513 try testing.expect(a5 == 0x11112222333344441234567887654321);
514}
515
516test "set" {
517 var a = try Rational.init(testing.allocator);
518 defer a.deinit();
519
520 try a.setInt(5);
521 try testing.expect((try a.p.toInt(u32)) == 5);
522 try testing.expect((try a.q.toInt(u32)) == 1);
523
524 try a.setRatio(7, 3);
525 try testing.expect((try a.p.toInt(u32)) == 7);
526 try testing.expect((try a.q.toInt(u32)) == 3);
527
528 try a.setRatio(9, 3);
529 try testing.expect((try a.p.toInt(i32)) == 3);
530 try testing.expect((try a.q.toInt(i32)) == 1);
531
532 try a.setRatio(-9, 3);
533 try testing.expect((try a.p.toInt(i32)) == -3);
534 try testing.expect((try a.q.toInt(i32)) == 1);
535
536 try a.setRatio(9, -3);
537 try testing.expect((try a.p.toInt(i32)) == -3);
538 try testing.expect((try a.q.toInt(i32)) == 1);
539
540 try a.setRatio(-9, -3);
541 try testing.expect((try a.p.toInt(i32)) == 3);
542 try testing.expect((try a.q.toInt(i32)) == 1);
543}
544
545test "setFloat" {
546 var a = try Rational.init(testing.allocator);
547 defer a.deinit();
548
549 try a.setFloat(f64, 2.5);
550 try testing.expect((try a.p.toInt(i32)) == 5);
551 try testing.expect((try a.q.toInt(i32)) == 2);
552
553 try a.setFloat(f32, -2.5);
554 try testing.expect((try a.p.toInt(i32)) == -5);
555 try testing.expect((try a.q.toInt(i32)) == 2);
556
557 try a.setFloat(f32, 3.141593);
558
559 // = 3.14159297943115234375
560 try testing.expect((try a.p.toInt(u32)) == 3294199);
561 try testing.expect((try a.q.toInt(u32)) == 1048576);
562
563 try a.setFloat(f64, 72.141593120712409172417410926841290461290467124);
564
565 // = 72.1415931207124145885245525278151035308837890625
566 try testing.expect((try a.p.toInt(u128)) == 5076513310880537);
567 try testing.expect((try a.q.toInt(u128)) == 70368744177664);
568}
569
570test "setFloatString" {
571 var a = try Rational.init(testing.allocator);
572 defer a.deinit();
573
574 try a.setFloatString("72.14159312071241458852455252781510353");
575
576 // = 72.1415931207124145885245525278151035308837890625
577 try testing.expect((try a.p.toInt(u128)) == 7214159312071241458852455252781510353);
578 try testing.expect((try a.q.toInt(u128)) == 100000000000000000000000000000000000);
579}
580
581test "toFloat" {
582 var a = try Rational.init(testing.allocator);
583 defer a.deinit();
584
585 // = 3.14159297943115234375
586 try a.setRatio(3294199, 1048576);
587 try testing.expect((try a.toFloat(f64)) == 3.14159297943115234375);
588
589 // = 72.1415931207124145885245525278151035308837890625
590 try a.setRatio(5076513310880537, 70368744177664);
591 try testing.expect((try a.toFloat(f64)) == 72.141593120712409172417410926841290461290467124);
592}
593
594test "set/to Float round-trip" {
595 var a = try Rational.init(testing.allocator);
596 defer a.deinit();
597 var prng = std.Random.DefaultPrng.init(std.testing.random_seed);
598 const random = prng.random();
599 var i: usize = 0;
600 while (i < 512) : (i += 1) {
601 const r = random.float(f64);
602 try a.setFloat(f64, r);
603 try testing.expect((try a.toFloat(f64)) == r);
604 }
605}
606
607test "copy" {
608 var a = try Rational.init(testing.allocator);
609 defer a.deinit();
610
611 var b = try Int.initSet(testing.allocator, 5);
612 defer b.deinit();
613
614 try a.copyInt(b);
615 try testing.expect((try a.p.toInt(u32)) == 5);
616 try testing.expect((try a.q.toInt(u32)) == 1);
617
618 var c = try Int.initSet(testing.allocator, 7);
619 defer c.deinit();
620 var d = try Int.initSet(testing.allocator, 3);
621 defer d.deinit();
622
623 try a.copyRatio(c, d);
624 try testing.expect((try a.p.toInt(u32)) == 7);
625 try testing.expect((try a.q.toInt(u32)) == 3);
626
627 var e = try Int.initSet(testing.allocator, 9);
628 defer e.deinit();
629 var f = try Int.initSet(testing.allocator, 3);
630 defer f.deinit();
631
632 try a.copyRatio(e, f);
633 try testing.expect((try a.p.toInt(u32)) == 3);
634 try testing.expect((try a.q.toInt(u32)) == 1);
635}
636
637test "negate" {
638 var a = try Rational.init(testing.allocator);
639 defer a.deinit();
640
641 try a.setInt(-50);
642 try testing.expect((try a.p.toInt(i32)) == -50);
643 try testing.expect((try a.q.toInt(i32)) == 1);
644
645 a.negate();
646 try testing.expect((try a.p.toInt(i32)) == 50);
647 try testing.expect((try a.q.toInt(i32)) == 1);
648
649 a.negate();
650 try testing.expect((try a.p.toInt(i32)) == -50);
651 try testing.expect((try a.q.toInt(i32)) == 1);
652}
653
654test "abs" {
655 var a = try Rational.init(testing.allocator);
656 defer a.deinit();
657
658 try a.setInt(-50);
659 try testing.expect((try a.p.toInt(i32)) == -50);
660 try testing.expect((try a.q.toInt(i32)) == 1);
661
662 a.abs();
663 try testing.expect((try a.p.toInt(i32)) == 50);
664 try testing.expect((try a.q.toInt(i32)) == 1);
665
666 a.abs();
667 try testing.expect((try a.p.toInt(i32)) == 50);
668 try testing.expect((try a.q.toInt(i32)) == 1);
669}
670
671test "swap" {
672 var a = try Rational.init(testing.allocator);
673 defer a.deinit();
674 var b = try Rational.init(testing.allocator);
675 defer b.deinit();
676
677 try a.setRatio(50, 23);
678 try b.setRatio(17, 3);
679
680 try testing.expect((try a.p.toInt(u32)) == 50);
681 try testing.expect((try a.q.toInt(u32)) == 23);
682
683 try testing.expect((try b.p.toInt(u32)) == 17);
684 try testing.expect((try b.q.toInt(u32)) == 3);
685
686 a.swap(&b);
687
688 try testing.expect((try a.p.toInt(u32)) == 17);
689 try testing.expect((try a.q.toInt(u32)) == 3);
690
691 try testing.expect((try b.p.toInt(u32)) == 50);
692 try testing.expect((try b.q.toInt(u32)) == 23);
693}
694
695test "order" {
696 var a = try Rational.init(testing.allocator);
697 defer a.deinit();
698 var b = try Rational.init(testing.allocator);
699 defer b.deinit();
700
701 try a.setRatio(500, 231);
702 try b.setRatio(18903, 8584);
703 try testing.expect((try a.order(b)) == .lt);
704
705 try a.setRatio(890, 10);
706 try b.setRatio(89, 1);
707 try testing.expect((try a.order(b)) == .eq);
708}
709
710test "order/orderAbs with negative" {
711 var a = try Rational.init(testing.allocator);
712 defer a.deinit();
713 var b = try Rational.init(testing.allocator);
714 defer b.deinit();
715
716 try a.setRatio(1, 1);
717 try b.setRatio(-2, 1);
718 try testing.expect((try a.order(b)) == .gt);
719 try testing.expect((try a.orderAbs(b)) == .lt);
720}
721
722test "add single-limb" {
723 var a = try Rational.init(testing.allocator);
724 defer a.deinit();
725 var b = try Rational.init(testing.allocator);
726 defer b.deinit();
727
728 try a.setRatio(500, 231);
729 try b.setRatio(18903, 8584);
730 try testing.expect((try a.order(b)) == .lt);
731
732 try a.setRatio(890, 10);
733 try b.setRatio(89, 1);
734 try testing.expect((try a.order(b)) == .eq);
735}
736
737test "add" {
738 var a = try Rational.init(testing.allocator);
739 defer a.deinit();
740 var b = try Rational.init(testing.allocator);
741 defer b.deinit();
742 var r = try Rational.init(testing.allocator);
743 defer r.deinit();
744
745 try a.setRatio(78923, 23341);
746 try b.setRatio(123097, 12441414);
747 try a.add(a, b);
748
749 try r.setRatio(984786924199, 290395044174);
750 try testing.expect((try a.order(r)) == .eq);
751}
752
753test "sub" {
754 var a = try Rational.init(testing.allocator);
755 defer a.deinit();
756 var b = try Rational.init(testing.allocator);
757 defer b.deinit();
758 var r = try Rational.init(testing.allocator);
759 defer r.deinit();
760
761 try a.setRatio(78923, 23341);
762 try b.setRatio(123097, 12441414);
763 try a.sub(a, b);
764
765 try r.setRatio(979040510045, 290395044174);
766 try testing.expect((try a.order(r)) == .eq);
767}
768
769test "mul" {
770 var a = try Rational.init(testing.allocator);
771 defer a.deinit();
772 var b = try Rational.init(testing.allocator);
773 defer b.deinit();
774 var r = try Rational.init(testing.allocator);
775 defer r.deinit();
776
777 try a.setRatio(78923, 23341);
778 try b.setRatio(123097, 12441414);
779 try a.mul(a, b);
780
781 try r.setRatio(571481443, 17082061422);
782 try testing.expect((try a.order(r)) == .eq);
783}
784
785test "div" {
786 {
787 var a = try Rational.init(testing.allocator);
788 defer a.deinit();
789 var b = try Rational.init(testing.allocator);
790 defer b.deinit();
791 var r = try Rational.init(testing.allocator);
792 defer r.deinit();
793
794 try a.setRatio(78923, 23341);
795 try b.setRatio(123097, 12441414);
796 try a.div(a, b);
797
798 try r.setRatio(75531824394, 221015929);
799 try testing.expect((try a.order(r)) == .eq);
800 }
801
802 {
803 var a = try Rational.init(testing.allocator);
804 defer a.deinit();
805 var r = try Rational.init(testing.allocator);
806 defer r.deinit();
807
808 try a.setRatio(78923, 23341);
809 a.invert();
810
811 try r.setRatio(23341, 78923);
812 try testing.expect((try a.order(r)) == .eq);
813
814 try a.setRatio(-78923, 23341);
815 a.invert();
816
817 try r.setRatio(-23341, 78923);
818 try testing.expect((try a.order(r)) == .eq);
819 }
820}
lib/std/math/float.zig+113
...@@ -4,6 +4,119 @@ const assert = std.debug.assert;...@@ -4,6 +4,119 @@ const assert = std.debug.assert;
4const expect = std.testing.expect;4const expect = std.testing.expect;
5const expectEqual = std.testing.expectEqual;5const expectEqual = std.testing.expectEqual;
66
7pub const Sign = enum(u1) { positive, negative };
8
9pub fn FloatRepr(comptime Float: type) type {
10 const fractional_bits = floatFractionalBits(Float);
11 const exponent_bits = floatExponentBits(Float);
12 return packed struct {
13 const Repr = @This();
14
15 mantissa: StoredMantissa,
16 exponent: BiasedExponent,
17 sign: Sign,
18
19 pub const StoredMantissa = @Type(.{ .int = .{
20 .signedness = .unsigned,
21 .bits = floatMantissaBits(Float),
22 } });
23 pub const Mantissa = @Type(.{ .int = .{
24 .signedness = .unsigned,
25 .bits = 1 + fractional_bits,
26 } });
27 pub const Exponent = @Type(.{ .int = .{
28 .signedness = .signed,
29 .bits = exponent_bits,
30 } });
31 pub const BiasedExponent = enum(@Type(.{ .int = .{
32 .signedness = .unsigned,
33 .bits = exponent_bits,
34 } })) {
35 denormal = 0,
36 min_normal = 1,
37 zero = (1 << (exponent_bits - 1)) - 1,
38 max_normal = (1 << exponent_bits) - 2,
39 infinite = (1 << exponent_bits) - 1,
40 _,
41
42 pub const Int = @typeInfo(BiasedExponent).@"enum".tag_type;
43
44 pub fn unbias(biased: BiasedExponent) Exponent {
45 switch (biased) {
46 .denormal => unreachable,
47 else => return @bitCast(@intFromEnum(biased) -% @intFromEnum(BiasedExponent.zero)),
48 .infinite => unreachable,
49 }
50 }
51
52 pub fn bias(unbiased: Exponent) BiasedExponent {
53 return @enumFromInt(@intFromEnum(BiasedExponent.zero) +% @as(Int, @bitCast(unbiased)));
54 }
55 };
56
57 pub const Normalized = struct {
58 fraction: Fraction,
59 exponent: Normalized.Exponent,
60
61 pub const Fraction = @Type(.{ .int = .{
62 .signedness = .unsigned,
63 .bits = fractional_bits,
64 } });
65 pub const Exponent = @Type(.{ .int = .{
66 .signedness = .signed,
67 .bits = 1 + exponent_bits,
68 } });
69
70 /// This currently truncates denormal values, which needs to be fixed before this can be used to
71 /// produce a rounded value.
72 pub fn reconstruct(normalized: Normalized, sign: Sign) Float {
73 if (normalized.exponent > BiasedExponent.max_normal.unbias()) return @bitCast(Repr{
74 .mantissa = 0,
75 .exponent = .infinite,
76 .sign = sign,
77 });
78 const mantissa = @as(Mantissa, 1 << fractional_bits) | normalized.fraction;
79 if (normalized.exponent < BiasedExponent.min_normal.unbias()) return @bitCast(Repr{
80 .mantissa = @truncate(std.math.shr(
81 Mantissa,
82 mantissa,
83 BiasedExponent.min_normal.unbias() - normalized.exponent,
84 )),
85 .exponent = .denormal,
86 .sign = sign,
87 });
88 return @bitCast(Repr{
89 .mantissa = @truncate(mantissa),
90 .exponent = .bias(@intCast(normalized.exponent)),
91 .sign = sign,
92 });
93 }
94 };
95
96 pub const Classified = union(enum) { normalized: Normalized, infinity, nan, invalid };
97 fn classify(repr: Repr) Classified {
98 return switch (repr.exponent) {
99 .denormal => {
100 const mantissa: Mantissa = repr.mantissa;
101 const shift = @clz(mantissa);
102 return .{ .normalized = .{
103 .fraction = @truncate(mantissa << shift),
104 .exponent = @as(Normalized.Exponent, comptime BiasedExponent.min_normal.unbias()) - shift,
105 } };
106 },
107 else => if (repr.mantissa <= std.math.maxInt(Normalized.Fraction)) .{ .normalized = .{
108 .fraction = @intCast(repr.mantissa),
109 .exponent = repr.exponent.unbias(),
110 } } else .invalid,
111 .infinite => switch (repr.mantissa) {
112 0 => .infinity,
113 else => .nan,
114 },
115 };
116 }
117 };
118}
119
7/// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic.120/// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic.
8inline fn mantissaOne(comptime T: type) comptime_int {121inline fn mantissaOne(comptime T: type) comptime_int {
9 return if (@typeInfo(T).float.bits == 80) 1 << floatFractionalBits(T) else 0;122 return if (@typeInfo(T).float.bits == 80) 1 << floatFractionalBits(T) else 0;
lib/std/zon/parse.zig+1-1
...@@ -593,7 +593,7 @@ const Parser = struct {...@@ -593,7 +593,7 @@ const Parser = struct {
593 switch (node.get(self.zoir)) {593 switch (node.get(self.zoir)) {
594 .int_literal => |int| switch (int) {594 .int_literal => |int| switch (int) {
595 .small => |val| return @floatFromInt(val),595 .small => |val| return @floatFromInt(val),
596 .big => |val| return val.toFloat(T),596 .big => |val| return val.toFloat(T, .nearest_even)[0],
597 },597 },
598 .float_literal => |val| return @floatCast(val),598 .float_literal => |val| return @floatCast(val),
599 .pos_inf => return std.math.inf(T),599 .pos_inf => return std.math.inf(T),
src/Sema.zig+42-64
...@@ -32843,24 +32843,21 @@ fn cmpNumeric(...@@ -32843,24 +32843,21 @@ fn cmpNumeric(
32843 }32843 }
32844 }32844 }
32845 if (lhs_is_float) {32845 if (lhs_is_float) {
32846 if (lhs_val.floatHasFraction(zcu)) {32846 const float = lhs_val.toFloat(f128, zcu);
32847 switch (op) {32847 var big_int: std.math.big.int.Mutable = .{
32848 .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
32849 .len = undefined,
32850 .positive = undefined,
32851 };
32852 switch (big_int.setFloat(float, .away)) {
32853 .inexact => switch (op) {
32848 .eq => return .bool_false,32854 .eq => return .bool_false,
32849 .neq => return .bool_true,32855 .neq => return .bool_true,
32850 else => {},32856 else => {},
32851 }32857 },
32852 }32858 .exact => {},
32853
32854 var bigint = try float128IntPartToBigInt(sema.gpa, lhs_val.toFloat(f128, zcu));
32855 defer bigint.deinit();
32856 if (lhs_val.floatHasFraction(zcu)) {
32857 if (lhs_is_signed) {
32858 try bigint.addScalar(&bigint, -1);
32859 } else {
32860 try bigint.addScalar(&bigint, 1);
32861 }
32862 }32859 }
32863 lhs_bits = bigint.toConst().bitCountTwosComp();32860 lhs_bits = big_int.toConst().bitCountTwosComp();
32864 } else {32861 } else {
32865 lhs_bits = lhs_val.intBitCountTwosComp(zcu);32862 lhs_bits = lhs_val.intBitCountTwosComp(zcu);
32866 }32863 }
...@@ -32890,24 +32887,21 @@ fn cmpNumeric(...@@ -32890,24 +32887,21 @@ fn cmpNumeric(
32890 }32887 }
32891 }32888 }
32892 if (rhs_is_float) {32889 if (rhs_is_float) {
32893 if (rhs_val.floatHasFraction(zcu)) {32890 const float = rhs_val.toFloat(f128, zcu);
32894 switch (op) {32891 var big_int: std.math.big.int.Mutable = .{
32892 .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
32893 .len = undefined,
32894 .positive = undefined,
32895 };
32896 switch (big_int.setFloat(float, .away)) {
32897 .inexact => switch (op) {
32895 .eq => return .bool_false,32898 .eq => return .bool_false,
32896 .neq => return .bool_true,32899 .neq => return .bool_true,
32897 else => {},32900 else => {},
32898 }32901 },
32899 }32902 .exact => {},
32900
32901 var bigint = try float128IntPartToBigInt(sema.gpa, rhs_val.toFloat(f128, zcu));
32902 defer bigint.deinit();
32903 if (rhs_val.floatHasFraction(zcu)) {
32904 if (rhs_is_signed) {
32905 try bigint.addScalar(&bigint, -1);
32906 } else {
32907 try bigint.addScalar(&bigint, 1);
32908 }
32909 }32903 }
32910 rhs_bits = bigint.toConst().bitCountTwosComp();32904 rhs_bits = big_int.toConst().bitCountTwosComp();
32911 } else {32905 } else {
32912 rhs_bits = rhs_val.intBitCountTwosComp(zcu);32906 rhs_bits = rhs_val.intBitCountTwosComp(zcu);
32913 }32907 }
...@@ -36955,31 +36949,6 @@ fn intFromFloat(...@@ -36955,31 +36949,6 @@ fn intFromFloat(
36955 return sema.intFromFloatScalar(block, src, val, int_ty, mode);36949 return sema.intFromFloatScalar(block, src, val, int_ty, mode);
36956}36950}
3695736951
36958// float is expected to be finite and non-NaN
36959fn float128IntPartToBigInt(
36960 arena: Allocator,
36961 float: f128,
36962) !std.math.big.int.Managed {
36963 const is_negative = std.math.signbit(float);
36964 const floored = @floor(@abs(float));
36965
36966 var rational = try std.math.big.Rational.init(arena);
36967 defer rational.q.deinit();
36968 rational.setFloat(f128, floored) catch |err| switch (err) {
36969 error.NonFiniteFloat => unreachable,
36970 error.OutOfMemory => return error.OutOfMemory,
36971 };
36972
36973 // The float is reduced in rational.setFloat, so we assert that denominator is equal to one
36974 const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true };
36975 assert(rational.q.toConst().eqlAbs(big_one));
36976
36977 if (is_negative) {
36978 rational.negate();
36979 }
36980 return rational.p;
36981}
36982
36983fn intFromFloatScalar(36952fn intFromFloatScalar(
36984 sema: *Sema,36953 sema: *Sema,
36985 block: *Block,36954 block: *Block,
...@@ -36993,13 +36962,6 @@ fn intFromFloatScalar(...@@ -36993,13 +36962,6 @@ fn intFromFloatScalar(
3699336962
36994 if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src);36963 if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src);
3699536964
36996 if (mode == .exact and val.floatHasFraction(zcu)) return sema.fail(
36997 block,
36998 src,
36999 "fractional component prevents float value '{}' from coercion to type '{}'",
37000 .{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },
37001 );
37002
37003 const float = val.toFloat(f128, zcu);36965 const float = val.toFloat(f128, zcu);
37004 if (std.math.isNan(float)) {36966 if (std.math.isNan(float)) {
37005 return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{36967 return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{
...@@ -37012,12 +36974,28 @@ fn intFromFloatScalar(...@@ -37012,12 +36974,28 @@ fn intFromFloatScalar(
37012 });36974 });
37013 }36975 }
3701436976
37015 var big_int = try float128IntPartToBigInt(sema.arena, float);36977 var big_int: std.math.big.int.Mutable = .{
37016 defer big_int.deinit();36978 .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
3701736979 .len = undefined,
36980 .positive = undefined,
36981 };
36982 switch (big_int.setFloat(float, .trunc)) {
36983 .inexact => switch (mode) {
36984 .exact => return sema.fail(
36985 block,
36986 src,
36987 "fractional component prevents float value '{}' from coercion to type '{}'",
36988 .{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },
36989 ),
36990 .truncate => {},
36991 },
36992 .exact => {},
36993 }
37018 const cti_result = try pt.intValue_big(.comptime_int, big_int.toConst());36994 const cti_result = try pt.intValue_big(.comptime_int, big_int.toConst());
36995 if (int_ty.toIntern() == .comptime_int_type) return cti_result;
3701936996
37020 if (!(try sema.intFitsInType(cti_result, int_ty, null))) {36997 const int_info = int_ty.intInfo(zcu);
36998 if (!big_int.toConst().fitsInTwosComp(int_info.signedness, int_info.bits)) {
37021 return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{36999 return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{
37022 val.fmtValueSema(pt, sema), int_ty.fmt(pt),37000 val.fmtValueSema(pt, sema), int_ty.fmt(pt),
37023 });37001 });
src/Sema/LowerZon.zig+12-19
...@@ -509,30 +509,23 @@ fn lowerInt(...@@ -509,30 +509,23 @@ fn lowerInt(
509 },509 },
510 },510 },
511 .float_literal => |val| {511 .float_literal => |val| {
512 // Check for fractional components512 var big_int: std.math.big.int.Mutable = .{
513 if (@rem(val, 1) != 0) {513 .limbs = try self.sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(val)),
514 return self.fail(514 .len = undefined,
515 .positive = undefined,
516 };
517 switch (big_int.setFloat(val, .trunc)) {
518 .inexact => return self.fail(
515 node,519 node,
516 "fractional component prevents float value '{}' from coercion to type '{}'",520 "fractional component prevents float value '{}' from coercion to type '{}'",
517 .{ val, res_ty.fmt(self.sema.pt) },521 .{ val, res_ty.fmt(self.sema.pt) },
518 );522 ),
523 .exact => {},
519 }524 }
520525
521 // Create a rational representation of the float
522 var rational = try std.math.big.Rational.init(self.sema.arena);
523 rational.setFloat(f128, val) catch |err| switch (err) {
524 error.NonFiniteFloat => unreachable,
525 error.OutOfMemory => return error.OutOfMemory,
526 };
527
528 // The float is reduced in rational.setFloat, so we assert that denominator is equal to
529 // one
530 const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true };
531 assert(rational.q.toConst().eqlAbs(big_one));
532
533 // Check that the result is in range of the result type526 // Check that the result is in range of the result type
534 const int_info = res_ty.intInfo(self.sema.pt.zcu);527 const int_info = res_ty.intInfo(self.sema.pt.zcu);
535 if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) {528 if (!big_int.toConst().fitsInTwosComp(int_info.signedness, int_info.bits)) {
536 return self.fail(529 return self.fail(
537 node,530 node,
538 "type '{}' cannot represent integer value '{}'",531 "type '{}' cannot represent integer value '{}'",
...@@ -543,7 +536,7 @@ fn lowerInt(...@@ -543,7 +536,7 @@ fn lowerInt(
543 return self.sema.pt.intern(.{536 return self.sema.pt.intern(.{
544 .int = .{537 .int = .{
545 .ty = res_ty.toIntern(),538 .ty = res_ty.toIntern(),
546 .storage = .{ .big_int = rational.p.toConst() },539 .storage = .{ .big_int = big_int.toConst() },
547 },540 },
548 });541 });
549 },542 },
...@@ -584,7 +577,7 @@ fn lowerFloat(...@@ -584,7 +577,7 @@ fn lowerFloat(
584 const value = switch (node.get(self.file.zoir.?)) {577 const value = switch (node.get(self.file.zoir.?)) {
585 .int_literal => |int| switch (int) {578 .int_literal => |int| switch (int) {
586 .small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),579 .small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
587 .big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128)),580 .big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128, .nearest_even)[0]),
588 },581 },
589 .float_literal => |val| try self.sema.pt.floatValue(res_ty, val),582 .float_literal => |val| try self.sema.pt.floatValue(res_ty, val),
590 .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),583 .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
src/Value.zig+5-19
...@@ -898,7 +898,7 @@ pub fn readFromPackedMemory(...@@ -898,7 +898,7 @@ pub fn readFromPackedMemory(
898pub fn toFloat(val: Value, comptime T: type, zcu: *const Zcu) T {898pub fn toFloat(val: Value, comptime T: type, zcu: *const Zcu) T {
899 return switch (zcu.intern_pool.indexToKey(val.toIntern())) {899 return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
900 .int => |int| switch (int.storage) {900 .int => |int| switch (int.storage) {
901 .big_int => |big_int| big_int.toFloat(T),901 .big_int => |big_int| big_int.toFloat(T, .nearest_even)[0],
902 inline .u64, .i64 => |x| {902 inline .u64, .i64 => |x| {
903 if (T == f80) {903 if (T == f80) {
904 @panic("TODO we can't lower this properly on non-x86 llvm backend yet");904 @panic("TODO we can't lower this properly on non-x86 llvm backend yet");
...@@ -997,16 +997,6 @@ pub fn floatCast(val: Value, dest_ty: Type, pt: Zcu.PerThread) !Value {...@@ -997,16 +997,6 @@ pub fn floatCast(val: Value, dest_ty: Type, pt: Zcu.PerThread) !Value {
997 } }));997 } }));
998}998}
999999
1000/// Asserts the value is a float
1001pub fn floatHasFraction(self: Value, zcu: *const Zcu) bool {
1002 return switch (zcu.intern_pool.indexToKey(self.toIntern())) {
1003 .float => |float| switch (float.storage) {
1004 inline else => |x| @rem(x, 1) != 0,
1005 },
1006 else => unreachable,
1007 };
1008}
1009
1010pub fn orderAgainstZero(lhs: Value, zcu: *Zcu) std.math.Order {1000pub fn orderAgainstZero(lhs: Value, zcu: *Zcu) std.math.Order {
1011 return orderAgainstZeroInner(lhs, .normal, zcu, {}) catch unreachable;1001 return orderAgainstZeroInner(lhs, .normal, zcu, {}) catch unreachable;
1012}1002}
...@@ -1557,17 +1547,13 @@ pub fn floatFromIntAdvanced(...@@ -1557,17 +1547,13 @@ pub fn floatFromIntAdvanced(
1557}1547}
15581548
1559pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptime strat: ResolveStrat) !Value {1549pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptime strat: ResolveStrat) !Value {
1560 const zcu = pt.zcu;1550 return switch (pt.zcu.intern_pool.indexToKey(val.toIntern())) {
1561 return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
1562 .undef => try pt.undefValue(float_ty),1551 .undef => try pt.undefValue(float_ty),
1563 .int => |int| switch (int.storage) {1552 .int => |int| switch (int.storage) {
1564 .big_int => |big_int| {1553 .big_int => |big_int| pt.floatValue(float_ty, big_int.toFloat(f128, .nearest_even)[0]),
1565 const float = big_int.toFloat(f128);
1566 return pt.floatValue(float_ty, float);
1567 },
1568 inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt),1554 inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt),
1569 .lazy_align => |ty| return floatFromIntInner((try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), pt.zcu, pt.tid)).scalar.toByteUnits() orelse 0, float_ty, pt),1555 .lazy_align => |ty| floatFromIntInner((try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), pt.zcu, pt.tid)).scalar.toByteUnits() orelse 0, float_ty, pt),
1570 .lazy_size => |ty| return floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, float_ty, pt),1556 .lazy_size => |ty| floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, float_ty, pt),
1571 },1557 },
1572 else => unreachable,1558 else => unreachable,
1573 };1559 };