| author | |
| committer | |
| log | 6026bbd0adb445a0edd6defb0b378937dcce7a9b |
| tree | bfd570b423f20b1f061bc04d8ee2d8339520f6f3 |
| parent | 9be9b8ca902bb7c250a6836c6090af8525464749 |
4 files changed, 121 insertions(+), 188 deletions(-)
src/arch/wasm/CodeGen.zig+43-2| ... | ... | @@ -1841,7 +1841,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 1841 | 1841 | .bit_or => func.airBinOp(inst, .@"or"), |
| 1842 | 1842 | .bool_and => func.airBinOp(inst, .@"and"), |
| 1843 | 1843 | .bool_or => func.airBinOp(inst, .@"or"), |
| 1844 | .rem => func.airBinOp(inst, .rem), | |
| 1844 | .rem => func.airRem(inst), | |
| 1845 | 1845 | .mod => func.airMod(inst), |
| 1846 | 1846 | .shl => func.airWrapBinOp(inst, .shl), |
| 1847 | 1847 | .shl_exact => func.airBinOp(inst, .shl), |
| ... | ... | @@ -6917,13 +6917,54 @@ fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WVal |
| 6917 | 6917 | try func.emitWValue(lhs); |
| 6918 | 6918 | try func.emitWValue(rhs); |
| 6919 | 6919 | } |
| 6920 | try func.addTag(.i32_div_s); | |
| 6920 | switch (wasm_bits) { | |
| 6921 | 32 => try func.addTag(.i32_div_s), | |
| 6922 | 64 => try func.addTag(.i64_div_s), | |
| 6923 | else => unreachable, | |
| 6924 | } | |
| 6925 | _ = try func.wrapOperand(.stack, ty); | |
| 6921 | 6926 | |
| 6922 | 6927 | const result = try func.allocLocal(ty); |
| 6923 | 6928 | try func.addLabel(.local_set, result.local.value); |
| 6924 | 6929 | return result; |
| 6925 | 6930 | } |
| 6926 | 6931 | |
| 6932 | fn airRem(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | |
| 6933 | const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; | |
| 6934 | ||
| 6935 | const mod = func.bin_file.base.comp.module.?; | |
| 6936 | const ty = func.typeOfIndex(inst); | |
| 6937 | const lhs = try func.resolveInst(bin_op.lhs); | |
| 6938 | const rhs = try func.resolveInst(bin_op.rhs); | |
| 6939 | ||
| 6940 | const result = if (ty.isSignedInt(mod)) result: { | |
| 6941 | const int_bits = ty.intInfo(mod).bits; | |
| 6942 | const wasm_bits = toWasmBits(int_bits) orelse { | |
| 6943 | return func.fail("TODO: `@rem` for signed integers larger than 128 bits ({d} bits requested)", .{int_bits}); | |
| 6944 | }; | |
| 6945 | ||
| 6946 | if (wasm_bits > 64) { | |
| 6947 | return func.fail("TODO: `@rem` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits}); | |
| 6948 | } | |
| 6949 | ||
| 6950 | const lhs_wasm = if (wasm_bits != int_bits) | |
| 6951 | try (try func.signExtendInt(lhs, ty)).toLocal(func, ty) | |
| 6952 | else | |
| 6953 | lhs; | |
| 6954 | ||
| 6955 | const rhs_wasm = if (wasm_bits != int_bits) | |
| 6956 | try (try func.signExtendInt(rhs, ty)).toLocal(func, ty) | |
| 6957 | else | |
| 6958 | rhs; | |
| 6959 | ||
| 6960 | _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .rem); | |
| 6961 | break :result try func.wrapOperand(.stack, ty); | |
| 6962 | } else try func.binOp(lhs, rhs, ty, .rem); | |
| 6963 | ||
| 6964 | const return_local = try result.toLocal(func, ty); | |
| 6965 | func.finishAir(inst, return_local, &.{ bin_op.lhs, bin_op.rhs }); | |
| 6966 | } | |
| 6967 | ||
| 6927 | 6968 | /// Remainder after floor division, defined by: |
| 6928 | 6969 | /// @divFloor(a, b) * b + @mod(a, b) = a |
| 6929 | 6970 | fn airMod(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
test/behavior/int_div.zig deleted-115| ... | ... | @@ -1,115 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const expect = std.testing.expect; | |
| 4 | ||
| 5 | test "integer division" { | |
| 6 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | |
| 7 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 8 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | |
| 9 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | |
| 10 | ||
| 11 | try testDivision(); | |
| 12 | try comptime testDivision(); | |
| 13 | } | |
| 14 | fn testDivision() !void { | |
| 15 | try expect(div(u32, 13, 3) == 4); | |
| 16 | try expect(div(u64, 13, 3) == 4); | |
| 17 | try expect(div(u8, 13, 3) == 4); | |
| 18 | ||
| 19 | try expect(divExact(u32, 55, 11) == 5); | |
| 20 | try expect(divExact(i32, -55, 11) == -5); | |
| 21 | try expect(divExact(i64, -55, 11) == -5); | |
| 22 | try expect(divExact(i16, -55, 11) == -5); | |
| 23 | ||
| 24 | try expect(divFloor(i8, 5, 3) == 1); | |
| 25 | try expect(divFloor(i16, -5, 3) == -2); | |
| 26 | try expect(divFloor(i64, -0x80000000, -2) == 0x40000000); | |
| 27 | try expect(divFloor(i32, 0, -0x80000000) == 0); | |
| 28 | try expect(divFloor(i64, -0x40000001, 0x40000000) == -2); | |
| 29 | try expect(divFloor(i32, -0x80000000, 1) == -0x80000000); | |
| 30 | try expect(divFloor(i32, 10, 12) == 0); | |
| 31 | try expect(divFloor(i32, -14, 12) == -2); | |
| 32 | try expect(divFloor(i32, -2, 12) == -1); | |
| 33 | ||
| 34 | try expect(divTrunc(i32, 5, 3) == 1); | |
| 35 | try expect(divTrunc(i32, -5, 3) == -1); | |
| 36 | try expect(divTrunc(i32, 9, -10) == 0); | |
| 37 | try expect(divTrunc(i32, -9, 10) == 0); | |
| 38 | try expect(divTrunc(i32, 10, 12) == 0); | |
| 39 | try expect(divTrunc(i32, -14, 12) == -1); | |
| 40 | try expect(divTrunc(i32, -2, 12) == 0); | |
| 41 | ||
| 42 | try expect(mod(u32, 10, 12) == 10); | |
| 43 | try expect(mod(i32, 10, 12) == 10); | |
| 44 | try expect(mod(i64, -14, 12) == 10); | |
| 45 | try expect(mod(i16, -2, 12) == 10); | |
| 46 | try expect(mod(i8, -2, 12) == 10); | |
| 47 | ||
| 48 | try expect(rem(i32, 10, 12) == 10); | |
| 49 | try expect(rem(i32, -14, 12) == -2); | |
| 50 | try expect(rem(i32, -2, 12) == -2); | |
| 51 | ||
| 52 | comptime { | |
| 53 | try expect( | |
| 54 | 1194735857077236777412821811143690633098347576 % 508740759824825164163191790951174292733114988 == 177254337427586449086438229241342047632117600, | |
| 55 | ); | |
| 56 | try expect( | |
| 57 | @rem(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -177254337427586449086438229241342047632117600, | |
| 58 | ); | |
| 59 | try expect( | |
| 60 | 1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2, | |
| 61 | ); | |
| 62 | try expect( | |
| 63 | @divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2, | |
| 64 | ); | |
| 65 | try expect( | |
| 66 | @divTrunc(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2, | |
| 67 | ); | |
| 68 | try expect( | |
| 69 | @divTrunc(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2, | |
| 70 | ); | |
| 71 | try expect( | |
| 72 | 4126227191251978491697987544882340798050766755606969681711 % 10 == 1, | |
| 73 | ); | |
| 74 | } | |
| 75 | } | |
| 76 | fn div(comptime T: type, a: T, b: T) T { | |
| 77 | return a / b; | |
| 78 | } | |
| 79 | fn divExact(comptime T: type, a: T, b: T) T { | |
| 80 | return @divExact(a, b); | |
| 81 | } | |
| 82 | fn divFloor(comptime T: type, a: T, b: T) T { | |
| 83 | return @divFloor(a, b); | |
| 84 | } | |
| 85 | fn divTrunc(comptime T: type, a: T, b: T) T { | |
| 86 | return @divTrunc(a, b); | |
| 87 | } | |
| 88 | fn mod(comptime T: type, a: T, b: T) T { | |
| 89 | return @mod(a, b); | |
| 90 | } | |
| 91 | fn rem(comptime T: type, a: T, b: T) T { | |
| 92 | return @rem(a, b); | |
| 93 | } | |
| 94 | ||
| 95 | test "large integer division" { | |
| 96 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | |
| 97 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 98 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; | |
| 99 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; | |
| 100 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | |
| 101 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | |
| 102 | ||
| 103 | { | |
| 104 | var numerator: u256 = 99999999999999999997315645440; | |
| 105 | var divisor: u256 = 10000000000000000000000000000; | |
| 106 | _ = .{ &numerator, &divisor }; | |
| 107 | try expect(numerator / divisor == 9); | |
| 108 | } | |
| 109 | { | |
| 110 | var numerator: u256 = 99999999999999999999000000000000000000000; | |
| 111 | var divisor: u256 = 10000000000000000000000000000000000000000; | |
| 112 | _ = .{ &numerator, &divisor }; | |
| 113 | try expect(numerator / divisor == 9); | |
| 114 | } | |
| 115 | } |
test/behavior/math.zig+78-17| ... | ... | @@ -439,7 +439,7 @@ test "division" { |
| 439 | 439 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO |
| 440 | 440 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 441 | 441 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 442 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; | |
| 442 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO | |
| 443 | 443 | if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest; |
| 444 | 444 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; |
| 445 | 445 | |
| ... | ... | @@ -448,23 +448,25 @@ test "division" { |
| 448 | 448 | return error.SkipZigTest; |
| 449 | 449 | } |
| 450 | 450 | |
| 451 | try testDivision(); | |
| 452 | try comptime testDivision(); | |
| 451 | try testIntDivision(); | |
| 452 | try comptime testIntDivision(); | |
| 453 | ||
| 454 | try testFloatDivision(); | |
| 455 | try comptime testFloatDivision(); | |
| 453 | 456 | } |
| 454 | 457 | |
| 455 | fn testDivision() !void { | |
| 458 | fn testIntDivision() !void { | |
| 456 | 459 | try expect(div(u32, 13, 3) == 4); |
| 457 | try expect(div(f32, 1.0, 2.0) == 0.5); | |
| 460 | try expect(div(u64, 13, 3) == 4); | |
| 461 | try expect(div(u8, 13, 3) == 4); | |
| 458 | 462 | |
| 459 | 463 | try expect(divExact(u32, 55, 11) == 5); |
| 460 | 464 | try expect(divExact(i32, -55, 11) == -5); |
| 461 | try expect(divExact(f32, 55.0, 11.0) == 5.0); | |
| 462 | try expect(divExact(f32, -55.0, 11.0) == -5.0); | |
| 465 | try expect(divExact(i64, -55, 11) == -5); | |
| 466 | try expect(divExact(i16, -55, 11) == -5); | |
| 463 | 467 | |
| 464 | 468 | try expect(divFloor(i32, 5, 3) == 1); |
| 465 | 469 | try expect(divFloor(i32, -5, 3) == -2); |
| 466 | try expect(divFloor(f32, 5.0, 3.0) == 1.0); | |
| 467 | try expect(divFloor(f32, -5.0, 3.0) == -2.0); | |
| 468 | 470 | try expect(divFloor(i32, -0x80000000, -2) == 0x40000000); |
| 469 | 471 | try expect(divFloor(i32, 0, -0x80000000) == 0); |
| 470 | 472 | try expect(divFloor(i32, -0x40000001, 0x40000000) == -2); |
| ... | ... | @@ -473,18 +475,15 @@ fn testDivision() !void { |
| 473 | 475 | try expect(divFloor(i32, -14, 12) == -2); |
| 474 | 476 | try expect(divFloor(i32, -2, 12) == -1); |
| 475 | 477 | |
| 478 | try expect(divFloor(i8, 5, 3) == 1); | |
| 479 | try expect(divFloor(i16, -5, 3) == -2); | |
| 480 | try expect(divFloor(i64, -0x80000000, -2) == 0x40000000); | |
| 481 | try expect(divFloor(i64, -0x40000001, 0x40000000) == -2); | |
| 482 | ||
| 476 | 483 | try expect(divTrunc(i32, 5, 3) == 1); |
| 477 | 484 | try expect(divTrunc(i32, -5, 3) == -1); |
| 478 | 485 | try expect(divTrunc(i32, 9, -10) == 0); |
| 479 | 486 | try expect(divTrunc(i32, -9, 10) == 0); |
| 480 | try expect(divTrunc(f32, 5.0, 3.0) == 1.0); | |
| 481 | try expect(divTrunc(f32, -5.0, 3.0) == -1.0); | |
| 482 | try expect(divTrunc(f32, 9.0, -10.0) == 0.0); | |
| 483 | try expect(divTrunc(f32, -9.0, 10.0) == 0.0); | |
| 484 | try expect(divTrunc(f64, 5.0, 3.0) == 1.0); | |
| 485 | try expect(divTrunc(f64, -5.0, 3.0) == -1.0); | |
| 486 | try expect(divTrunc(f64, 9.0, -10.0) == 0.0); | |
| 487 | try expect(divTrunc(f64, -9.0, 10.0) == 0.0); | |
| 488 | 487 | try expect(divTrunc(i32, 10, 12) == 0); |
| 489 | 488 | try expect(divTrunc(i32, -14, 12) == -1); |
| 490 | 489 | try expect(divTrunc(i32, -2, 12) == 0); |
| ... | ... | @@ -496,6 +495,19 @@ fn testDivision() !void { |
| 496 | 495 | try expect(mod(i32, -14, -12) == -2); |
| 497 | 496 | try expect(mod(i32, -2, -12) == -2); |
| 498 | 497 | |
| 498 | try expect(mod(i64, -118, 12) == 2); | |
| 499 | try expect(mod(u32, 10, 12) == 10); | |
| 500 | try expect(mod(i64, -14, 12) == 10); | |
| 501 | try expect(mod(i16, -2, 12) == 10); | |
| 502 | try expect(mod(i16, -118, 12) == 2); | |
| 503 | try expect(mod(i8, -2, 12) == 10); // TODO: fails in x86_64 | |
| 504 | ||
| 505 | try expect(rem(i64, -118, 12) == -10); | |
| 506 | try expect(rem(i32, 10, 12) == 10); | |
| 507 | try expect(rem(i32, -14, 12) == -2); | |
| 508 | try expect(rem(i32, -2, 12) == -2); | |
| 509 | try expect(rem(i16, -118, 12) == -10); | |
| 510 | ||
| 499 | 511 | try expect(divTrunc(i20, 20, -5) == -4); |
| 500 | 512 | try expect(divTrunc(i20, -20, -4) == 5); |
| 501 | 513 | |
| ... | ... | @@ -524,6 +536,52 @@ fn testDivision() !void { |
| 524 | 536 | } |
| 525 | 537 | } |
| 526 | 538 | |
| 539 | fn testFloatDivision() !void { | |
| 540 | try expect(div(f32, 1.0, 2.0) == 0.5); | |
| 541 | ||
| 542 | try expect(divExact(f32, 55.0, 11.0) == 5.0); | |
| 543 | try expect(divExact(f32, -55.0, 11.0) == -5.0); | |
| 544 | ||
| 545 | try expect(divFloor(f32, 5.0, 3.0) == 1.0); | |
| 546 | try expect(divFloor(f32, -5.0, 3.0) == -2.0); | |
| 547 | try expect(divFloor(f32, 56.0, 9.0) == 6.0); | |
| 548 | try expect(divFloor(f32, 1053.0, -41.0) == -26.0); | |
| 549 | try expect(divFloor(f16, -43.0, 12.0) == -4.0); | |
| 550 | try expect(divFloor(f64, -90.0, -9.0) == 10.0); | |
| 551 | ||
| 552 | try expect(divTrunc(f32, 5.0, 3.0) == 1.0); | |
| 553 | try expect(divTrunc(f32, -5.0, 3.0) == -1.0); | |
| 554 | try expect(divTrunc(f32, 9.0, -10.0) == 0.0); | |
| 555 | try expect(divTrunc(f32, -9.0, 10.0) == 0.0); | |
| 556 | try expect(divTrunc(f64, 5.0, 3.0) == 1.0); | |
| 557 | try expect(divTrunc(f64, -5.0, 3.0) == -1.0); | |
| 558 | try expect(divTrunc(f64, 9.0, -10.0) == 0.0); | |
| 559 | try expect(divTrunc(f64, -9.0, 10.0) == 0.0); | |
| 560 | } | |
| 561 | ||
| 562 | test "large integer division" { | |
| 563 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; | |
| 564 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | |
| 565 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 566 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; | |
| 567 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; | |
| 568 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | |
| 569 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | |
| 570 | ||
| 571 | { | |
| 572 | var numerator: u256 = 99999999999999999997315645440; | |
| 573 | var divisor: u256 = 10000000000000000000000000000; | |
| 574 | _ = .{ &numerator, &divisor }; | |
| 575 | try expect(numerator / divisor == 9); | |
| 576 | } | |
| 577 | { | |
| 578 | var numerator: u256 = 99999999999999999999000000000000000000000; | |
| 579 | var divisor: u256 = 10000000000000000000000000000000000000000; | |
| 580 | _ = .{ &numerator, &divisor }; | |
| 581 | try expect(numerator / divisor == 9); | |
| 582 | } | |
| 583 | } | |
| 584 | ||
| 527 | 585 | test "division half-precision floats" { |
| 528 | 586 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO |
| 529 | 587 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| ... | ... | @@ -564,6 +622,9 @@ fn divTrunc(comptime T: type, a: T, b: T) T { |
| 564 | 622 | fn mod(comptime T: type, a: T, b: T) T { |
| 565 | 623 | return @mod(a, b); |
| 566 | 624 | } |
| 625 | fn rem(comptime T: type, a: T, b: T) T { | |
| 626 | return @rem(a, b); | |
| 627 | } | |
| 567 | 628 | |
| 568 | 629 | test "unsigned wrapping" { |
| 569 | 630 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; |
test/behavior/stage2_wasm_div.zig deleted-54| ... | ... | @@ -1,54 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const expect = std.testing.expect; | |
| 4 | ||
| 5 | test "wasm integer division" { | |
| 6 | // This test is copied from int_div.zig, with additional test cases for @divFloor on floats. | |
| 7 | // TODO: Remove this test once the division tests in math.zig and int_div.zig pass with the | |
| 8 | // stage2 wasm backend. | |
| 9 | if (builtin.zig_backend != .stage2_wasm) return error.SkipZigTest; | |
| 10 | ||
| 11 | try testDivision(); | |
| 12 | try comptime testDivision(); | |
| 13 | } | |
| 14 | fn testDivision() !void { | |
| 15 | try expect(div(u32, 13, 3) == 4); | |
| 16 | try expect(div(u64, 13, 3) == 4); | |
| 17 | try expect(div(u8, 13, 3) == 4); | |
| 18 | ||
| 19 | try expect(divFloor(i8, 5, 3) == 1); | |
| 20 | try expect(divFloor(i16, -5, 3) == -2); | |
| 21 | try expect(divFloor(i64, -0x80000000, -2) == 0x40000000); | |
| 22 | try expect(divFloor(i32, 0, -0x80000000) == 0); | |
| 23 | try expect(divFloor(i64, -0x40000001, 0x40000000) == -2); | |
| 24 | try expect(divFloor(i32, -0x80000000, 1) == -0x80000000); | |
| 25 | try expect(divFloor(i32, 10, 12) == 0); | |
| 26 | try expect(divFloor(i32, -14, 12) == -2); | |
| 27 | try expect(divFloor(i32, -2, 12) == -1); | |
| 28 | try expect(divFloor(f32, 56.0, 9.0) == 6.0); | |
| 29 | try expect(divFloor(f32, 1053.0, -41.0) == -26.0); | |
| 30 | try expect(divFloor(f16, -43.0, 12.0) == -4.0); | |
| 31 | try expect(divFloor(f64, -90.0, -9.0) == 10.0); | |
| 32 | ||
| 33 | try expect(mod(u32, 10, 12) == 10); | |
| 34 | try expect(mod(i32, 10, 12) == 10); | |
| 35 | try expect(mod(i64, -14, 12) == 10); | |
| 36 | try expect(mod(i16, -2, 12) == 10); | |
| 37 | try expect(mod(i8, -2, 12) == 10); | |
| 38 | ||
| 39 | try expect(rem(i32, 10, 12) == 10); | |
| 40 | try expect(rem(i32, -14, 12) == -2); | |
| 41 | try expect(rem(i32, -2, 12) == -2); | |
| 42 | } | |
| 43 | fn div(comptime T: type, a: T, b: T) T { | |
| 44 | return a / b; | |
| 45 | } | |
| 46 | fn divFloor(comptime T: type, a: T, b: T) T { | |
| 47 | return @divFloor(a, b); | |
| 48 | } | |
| 49 | fn mod(comptime T: type, a: T, b: T) T { | |
| 50 | return @mod(a, b); | |
| 51 | } | |
| 52 | fn rem(comptime T: type, a: T, b: T) T { | |
| 53 | return @rem(a, b); | |
| 54 | } |