| ... | @@ -228,16 +228,24 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -228,16 +228,24 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 228 | return ct.eql((x_limbs[0] ^ 1) | orLimbs(x_limbs[1..]), 0); | 228 | return ct.eql((x_limbs[0] ^ 1) | orLimbs(x_limbs[1..]), 0); |
| 229 | } | 229 | } |
| 230 | | 230 | |
| 231 | /// Adds `y` to `x`, and returns `true` if the operation overflowed. | 231 | /// Adds `y` to `x`, wrapping at the larger active width. Returns 1 on overflow. |
| 232 | pub fn addWithOverflow(x: *Self, y: Self) u1 { | 232 | pub fn addWithOverflow(x: *Self, y: Self) u1 { |
| | 233 | x.expandTo(@max(x.limbs_len, y.limbs_len)); |
| 233 | return x.conditionalAddWithOverflow(true, y); | 234 | return x.conditionalAddWithOverflow(true, y); |
| 234 | } | 235 | } |
| 235 | | 236 | |
| 236 | /// Subtracts `y` from `x`, and returns `true` if the operation overflowed. | 237 | /// Subtracts `y` from `x`, wrapping at the larger active width. Returns 1 on underflow. |
| 237 | pub fn subWithOverflow(x: *Self, y: Self) u1 { | 238 | pub fn subWithOverflow(x: *Self, y: Self) u1 { |
| | 239 | x.expandTo(@max(x.limbs_len, y.limbs_len)); |
| 238 | return x.conditionalSubWithOverflow(true, y); | 240 | return x.conditionalSubWithOverflow(true, y); |
| 239 | } | 241 | } |
| 240 | | 242 | |
| | 243 | fn expandTo(x: *Self, new_len: usize) void { |
| | 244 | assert(new_len >= x.limbs_len and new_len <= x.limbs_buffer.len); |
| | 245 | @memset(x.limbs_buffer[x.limbs_len..new_len], 0); |
| | 246 | x.limbs_len = new_len; |
| | 247 | } |
| | 248 | |
| 241 | // Replaces the limbs of `x` with the limbs of `y` if `on` is `true`. | 249 | // Replaces the limbs of `x` with the limbs of `y` if `on` is `true`. |
| 242 | fn cmov(x: *Self, on: bool, y: Self) void { | 250 | fn cmov(x: *Self, on: bool, y: Self) void { |
| 243 | for (x.limbs(), y.limbsConst()) |*x_limb, y_limb| { | 251 | for (x.limbs(), y.limbsConst()) |*x_limb, y_limb| { |
| ... | @@ -245,24 +253,24 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -245,24 +253,24 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 245 | } | 253 | } |
| 246 | } | 254 | } |
| 247 | | 255 | |
| 248 | // Adds `y` to `x` if `on` is `true`, and returns `true` if the | 256 | // Zeroed unused limbs allow `y` to be narrower than `x`. |
| 249 | // operation overflowed. | | |
| 250 | fn conditionalAddWithOverflow(x: *Self, on: bool, y: Self) u1 { | 257 | fn conditionalAddWithOverflow(x: *Self, on: bool, y: Self) u1 { |
| 251 | var carry: u1 = 0; | 258 | var carry: u1 = 0; |
| 252 | for (x.limbs(), y.limbsConst()) |*x_limb, y_limb| { | 259 | assert(y.limbs_len <= x.limbs_len); |
| 253 | const res = x_limb.* + y_limb + carry; | 260 | for (x.limbs(), 0..) |*x_limb, i| { |
| | 261 | const res = x_limb.* + y.limbs_buffer[i] + carry; |
| 254 | x_limb.* = ct.select(on, @as(TLimb, @truncate(res)), x_limb.*); | 262 | x_limb.* = ct.select(on, @as(TLimb, @truncate(res)), x_limb.*); |
| 255 | carry = @truncate(res >> t_bits); | 263 | carry = @truncate(res >> t_bits); |
| 256 | } | 264 | } |
| 257 | return carry; | 265 | return carry; |
| 258 | } | 266 | } |
| 259 | | 267 | |
| 260 | // Subtracts `y` from `x` if `on` is `true`, and returns `true` if the | 268 | // Zeroed unused limbs allow `y` to be narrower than `x`. |
| 261 | // operation overflowed. | | |
| 262 | fn conditionalSubWithOverflow(x: *Self, on: bool, y: Self) u1 { | 269 | fn conditionalSubWithOverflow(x: *Self, on: bool, y: Self) u1 { |
| 263 | var borrow: u1 = 0; | 270 | var borrow: u1 = 0; |
| 264 | for (x.limbs(), y.limbsConst()) |*x_limb, y_limb| { | 271 | assert(y.limbs_len <= x.limbs_len); |
| 265 | const res = x_limb.* -% y_limb -% borrow; | 272 | for (x.limbs(), 0..) |*x_limb, i| { |
| | 273 | const res = x_limb.* -% y.limbs_buffer[i] -% borrow; |
| 266 | x_limb.* = ct.select(on, @as(TLimb, @truncate(res)), x_limb.*); | 274 | x_limb.* = ct.select(on, @as(TLimb, @truncate(res)), x_limb.*); |
| 267 | borrow = @truncate(res >> t_bits); | 275 | borrow = @truncate(res >> t_bits); |
| 268 | } | 276 | } |
| ... | @@ -1210,3 +1218,22 @@ test "field element decoding" { | ... | @@ -1210,3 +1218,22 @@ test "field element decoding" { |
| 1210 | try testing.expect(x.eql(try M.Fe.fromBytes(m, &buf, endian))); | 1218 | try testing.expect(x.eql(try M.Fe.fromBytes(m, &buf, endian))); |
| 1211 | } | 1219 | } |
| 1212 | } | 1220 | } |
| | 1221 | |
| | 1222 | test "Uint addition and multiply-add" { |
| | 1223 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| | 1224 | |
| | 1225 | const U = Uint(256); |
| | 1226 | const full = try U.fromBytes(&maxUintBytes(U), .big); |
| | 1227 | const one = try U.fromPrimitive(u8, 1); |
| | 1228 | var carry = (try U.fromPrimitive(u64, math.maxInt(TLimb))).normalize(); |
| | 1229 | try testing.expectEqual(0, carry.addWithOverflow(one)); |
| | 1230 | try testing.expectEqual(one.limbs_len, carry.limbs_len); |
| | 1231 | try testing.expectEqual(1 << t_bits, try carry.toPrimitive(u128)); |
| | 1232 | try testing.expectEqual(0, carry.subWithOverflow(one.normalize())); |
| | 1233 | try testing.expectEqual(math.maxInt(TLimb), try carry.toPrimitive(u64)); |
| | 1234 | var wrapped = U.zero.normalize(); |
| | 1235 | try testing.expectEqual(1, wrapped.subWithOverflow(one)); |
| | 1236 | try testing.expect(wrapped.eql(full)); |
| | 1237 | try testing.expectEqual(1, wrapped.addWithOverflow(one)); |
| | 1238 | try testing.expect(wrapped.isZero()); |
| | 1239 | } |