Skip to content

Commit 13b78c2

Browse files
spokodevfanatid
andcommitted
fix: correct divRound rounding for negative operands (#320)
* fix: correct divRound rounding for negative operands divRound computed the half-way threshold and the round-up direction from the raw operands, which only works when both are positive. With a negative dividend or divisor it compared a signed remainder against a value derived from the signed divisor, rounded in the wrong direction, and lost the sign when the truncated quotient was zero. Compare the magnitude of the remainder against half of the absolute divisor and step the quotient away from zero using the sign of the result, so rounding matches the positive case for every sign. * fix: normalize sign when imuln multiplies by zero (#321) * fix: normalize sign when imuln multiplies by zero Multiplying a negative number by the plain number 0 zeroed the words but left the negative flag set, producing a negative zero. That value reported isNeg() as true, was not eq() to zero, compared as less than zero, and printed as -0. Clear the sign along with the length when the factor is zero so the result is a plain zero. * use _normSign() --------- Co-authored-by: Kirill Fomichev <fanatid@ya.ru> * add more tests --------- Co-authored-by: Kirill Fomichev <fanatid@ya.ru> (cherry picked from commit 73fb9a4)
1 parent 9c6ef2f commit 13b78c2

2 files changed

Lines changed: 69 additions & 5 deletions

File tree

lib/bn.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2469,17 +2469,19 @@
24692469
// Fast case - exact division
24702470
if (dm.mod.isZero()) return dm.div;
24712471

2472-
var mod = dm.div.negative !== 0 ? dm.mod.isub(num) : dm.mod;
2472+
var mod = dm.mod.abs();
24732473

2474-
var half = num.ushrn(1);
2475-
var r2 = num.andln(1);
2474+
var half = num.abs().iushrn(1);
2475+
var r2 = num.words[0] & 1;
24762476
var cmp = mod.cmp(half);
24772477

24782478
// Round down
24792479
if (cmp < 0 || r2 === 1 && cmp === 0) return dm.div;
24802480

2481-
// Round up
2482-
return dm.div.negative !== 0 ? dm.div.isubn(1) : dm.div.iaddn(1);
2481+
// Round up, away from zero
2482+
var up = new BN(1);
2483+
up.negative = this.negative ^ num.negative;
2484+
return dm.div.iadd(up);
24832485
};
24842486

24852487
BN.prototype.modn = function modn (num) {

test/arithmetic-test.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,8 +443,70 @@ describe('BN.js/Arithmetic', function () {
443443
'-8');
444444
});
445445

446+
it('should round with negative operands', function () {
447+
assert.equal(new BN(-40).divRound(new BN(-13)).toString(10), '3');
448+
assert.equal(new BN(40).divRound(new BN(-13)).toString(10), '-3');
449+
assert.equal(new BN(-40).divRound(new BN(13)).toString(10), '-3');
450+
assert.equal(new BN(-30).divRound(new BN(40)).toString(10), '-1');
451+
assert.equal(new BN(30).divRound(new BN(-40)).toString(10), '-1');
452+
assert.equal(new BN(-7).divRound(new BN(2)).toString(10), '-4');
453+
assert.equal(new BN(7).divRound(new BN(-2)).toString(10), '-4');
454+
assert.equal(new BN(-25).divRound(new BN(10)).toString(10), '-3');
455+
// odd divisor, remainder exactly floor(|divisor| / 2): round toward zero
456+
assert.equal(new BN(-4).divRound(new BN(3)).toString(10), '-1');
457+
assert.equal(new BN(4).divRound(new BN(-3)).toString(10), '-1');
458+
assert.equal(new BN(-7).divRound(new BN(5)).toString(10), '-1');
459+
assert.equal(new BN(7).divRound(new BN(-5)).toString(10), '-1');
460+
});
461+
462+
it('should round values smaller than the divisor', function () {
463+
assert.equal(new BN(4).divRound(new BN(10)).toString(10), '0');
464+
assert.equal(new BN(-4).divRound(new BN(10)).toString(10), '0');
465+
assert.equal(new BN(4).divRound(new BN(-10)).toString(10), '0');
466+
assert.equal(new BN(6).divRound(new BN(10)).toString(10), '1');
467+
assert.equal(new BN(-6).divRound(new BN(10)).toString(10), '-1');
468+
assert.equal(new BN(6).divRound(new BN(-10)).toString(10), '-1');
469+
});
470+
471+
it('should round with large multi-word operands', function () {
472+
assert.equal(
473+
new BN('123456789012345678901234567890')
474+
.divRound(new BN('98765432109876543210')).toString(10),
475+
'1249999989');
476+
assert.equal(
477+
new BN('-123456789012345678901234567890')
478+
.divRound(new BN('98765432109876543210')).toString(10),
479+
'-1249999989');
480+
assert.equal(
481+
new BN('123456789012345678901234567890')
482+
.divRound(new BN('-98765432109876543210')).toString(10),
483+
'-1249999989');
484+
assert.equal(
485+
new BN('-123456789012345678901234567890')
486+
.divRound(new BN('-98765432109876543210')).toString(10),
487+
'1249999989');
488+
});
489+
490+
it('should round large multi-word ties away from zero', function () {
491+
assert.equal(
492+
new BN('3048315750647767350192043944016156078651272672090')
493+
.divRound(new BN('246913578024691357802469135780')).toString(10),
494+
'12345678901234567891');
495+
assert.equal(
496+
new BN('-3048315750647767350192043944016156078651272672090')
497+
.divRound(new BN('246913578024691357802469135780')).toString(10),
498+
'-12345678901234567891');
499+
assert.equal(
500+
new BN('3048315750647767350192043944016156078651272672090')
501+
.divRound(new BN('-246913578024691357802469135780')).toString(10),
502+
'-12345678901234567891');
503+
});
504+
446505
it('should return 1 on exact division', function () {
447506
assert.equal(new BN(144).divRound(new BN(144)).toString(10), '1');
507+
assert.equal(new BN(-144).divRound(new BN(144)).toString(10), '-1');
508+
assert.equal(new BN(144).divRound(new BN(-144)).toString(10), '-1');
509+
assert.equal(new BN(-144).divRound(new BN(-144)).toString(10), '1');
448510
});
449511
});
450512

0 commit comments

Comments
 (0)