Attempt to improve handling for common 1/3 and 2/3 amounts

This commit is contained in:
Christopher C. Wells 2021-04-10 14:22:14 -07:00
parent 9ed27c0108
commit 232f5ddfe8
7 changed files with 42 additions and 24 deletions

View File

@ -270,7 +270,7 @@ class JournalEntryController extends Controller
} }
// Add amount, unit, and name to summary. // Add amount, unit, and name to summary.
$amount = Number::fractionStringFromFloat($amount); $amount = Number::rationalStringFromFloat($amount);
$summary = "{$amount} {$unit} {$name}"; $summary = "{$amount} {$unit} {$name}";
// Add detail if available. // Add detail if available.

View File

@ -170,7 +170,7 @@ final class Food extends Model
public function getServingSizeFormattedAttribute(): ?string { public function getServingSizeFormattedAttribute(): ?string {
$result = null; $result = null;
if (!empty($this->serving_size)) { if (!empty($this->serving_size)) {
$result = Number::fractionStringFromFloat($this->serving_size); $result = Number::rationalStringFromFloat($this->serving_size);
} }
return $result; return $result;
} }

View File

@ -92,7 +92,7 @@ final class IngredientAmount extends Model
* Get the amount as a formatted string (e.g. 0.5 = 1/2). * Get the amount as a formatted string (e.g. 0.5 = 1/2).
*/ */
public function getAmountFormattedAttribute(): string { public function getAmountFormattedAttribute(): string {
return Number::fractionStringFromFloat($this->amount); return Number::rationalStringFromFloat($this->amount);
} }
/** /**

View File

@ -7,10 +7,10 @@ use Phospr\Fraction;
class Number class Number
{ {
/** /**
* Get a float value from a decimal or fraction string. * Get a float value from a decimal or rational string.
* *
* @param string $value * @param string $value
* Value in decimal or string format. * Decimal or rational string.
* @return float * @return float
* Float representation of the value. * Float representation of the value.
* *
@ -27,21 +27,38 @@ class Number
} }
/** /**
* Get a string faction representation of a float. * Get a string rational representation of a float.
* *
* @todo Handle repeating values like 1/3, 2/3, etc. better. * Special handling is used for common cases 1/3 and 2/3 to ensure the
* * expected rationals. Other less common rationals (e.g. n/7 or n/9) will
* @see https://rosettacode.org/wiki/Convert_decimal_number_to_rational#PHP * not be well handled here.
* *
* @param float $value * @param float $value
* Value to convert to string fraction. * Value to convert to rational string.
* @return string * @return string
* String fraction. * Rational string.
*
* @todo Learn maths.
*/ */
public static function fractionStringFromFloat(float $value): string { public static function rationalStringFromFloat(float $value): string {
$fraction = (string) Fraction::fromFloat($value); $decimal = Fraction::fromFloat(($value - floor($value)));
$fraction = str_replace(['33/100', '33333333/100000000'], '1/3', $fraction); if ($decimal->isSameValueAs(Fraction::fromFloat(1/3))) {
$fraction = str_replace(['67/100', '66666667/100000000'], '2/3', $fraction); $string = '1/3';
return $fraction; $whole = floor($value);
if ($whole > 0) {
$string = "{$whole} {$string}";
}
}
elseif ($decimal->isSameValueAs(Fraction::fromFloat(2/3))) {
$string = '2/3';
$whole = floor($value);
if ($whole > 0) {
$string = "{$whole} {$string}";
}
}
else {
$string = (string) Fraction::fromFloat($value);
}
return $string;
} }
} }

View File

@ -33,10 +33,11 @@ class IngredientAmountFactory extends Factory
$ingredient_unit = 'serving'; $ingredient_unit = 'serving';
} }
$amounts = [1/8, 1/4, 1/3, 1/2, 2/3, 3/4, 1, 1 + 1/4, 1 + 1/3, 1 + 1/2, 1 + 2/3, 1 + 3/4, 2, 2 + 1/2, 3];
return [ return [
'ingredient_id' => $ingredient_factory, 'ingredient_id' => $ingredient_factory,
'ingredient_type' => $ingredient_type, 'ingredient_type' => $ingredient_type,
'amount' => $this->faker->randomElement([1/8, 1/4, 1/2, 3/4, 1, 1.25, 1.5, 1.75, 2, 2.5, 3]), 'amount' => $this->faker->randomElement($amounts),
'unit' => $ingredient_unit, 'unit' => $ingredient_unit,
'detail' => $this->faker->boolean() ? Words::randomWords('a') : null, 'detail' => $this->faker->boolean() ? Words::randomWords('a') : null,
'weight' => $this->faker->numberBetween(0, 50), 'weight' => $this->faker->numberBetween(0, 50),

View File

@ -56,7 +56,7 @@
@if($item::class === \App\Models\IngredientAmount::class) @if($item::class === \App\Models\IngredientAmount::class)
<li> <li>
<span> <span>
{{ \App\Support\Number::fractionStringFromFloat($item->amount) }} {{ \App\Support\Number::rationalStringFromFloat($item->amount) }}
@if($item->unitFormatted){{ $item->unitFormatted }}@endif @if($item->unitFormatted){{ $item->unitFormatted }}@endif
@if($item->ingredient->type === \App\Models\Recipe::class) @if($item->ingredient->type === \App\Models\Recipe::class)
<a class="text-gray-500 hover:text-gray-700 hover:border-gray-300" <a class="text-gray-500 hover:text-gray-700 hover:border-gray-300"

View File

@ -24,15 +24,15 @@ class NumberTest extends TestCase
} }
/** /**
* Test (fraction) string to float conversion. * Test (rational) string to float conversion.
* *
* @dataProvider fractionStringFloatsProvider * @dataProvider fractionStringFloatsProvider
* *
* @see \App\Support\Number::fractionStringFromFloat() * @see \App\Support\Number::rationalStringFromFloat()
*/ */
public function testFractionStringFromFloat(string $expectedString, float $float): void public function testFractionStringFromFloat(string $expectedString, float $float): void
{ {
$result = Number::fractionStringFromFloat($float); $result = Number::rationalStringFromFloat($float);
$this->assertIsString($result); $this->assertIsString($result);
$this->assertEquals($expectedString, $result); $this->assertEquals($expectedString, $result);
} }
@ -62,9 +62,9 @@ class NumberTest extends TestCase
*/ */
public function fractionStringFloatsProvider(): array { public function fractionStringFloatsProvider(): array {
return [ return [
['0', 0.0], ['1/8', 1/8], ['1/4', 1/4], ['1/2', 1/2], ['0', 0.0], ['1/8', 1/8], ['1/4', 1/4], ['1/3', 1/3], ['1/2', 1/2],
['3/4', 3/4], ['1', 1.0], ['1 1/4', 1.25], ['2/3', 2/3], ['3/4', 3/4], ['1', 1.0], ['1 1/4', 1.25], ['1 1/3', 1 + 1/3],
['1 1/2', 1.5], ['2 1/2', 2.5], ['2 3/4', 2.75], ['1 1/2', 1.5], ['1 2/3', 1 + 2/3], ['2 1/2', 2.5], ['2 3/4', 2.75],
]; ];
} }
} }