| ... | ... | @@ -228,16 +228,24 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 228 | 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 | 232 | pub fn addWithOverflow(x: *Self, y: Self) u1 { |
| 233 | x.expandTo(@max(x.limbs_len, y.limbs_len)); |
| 233 | 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 | 238 | pub fn subWithOverflow(x: *Self, y: Self) u1 { |
| 239 | x.expandTo(@max(x.limbs_len, y.limbs_len)); |
| 238 | 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 | 249 | // Replaces the limbs of `x` with the limbs of `y` if `on` is `true`. |
| 242 | 250 | fn cmov(x: *Self, on: bool, y: Self) void { |
| 243 | 251 | for (x.limbs(), y.limbsConst()) |*x_limb, y_limb| { |
| ... | ... | @@ -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 |
| 249 | | // operation overflowed. |
| 256 | // Zeroed unused limbs allow `y` to be narrower than `x`. |
| 250 | 257 | fn conditionalAddWithOverflow(x: *Self, on: bool, y: Self) u1 { |
| 251 | 258 | var carry: u1 = 0; |
| 252 | | for (x.limbs(), y.limbsConst()) |*x_limb, y_limb| { |
| 253 | | const res = x_limb.* + y_limb + carry; |
| 259 | assert(y.limbs_len <= x.limbs_len); |
| 260 | for (x.limbs(), 0..) |*x_limb, i| { |
| 261 | const res = x_limb.* + y.limbs_buffer[i] + carry; |
| 254 | 262 | x_limb.* = ct.select(on, @as(TLimb, @truncate(res)), x_limb.*); |
| 255 | 263 | carry = @truncate(res >> t_bits); |
| 256 | 264 | } |
| 257 | 265 | return carry; |
| 258 | 266 | } |
| 259 | 267 | |
| 260 | | // Subtracts `y` from `x` if `on` is `true`, and returns `true` if the |
| 261 | | // operation overflowed. |
| 268 | // Zeroed unused limbs allow `y` to be narrower than `x`. |
| 262 | 269 | fn conditionalSubWithOverflow(x: *Self, on: bool, y: Self) u1 { |
| 263 | 270 | var borrow: u1 = 0; |
| 264 | | for (x.limbs(), y.limbsConst()) |*x_limb, y_limb| { |
| 265 | | const res = x_limb.* -% y_limb -% borrow; |
| 271 | assert(y.limbs_len <= x.limbs_len); |
| 272 | for (x.limbs(), 0..) |*x_limb, i| { |
| 273 | const res = x_limb.* -% y.limbs_buffer[i] -% borrow; |
| 266 | 274 | x_limb.* = ct.select(on, @as(TLimb, @truncate(res)), x_limb.*); |
| 267 | 275 | borrow = @truncate(res >> t_bits); |
| 268 | 276 | } |
| ... | ... | @@ -1210,3 +1218,22 @@ test "field element decoding" { |
| 1210 | 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 | } |