mirror of https://github.com/kcal-app/kcal.git
Attempt to improve handling for common 1/3 and 2/3 amounts
This commit is contained in:
parent
9ed27c0108
commit
232f5ddfe8
|
|
@ -270,7 +270,7 @@ class JournalEntryController extends Controller
|
|||
}
|
||||
|
||||
// Add amount, unit, and name to summary.
|
||||
$amount = Number::fractionStringFromFloat($amount);
|
||||
$amount = Number::rationalStringFromFloat($amount);
|
||||
$summary = "{$amount} {$unit} {$name}";
|
||||
|
||||
// Add detail if available.
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ final class Food extends Model
|
|||
public function getServingSizeFormattedAttribute(): ?string {
|
||||
$result = null;
|
||||
if (!empty($this->serving_size)) {
|
||||
$result = Number::fractionStringFromFloat($this->serving_size);
|
||||
$result = Number::rationalStringFromFloat($this->serving_size);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ final class IngredientAmount extends Model
|
|||
* Get the amount as a formatted string (e.g. 0.5 = 1/2).
|
||||
*/
|
||||
public function getAmountFormattedAttribute(): string {
|
||||
return Number::fractionStringFromFloat($this->amount);
|
||||
return Number::rationalStringFromFloat($this->amount);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ use Phospr\Fraction;
|
|||
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
|
||||
* Value in decimal or string format.
|
||||
* Decimal or rational string.
|
||||
* @return float
|
||||
* 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.
|
||||
*
|
||||
* @see https://rosettacode.org/wiki/Convert_decimal_number_to_rational#PHP
|
||||
* 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
|
||||
* not be well handled here.
|
||||
*
|
||||
* @param float $value
|
||||
* Value to convert to string fraction.
|
||||
* Value to convert to rational string.
|
||||
* @return string
|
||||
* String fraction.
|
||||
* Rational string.
|
||||
*
|
||||
* @todo Learn maths.
|
||||
*/
|
||||
public static function fractionStringFromFloat(float $value): string {
|
||||
$fraction = (string) Fraction::fromFloat($value);
|
||||
$fraction = str_replace(['33/100', '33333333/100000000'], '1/3', $fraction);
|
||||
$fraction = str_replace(['67/100', '66666667/100000000'], '2/3', $fraction);
|
||||
return $fraction;
|
||||
public static function rationalStringFromFloat(float $value): string {
|
||||
$decimal = Fraction::fromFloat(($value - floor($value)));
|
||||
if ($decimal->isSameValueAs(Fraction::fromFloat(1/3))) {
|
||||
$string = '1/3';
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,11 @@ class IngredientAmountFactory extends Factory
|
|||
$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 [
|
||||
'ingredient_id' => $ingredient_factory,
|
||||
'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,
|
||||
'detail' => $this->faker->boolean() ? Words::randomWords('a') : null,
|
||||
'weight' => $this->faker->numberBetween(0, 50),
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
@if($item::class === \App\Models\IngredientAmount::class)
|
||||
<li>
|
||||
<span>
|
||||
{{ \App\Support\Number::fractionStringFromFloat($item->amount) }}
|
||||
{{ \App\Support\Number::rationalStringFromFloat($item->amount) }}
|
||||
@if($item->unitFormatted){{ $item->unitFormatted }}@endif
|
||||
@if($item->ingredient->type === \App\Models\Recipe::class)
|
||||
<a class="text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
|
|
|
|||
|
|
@ -24,15 +24,15 @@ class NumberTest extends TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* Test (fraction) string to float conversion.
|
||||
* Test (rational) string to float conversion.
|
||||
*
|
||||
* @dataProvider fractionStringFloatsProvider
|
||||
*
|
||||
* @see \App\Support\Number::fractionStringFromFloat()
|
||||
* @see \App\Support\Number::rationalStringFromFloat()
|
||||
*/
|
||||
public function testFractionStringFromFloat(string $expectedString, float $float): void
|
||||
{
|
||||
$result = Number::fractionStringFromFloat($float);
|
||||
$result = Number::rationalStringFromFloat($float);
|
||||
$this->assertIsString($result);
|
||||
$this->assertEquals($expectedString, $result);
|
||||
}
|
||||
|
|
@ -62,9 +62,9 @@ class NumberTest extends TestCase
|
|||
*/
|
||||
public function fractionStringFloatsProvider(): array {
|
||||
return [
|
||||
['0', 0.0], ['1/8', 1/8], ['1/4', 1/4], ['1/2', 1/2],
|
||||
['3/4', 3/4], ['1', 1.0], ['1 1/4', 1.25],
|
||||
['1 1/2', 1.5], ['2 1/2', 2.5], ['2 3/4', 2.75],
|
||||
['0', 0.0], ['1/8', 1/8], ['1/4', 1/4], ['1/3', 1/3], ['1/2', 1/2],
|
||||
['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], ['1 2/3', 1 + 2/3], ['2 1/2', 2.5], ['2 3/4', 2.75],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue