Skip to content

Commit 83e16f4

Browse files
github-actions[bot]tannergoodingtfenise
authored
[release/7.0] Simplify and fix the Int128 *, /, and % logic (#75567)
* Simplify and fix the Int128 *, /, and % logic * Apply suggestions from code review Co-authored-by: tfenise <tfenise@live.com> * Fixing some code comments Co-authored-by: Tanner Gooding <tagoo@outlook.com> Co-authored-by: tfenise <tfenise@live.com>
1 parent 9be2ce8 commit 83e16f4

File tree

3 files changed

+50
-95
lines changed

3 files changed

+50
-95
lines changed

src/libraries/System.Private.CoreLib/src/System/Int128.cs

Lines changed: 30 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,7 @@ public static Int128 Log2(Int128 value)
11561156
}
11571157

11581158
// We simplify the logic here by just doing unsigned division on the
1159-
// one's complement representation and then taking the correct sign.
1159+
// two's complement representation and then taking the correct sign.
11601160

11611161
ulong sign = (left._upper ^ right._upper) & (1UL << 63);
11621162

@@ -1172,17 +1172,13 @@ public static Int128 Log2(Int128 value)
11721172

11731173
UInt128 result = (UInt128)(left) / (UInt128)(right);
11741174

1175-
if (result == 0U)
1176-
{
1177-
sign = 0;
1178-
}
1179-
else if (sign != 0)
1175+
if (sign != 0)
11801176
{
11811177
result = ~result + 1U;
11821178
}
11831179

11841180
return new Int128(
1185-
result.Upper | sign,
1181+
result.Upper,
11861182
result.Lower
11871183
);
11881184
}
@@ -1227,36 +1223,8 @@ public static Int128 Log2(Int128 value)
12271223
/// <inheritdoc cref="IModulusOperators{TSelf, TOther, TResult}.op_Modulus(TSelf, TOther)" />
12281224
public static Int128 operator %(Int128 left, Int128 right)
12291225
{
1230-
// We simplify the logic here by just doing unsigned modulus on the
1231-
// one's complement representation and then taking the correct sign.
1232-
1233-
ulong sign = (left._upper ^ right._upper) & (1UL << 63);
1234-
1235-
if (IsNegative(left))
1236-
{
1237-
left = ~left + 1U;
1238-
}
1239-
1240-
if (IsNegative(right))
1241-
{
1242-
right = ~right + 1U;
1243-
}
1244-
1245-
UInt128 result = (UInt128)(left) % (UInt128)(right);
1246-
1247-
if (result == 0U)
1248-
{
1249-
sign = 0;
1250-
}
1251-
else if (sign != 0)
1252-
{
1253-
result = ~result + 1U;
1254-
}
1255-
1256-
return new Int128(
1257-
result.Upper | sign,
1258-
result.Lower
1259-
);
1226+
Int128 quotient = left / right;
1227+
return left - (quotient * right);
12601228
}
12611229

12621230
//
@@ -1273,76 +1241,43 @@ public static Int128 Log2(Int128 value)
12731241
/// <inheritdoc cref="IMultiplyOperators{TSelf, TOther, TResult}.op_Multiply(TSelf, TOther)" />
12741242
public static Int128 operator *(Int128 left, Int128 right)
12751243
{
1276-
// We simplify the logic here by just doing unsigned multiplication on
1277-
// the one's complement representation and then taking the correct sign.
1278-
1279-
ulong sign = (left._upper ^ right._upper) & (1UL << 63);
1280-
1281-
if (IsNegative(left))
1282-
{
1283-
left = ~left + 1U;
1284-
}
1285-
1286-
if (IsNegative(right))
1287-
{
1288-
right = ~right + 1U;
1289-
}
1290-
1291-
UInt128 result = (UInt128)(left) * (UInt128)(right);
1292-
1293-
if (result == 0U)
1294-
{
1295-
sign = 0;
1296-
}
1297-
else if (sign != 0)
1298-
{
1299-
result = ~result + 1U;
1300-
}
1301-
1302-
return new Int128(
1303-
result.Upper | sign,
1304-
result.Lower
1305-
);
1244+
// Multiplication is the same for signed and unsigned provided the "upper" bits aren't needed
1245+
return (Int128)((UInt128)(left) * (UInt128)(right));
13061246
}
13071247

