fractions: Option to render mixed fractions

Add `MixedFractions` option to render `3/2` as `1 1/2`.
The opposite setting is `ImproperFractions`.

In addition, fractions can be rendered as small digits,
e.g. `¹²/₄₃`. This is controlled by the `SmallFractions` and
`BigFractions` settings.

The menus were updated accordingly.

Fixes #554

Signed-off-by: Christophe de Dinechin <christophe@dinechin.org>
This commit is contained in:
Christophe de Dinechin 2023-11-10 23:01:46 +01:00
parent 13ecc55866
commit 97b5917069
10 changed files with 241 additions and 56 deletions

View file

@ -89,6 +89,22 @@ Display the exponent in scientific mode using a fancy rendering that is visually
Display the exponent in scientific mode in a way reminiscent of classical HP48 calculators, for example `1.23E-4`.
## MixedFractions
Display fractions as mixed fractions when necessary, e.g. `3/2` will show up as `1 1/2`.
## ImproperFractions
Display fractions as improper fractions, e.g. `3/2` will show up as `3/2` and not `1 1/2`.
## SmallFractions
Show fractions using smaller characters, for example `¹²/₄₃`
## BigFractions
Show fractions using regular characters, for example `12/43`
# Angle settings
The angle mode determines how the calculator interprets angle arguments and how

View file

@ -4995,6 +4995,22 @@ Display the exponent in scientific mode using a fancy rendering that is visually
Display the exponent in scientific mode in a way reminiscent of classical HP48 calculators, for example `1.23E-4`.
## MixedFractions
Display fractions as mixed fractions when necessary, e.g. `3/2` will show up as `1 1/2`.
## ImproperFractions
Display fractions as improper fractions, e.g. `3/2` will show up as `3/2` and not `1 1/2`.
## SmallFractions
Show fractions using smaller characters, for example `¹²/₄₃`
## BigFractions
Show fractions using regular characters, for example `12/43`
# Angle settings
The angle mode determines how the calculator interprets angle arguments and how

View file

@ -4995,6 +4995,22 @@ Display the exponent in scientific mode using a fancy rendering that is visually
Display the exponent in scientific mode in a way reminiscent of classical HP48 calculators, for example `1.23E-4`.
## MixedFractions
Display fractions as mixed fractions when necessary, e.g. `3/2` will show up as `1 1/2`.
## ImproperFractions
Display fractions as improper fractions, e.g. `3/2` will show up as `3/2` and not `1 1/2`.
## SmallFractions
Show fractions using smaller characters, for example `¹²/₄₃`
## BigFractions
Show fractions using regular characters, for example `12/43`
# Angle settings
The angle mode determines how the calculator interprets angle arguments and how

View file

@ -141,6 +141,24 @@ static size_t render_num(renderer &r,
return result;
}
// Upper / lower rendering
bool upper = *fmt == '^';
bool lower = *fmt == 'v';
if (upper || lower)
fmt++;
if (!Settings.small_fractions || r.editing())
upper = lower = false;
static uint16_t fancy_upper_digits[10] =
{
L'', L'¹', L'²', L'³', L'',
L'', L'', L'', L'', L''
};
static uint16_t fancy_lower_digits[10] =
{
L'', L'', L'', L'', L'',
L'', L'', L'', L'', L''
};
// Check which kind of spacing to use
bool based = *fmt == '#';
bool fancy_base = based && r.stack();
@ -178,7 +196,10 @@ static size_t render_num(renderer &r,
printf("Ooops: digit=%u, base=%u\n", digit, base);
bignum::quorem(n, b, bignum::ID_bignum, &quotient, &remainder);
}
char c = (digit < 10) ? digit + '0' : digit + ('A' - 10);
unicode c = upper ? fancy_upper_digits[digit]
: lower ? fancy_lower_digits[digit]
: (digit < 10) ? digit + '0'
: digit + ('A' - 10);
r.put(c);
n = quotient;
@ -191,20 +212,15 @@ static size_t render_num(renderer &r,
// Revert the digits
byte *dest = (byte *) r.text();
bool multibyte = spacing && space > 0xFF;
bool multibyte = upper || lower || (spacing && space > 0xFF);
utf8_reverse(dest + findex, dest + r.size(), multibyte);
// Add suffix if there is one
if (fancy_base)
{
static uint16_t fancy_base_digits[10] =
{
L'', L'', L'', L'', L'',
L'', L'', L'', L'', L''
};
if (base / 10)
r.put(unicode(fancy_base_digits[base/10]));
r.put(unicode(fancy_base_digits[base%10]));
r.put(unicode(fancy_lower_digits[base/10]));
r.put(unicode(fancy_lower_digits[base%10]));
}
else if (*fmt)
r.put(*fmt++);
@ -731,17 +747,40 @@ bignum_g bignum::pow(bignum_r yr, bignum_r xr)
}
static size_t fraction_render(big_fraction_p o, renderer &r, bool negative)
// ----------------------------------------------------------------------------
// Common code for positive and negative fractions
// ----------------------------------------------------------------------------
{
bignum_g n = o->numerator();
bignum_g d = o->denominator();
if (r.stack() && Settings.mixed_fractions)
{
bignum_g quo, rem;
if (bignum::quorem(n, d, bignum::ID_bignum, &quo, &rem))
{
if (!quo->is_zero())
{
render_num(r, quo, 10, "");
r.put(unicode(settings::SPACE_MEDIUM_MATH));
n = rem;
}
}
}
render_num(r, n, 10, negative ? "^-/" : "^");
if (!negative)
r.put('/');
render_num(r, d, 10, "v");
return r.size();
}
RENDER_BODY(big_fraction)
// ----------------------------------------------------------------------------
// Render the fraction as 'num/den'
// ----------------------------------------------------------------------------
{
bignum_g n = o->numerator();
bignum_g d = o->denominator();
render_num(r, n, 10, "");
r.put('/');
render_num(r, d, 10, "");
return r.size();
return fraction_render(o, r, false);
}
@ -750,9 +789,5 @@ RENDER_BODY(neg_big_fraction)
// Render the fraction as '-num/den'
// ----------------------------------------------------------------------------
{
bignum_g n = o->numerator();
bignum_g d = o->denominator();
render_num(r, n, 10, "-/");
render_num(r, d, 10, "");
return r.size();
return fraction_render(o, r, true);
}

View file

@ -463,6 +463,10 @@ CMD(StandardExponent)
CMD(MinimumSignificantDigits)
CMD(FancyExponent)
CMD(ClassicExponent)
CMD(MixedFractions)
CMD(ImproperFractions)
CMD(SmallFractions)
CMD(BigFractions)
CMD(Base)
NAMED(Bin, "Binary")

View file

