Skip to content

Commit 1f0e57c

Browse files
authored
Merge pull request #16 from simPod/narrow-int-arithmetic-rounding
2 parents 29ff632 + ba2de35 commit 1f0e57c

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

money/src/MoneyOperationThrowTypeExtension.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ private function narrowArithmetic(
182182
return $methodReflection->getThrowType();
183183
}
184184

185+
// For plus/minus/multipliedBy with int arg, rounding is impossible:
186+
// int arithmetic on a Money value always produces an exact result
187+
// that fits the same context (any scale, any step).
188+
$intArgSuppressesRounding = $methodName !== 'dividedBy' && SafeType::isInteger($argType);
189+
185190
// Money: build residual throw types.
186191
$residualTypes = [];
187192

@@ -195,8 +200,8 @@ private function narrowArithmetic(
195200
$residualTypes[] = new ObjectType(NumberFormatException::class);
196201
}
197202

198-
// RoundingNecessaryException when rounding mode is not safe.
199-
if (! $roundingModeIsSafe) {
203+
// RoundingNecessaryException when rounding mode is not safe and int arg doesn't suppress it.
204+
if (! $roundingModeIsSafe && ! $intArgSuppressesRounding) {
200205
$residualTypes[] = new ObjectType(RoundingNecessaryException::class);
201206
}
202207

money/src/SafeType.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ public static function isSafeRoundingMode(Type $type): bool
8080
return $unnecessaryType->isSuperTypeOf($type)->no();
8181
}
8282

83+
/**
84+
* Returns whether the given type is an integer.
85+
*
86+
* Integer arithmetic (plus, minus, multipliedBy) on Money always produces
87+
* an exact result that fits the same context, so rounding cannot occur.
88+
*/
89+
public static function isInteger(Type $type): bool
90+
{
91+
return (new IntegerType())->isSuperTypeOf($type)->yes();
92+
}
93+
8394
/**
8495
* Returns whether the given type is guaranteed to be zero.
8596
*

money/tests/data/MoneyThrowTypes.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,53 @@ public function dividedByWithUnnecessaryRounding(Money $a): void
212212
}
213213
}
214214

215+
// --- Int arithmetic (no rounding mode needed) ---
216+
217+
public function multipliedByInt(Money $a): void
218+
{
219+
try {
220+
$result = $a->multipliedBy(5);
221+
} finally {
222+
assertVariableCertainty(TrinaryLogic::createYes(), $result);
223+
}
224+
}
225+
226+
public function plusWithInt(Money $a): void
227+
{
228+
try {
229+
$result = $a->plus(10);
230+
} finally {
231+
assertVariableCertainty(TrinaryLogic::createYes(), $result);
232+
}
233+
}
234+
235+
public function minusWithInt(Money $a): void
236+
{
237+
try {
238+
$result = $a->minus(10);
239+
} finally {
240+
assertVariableCertainty(TrinaryLogic::createYes(), $result);
241+
}
242+
}
243+
244+
public function dividedByIntNoRoundingMode(Money $a): void
245+
{
246+
try {
247+
$result = $a->dividedBy(3);
248+
} finally {
249+
assertVariableCertainty(TrinaryLogic::createMaybe(), $result);
250+
}
251+
}
252+
253+
public function multipliedByString(Money $a, string $s): void
254+
{
255+
try {
256+
$result = $a->multipliedBy($s);
257+
} finally {
258+
assertVariableCertainty(TrinaryLogic::createMaybe(), $result);
259+
}
260+
}
261+
215262
// --- RationalMoney arithmetic ---
216263

217264
public function rationalPlusWithInt(RationalMoney $a): void

0 commit comments

Comments
 (0)