13081248
/// <inheritdoc cref="IMultiplyOperators{TSelf, TOther, TResult}.op_CheckedMultiply(TSelf, TOther)" />
13091249
public static Int128 operator checked *(Int128 left, Int128 right)
13101250
{
1311-
// We simplify the logic here by just doing unsigned multiplication on
1312-
// the one's complement representation and then taking the correct sign.
1313-
1314-
ulong sign = (left._upper ^ right._upper) & (1UL << 63);
1315-
1316-
if (IsNegative(left))
1317-
{
1318-
left = ~left + 1U;
1319-
}
1251+
Int128 upper = BigMul(left, right, out Int128 lower);
13201252

1321-
if (IsNegative(right))
1253+
if (((upper != 0) || (lower < 0)) && ((~upper != 0) || (lower >= 0)))
13221254
{
1323-
right = ~right + 1U;
1324-
}
1255+
// The upper bits can safely be either Zero or AllBitsSet
1256+
// where the former represents a positive value and the
1257+
// latter a negative value.
1258+
//
1259+
// However, when the upper bits are Zero, we also need to
1260+
// confirm the lower bits are positive, otherwise we have
1261+
// a positive value greater than MaxValue and should throw
1262+
//
1263+
// Likewise, when the upper bits are AllBitsSet, we also
1264+
// need to confirm the lower bits are negative, otherwise
1265+
// we have a large negative value less than MinValue and
1266+
// should throw.
13251267

1326-
UInt128 result = checked((UInt128)(left) * (UInt128)(right));
1327-
1328-
if ((long)(result.Upper) < 0)
1329-
{
13301268
ThrowHelper.ThrowOverflowException();
13311269
}
13321270

1333-
if (result == 0U)
1334-
{
1335-
sign = 0;
1336-
}
1337-
else if (sign != 0)
1338-
{
1339-
result = ~result + 1U;
1340-
}
1271+
return lower;
1272+
}
13411273

1342-
return new Int128(
1343-
result.Upper | sign,
1344-
result.Lower
1345-
);
1274+
internal static Int128 BigMul(Int128 left, Int128 right, out Int128 lower)
1275+
{
1276+
// This follows the same logic as is used in `long Math.BigMul(long, long, out long)`
1277+
1278+
UInt128 upper = UInt128.BigMul((UInt128)(left), (UInt128)(right), out UInt128 ulower);
1279+
lower = (Int128)(ulower);
1280+
return (Int128)(upper) - ((left >> 127) & right) - ((right >> 127) & left);
13461281
}
13471282

13481283
//

src/libraries/System.Runtime/tests/System/Int128Tests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,5 +458,15 @@ public static void TestNegativeNumberParsingWithHyphen()
458458
CultureInfo ci = CultureInfo.GetCultureInfo("sv-SE");
459459
Assert.Equal(-15868, Int128.Parse("-15868", NumberStyles.Number, ci));
460460
}
461+
462+
[Fact]
463+
public static void Runtime75416()
464+
{
465+
Int128 a = (Int128.MaxValue - 10) * +100;
466+
Assert.Equal(a, -1100);
467+
468+
Int128 b = (Int128.MaxValue - 10) * -100;
469+
Assert.Equal(b, +1100);
470+
}
461471
}
462472
}

src/libraries/System.Runtime/tests/System/UInt128Tests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,5 +441,15 @@ public static void TryFormat(UInt128 i, string format, IFormatProvider provider,
441441
Assert.Equal(expected.ToLowerInvariant(), new string(actual));
442442
}
443443
}
444+
445+
[Fact]
446+
public static void Runtime75416()
447+
{
448+
UInt128 a = (UInt128Tests_GenericMath.Int128MaxValue - 10u) * +100u;
449+
Assert.Equal(a, (UInt128)(Int128)(-1100));
450+
451+
UInt128 b = (UInt128Tests_GenericMath.Int128MaxValue - 10u) * (UInt128)(Int128)(-100);
452+
Assert.Equal(b, 1100u);
453+
}
444454
}
445455
}

0 commit comments

Comments
 (0)