@ -428,6 +428,24 @@ static size_t render_num(renderer &r,
return result;
}
// Upper / lower rendering
bool upper = *fmt == '^';
bool lower = *fmt == 'v';
if (upper || lower)
fmt++;
if (!Settings.small_fractions || r.editing())
upper = lower = false;
static uint16_t fancy_upper_digits[10] =
{
L'', L'¹', L'²', L'³', L'',
L'', L'', L'', L'', L''
};
static uint16_t fancy_lower_digits[10] =
{
L'', L'', L'', L'', L'',
L'', L'', L'', L'', L''
};
// Check which kind of spacing to use
bool based = *fmt == '#';
bool fancy_base = based && r.stack();
@ -457,7 +475,10 @@ static size_t render_num(renderer &r,
{
ularge digit = n % base;
n /= base;
char c = (digit < 10) ? digit + '0' : digit + ('A' - 10);
unicode c = upper ? fancy_upper_digits[digit]
: lower ? fancy_lower_digits[digit]
: (digit < 10) ? digit + '0'
: digit + ('A' - 10);
r.put(c);
if (n && ++sep == spacing)
@ -469,20 +490,15 @@ static size_t render_num(renderer &r,
// Revert the digits
byte *dest = (byte *) r.text();
bool multibyte = spacing && space > 0xFF;
bool multibyte = upper || lower || (spacing && space > 0xFF);
utf8_reverse(dest + findex, dest + r.size(), multibyte);
// Add suffix
if (fancy_base)
{
static uint16_t fancy_base_digits[10] =
{
L'', L'', L'', L'', L'',
L'', L'', L'', L'', L''
};
if (base / 10)
r.put(unicode(fancy_base_digits[base/10]));
r.put(unicode(fancy_base_digits[base%10]));
r.put(unicode(fancy_lower_digits[base/10]));
r.put(unicode(fancy_lower_digits[base%10]));
}
else if (*fmt)
{
@ -618,17 +634,39 @@ HELP_BODY(based_integer)
}
static size_t fraction_render(fraction_p o, renderer &r, bool negative)
// ----------------------------------------------------------------------------
// Common code for positive and negative fractions
// ----------------------------------------------------------------------------
{
integer_g n = o->numerator(1);
integer_g d = o->denominator(1);
if (r.stack() && Settings.mixed_fractions)
{
ularge nv = n->value<ularge>();
ularge dv = d->value<ularge>();
if (nv >= dv)
{
integer_g i = integer::make(nv / dv);
render_num(r, i, 10, "");
r.put(unicode(settings::SPACE_MEDIUM_MATH));
n = integer::make(nv % dv);
}
}
render_num(r, n, 10, negative ? "^-/" : "^");
if (!negative)
r.put('/');
render_num(r, d, 10, "v");
return r.size();
}
RENDER_BODY(fraction)
// ----------------------------------------------------------------------------
// Render the fraction as 'num/den'
// ----------------------------------------------------------------------------
{
integer_g n = o->numerator(1);
integer_g d = o->denominator(1);
render_num(r, n, 10, "");
r.put('/');
render_num(r, d, 10, "");
return r.size();
return fraction_render(o, r, false);
}
@ -637,9 +675,5 @@ RENDER_BODY(neg_fraction)
// Render the fraction as '-num/den'
// ----------------------------------------------------------------------------
{
integer_g n = o->numerator(1);
integer_g d = o->denominator(1);
render_num(r, n, 10, "-/");
render_num(r, d, 10, "");
return r.size();
return fraction_render(o, r, true);
}

View file

@ -1068,6 +1068,10 @@ MENU(ModesMenu,
"Seps", ID_SeparatorModesMenu,
"UI", ID_UserInterfaceModesMenu,
"1 1/2", ID_MixedFractions,
"3/2", ID_ImproperFractions,
"1/3", ID_BigFractions,
"¹/₃", ID_SmallFractions,
"Modes", ID_Modes,
"Reset", ID_ResetModes);
@ -1100,24 +1104,24 @@ MENU(SeparatorModesMenu,
// ----------------------------------------------------------------------------
// Separators
// ----------------------------------------------------------------------------
"1 000", ID_NumberSpaces,
"1 000", ID_NumberSpaces,
Settings.decimal_mark == '.' ? "1,000." : "1.000,", ID_NumberDotOrComma,
"1'000", ID_NumberTicks,
"1_000", ID_NumberUnderscore,
"2.3", ID_DecimalDot,
"2,3", ID_DecimalComma,
"1'000", ID_NumberTicks,
"1_000", ID_NumberUnderscore,
"2.3", ID_DecimalDot,
"2,3", ID_DecimalComma,
"#1 000", ID_BasedSpaces,
"#1 000", ID_BasedSpaces,
Settings.decimal_mark == '.' ? "#1,000" : "#1.000", ID_BasedDotOrComma,
"#1'000", ID_BasedTicks,
"#1_000", ID_BasedUnderscore,
"Disp", ID_DisplayModesMenu,
"Modes", ID_ModesMenu,
"#1'000", ID_BasedTicks,
"#1_000", ID_BasedUnderscore,
"Disp", ID_DisplayModesMenu,
"Modes", ID_ModesMenu,
"1.2x10³²", ID_FancyExponent,
"1.2E32", ID_ClassicExponent,
"1.0→1.", ID_TrailingDecimal,
"1.0→1", ID_NoTrailingDecimal);
"1.2x10³²", ID_FancyExponent,
"1.2E32", ID_ClassicExponent,
"1.0→1.", ID_TrailingDecimal,
"1.0→1", ID_NoTrailingDecimal);
MENU(UserInterfaceModesMenu,
// ----------------------------------------------------------------------------

View file

@ -86,13 +86,25 @@ void settings::save(renderer &out, bool show_defaults)
else if (show_defaults)
out.put("DecimalDot\n");
// Save mixed fraction state
if (mixed_fractions)
out.put("MixedFractions\n");
else if (show_defaults)
out.put("ImproperFractions\n");
// Small fraction state
if (!small_fractions)
out.put("BigFractions\n");
else if (show_defaults)
out.put("SmallFractions\n");
// Save preferred exponent display mode
if (exponent_mark != L'' || !fancy_exponent)
out.put("ClassicExponent\n");
else if (show_defaults)
out.put("FancyExponent\n");
// Save preferred expenent for switching to scientfic mode
// Save preferred exponent for switching to scientfic mode
if (standard_exp != Defaults.standard_exp || show_defaults)
out.printf("%u StandardExponent\n", standard_exp);
@ -779,6 +791,46 @@ SETTINGS_COMMAND_NOLABEL(ClassicExponent, !Settings.fancy_exponent)
}
SETTINGS_COMMAND_NOLABEL(MixedFractions, Settings.mixed_fractions)
// ----------------------------------------------------------------------------
// Select mixed fractions mode
// ----------------------------------------------------------------------------
{
Settings.mixed_fractions = true;
return OK;
}
SETTINGS_COMMAND_NOLABEL(ImproperFractions, !Settings.mixed_fractions)
// ----------------------------------------------------------------------------
// Select improper fractions mode
// ----------------------------------------------------------------------------
{
Settings.mixed_fractions = false;
return OK;
}
SETTINGS_COMMAND_NOLABEL(BigFractions, !Settings.small_fractions)
// ----------------------------------------------------------------------------
// Select mixed fractions mode
// ----------------------------------------------------------------------------
{
Settings.small_fractions = false;
return OK;
}
SETTINGS_COMMAND_NOLABEL(SmallFractions, Settings.small_fractions)
// ----------------------------------------------------------------------------
// Select improper fractions mode
// ----------------------------------------------------------------------------
{
Settings.small_fractions = true;
return OK;
}
SETTINGS_COMMAND_BODY(Base, 0)
// ----------------------------------------------------------------------------
// Setting the maximum exponent before switching to scientific mode

View file

@ -93,6 +93,8 @@ struct settings
command_fmt(LONG_FORM),
show_decimal(true),
fancy_exponent(true),
mixed_fractions(false),
small_fractions(true),
auto_simplify(true),
numeric(false),
show_time(true),
@ -210,6 +212,8 @@ public:
commands command_fmt; // How we prefer to display commands
bool show_decimal :1; // Show decimal dot for integral real numbers
bool fancy_exponent :1; // Show exponent with fancy superscripts
bool mixed_fractions:1; // Show 3/2 as 1 1/2
bool small_fractions:1; // Show fractions with small digits
bool auto_simplify :1; // Automatically simplify symbolic results
bool numeric :1; // Convert results to numeric values
bool show_time :1; // Show time in status bar
@ -312,6 +316,10 @@ SETTINGS_COMMAND_DECLARE(StandardExponent);
SETTINGS_COMMAND_DECLARE(MinimumSignificantDigits);
SETTINGS_COMMAND_DECLARE(FancyExponent);
SETTINGS_COMMAND_DECLARE(ClassicExponent);
SETTINGS_COMMAND_DECLARE(MixedFractions);
SETTINGS_COMMAND_DECLARE(ImproperFractions);
SETTINGS_COMMAND_DECLARE(SmallFractions);
SETTINGS_COMMAND_DECLARE(BigFractions);
SETTINGS_COMMAND_DECLARE(Base);
SETTINGS_COMMAND_DECLARE(Bin);

View file

@ -257,7 +257,7 @@ inline void utf8_reverse(byte *start, byte *end, bool multibyte = true)
if (multibyte)
{
for (byte *p = start; p < end; p++)
for (byte *p = end - 1; p >= start; p--)
{
if (is_utf8_first(*p))
{