简体   繁体   中英

How can I keep fraction formatting using JavaScript?

My code is focused on cooking (Banana Bread recipe). Depending on the number of people, I will sometimes make two Banana Bread's as opposed to one. Thus, I use the selection tool to account for this by changing the amount of each ingredient. Yet, my problem is JavaScript outputting decimals instead of fractions. I want to keep the numbers in fractions.

Ideal
If 1 was selected, it would say 2 cups flour, ½ tsp salt.
If 2 was selected, it would say 4 cups flour, 1 tsp salt.
If 3 was selected, it would say 6 cups flour, 1 ½ tsp salt.

What actually happens:
If 1 was selected, it would say 2 cups flour, 0.5 tsp salt.
If 2 was selected, it would say 4 cups flour, 1 tsp salt.
If 3 was selected, it would say 6 cups flour, 1.5 tsp salt.

Code:

 document.getElementById("button").addEventListener("click", onButtonClick); function onButtonClick() { document.getElementById("amount").innerText = 2; document.getElementById("amount2").innerText = 1 / 2; var n1 = document.getElementById("amount").innerText; var n2 = document.getElementById("amount2").innerText; var selection = document.getElementById("quantity").value; if (selection === 'a') { document.getElementById('amount').innerText = n1; document.getElementById('amount2').innerText = numberToFraction(n2); } if (selection === 'b') { document.getElementById('amount').innerText = n1 * 2; document.getElementById('amount2').innerText = n2 * 2; } if (selection === 'c') { document.getElementById('amount').innerText = n1 * 3; document.getElementById('amount2').innerText = numberToFraction(n2 * 3) } } var numberToFraction = function(amount) { // This is a whole number and doesn't need modification. if (parseFloat(amount) === parseInt(amount)) { return amount; } // Next 12 lines are cribbed from https://stackoverflow.com/a/23575406. var gcd = function(a, b) { if (b < 0.0000001) { return a; } return gcd(b, Math.floor(a % b)); }; var len = amount.toString().length - 2; var denominator = Math.pow(10, len); var numerator = amount * denominator; var divisor = gcd(numerator, denominator); numerator /= divisor; denominator /= divisor; var base = 0; // In a scenario like 3/2, convert to 1 1/2 // by pulling out the base number and reducing the numerator. if (numerator > denominator) { base = Math.floor(numerator / denominator); numerator -= base * denominator; } amount = Math.floor(numerator) + '/' + Math.floor(denominator); if (base) { amount = base + ' ' + amount; } return amount; }; 
 <label> How many Banana Bread's are you making? </label> <!-- Selection --> <select id="quantity"> <option value="a">1</option> <option value="b">2</option> <option value="c">3</option> </select><br><br> <!-- Button --> <button id="button" type="button">Let's get started!</button><br><br> <!-- HTML Recipe --> <p> Step 1: Add <span id="amount">2</span> cups flour and <span id="amount2"> &frac12;</span> tsp salt into a large, dry bowl. </p> 

Instead of assigning values a, b, c to the options 1, 2, 3, just use the latter value: it is more intuitive. You can directly use that value for the multiplication instead of doing three different if blocks. You can also do away with the button, since you could just respond to the change in the select-list.

For the particular display of fractions, I would suggest putting all that logic in a class, which you could call Rational . It would keep numerator and denominator separate and allow to do multiplication (and other operations) on it, and to display the result with the desired output format. This way you isolate that formatting logic from the recipe logic.

Here is such a Rational class: it could be easily extended to do more than multiplication (eg addition, division, negation, inversion, ...):

 const gcd = (a, b) => b ? gcd(b, a % b) : a; class Rational { constructor(num, denom = 1) { if (num % 1 || denom % 1) throw "Rational constructor should get integer value(s)"; this.num = num * Math.sign(denom); this.denom = Math.abs(denom); } mul(b) { if (typeof b === "number") b = new Rational(b); const denom1 = gcd(this.denom, b.num); const denom2 = gcd(this.num, b.denom); return new Rational((this.num / denom2) * (b.num / denom1), (this.denom / denom1) * (b.denom / denom2)); } toString() { const sgn = this.num < 0 ? "-" : ""; const n = Math.abs(this.num); const i = Math.trunc(n / this.denom); const rem = (n % this.denom); const frac = rem ? rem + "/" + this.denom : ""; const remainder = { "1/2": "½", "1/3": "⅓", "2/3": "⅔", "1/4": "¼", "3/4": "¾", "1/5": "⅕", "2/5": "⅖", "3/5": "⅗", "4/5": "⅘", "1/6": "⅙", "5/6": "⅚", "1/7": "⅐", "1/8": "⅛", "3/8": "⅜", "5/8": "⅝", "7/8": "⅞", "1/9": "⅑", "1/10": "⅒" }[frac] || frac && ((i ? "+" : "") + frac); return sgn + (i || !remainder ? i : "") + remainder; } } document.getElementById("quantity").addEventListener("change", onSelectionChange); var n1 = new Rational(2); var n2 = new Rational(1, 2); function onSelectionChange() { var selection = +document.getElementById("quantity").value; document.getElementById('amount').textContent = n1.mul(selection); document.getElementById('amount2').textContent = n2.mul(selection); } 
 <label>How many Banana Bread's are you making?</label> <!-- Selection --> <select id="quantity"> <option>1</option> <option>2</option> <option>3</option> </select><br><br> <!-- HTML Recipe --> <p> Step 1: Add <span id="amount">2</span> cups flour and <span id="amount2">&frac12;</span> tsp salt into a large, dry bowl. </p> 

Notice how the last function can concentrate on the recipe logic, delegating the use of fractions to the Rational class, which in turn is completely ignorant of the recipe-business.

For older JS engines...

As you comment about an error that may be related to a JS engine that does not support ES6 syntax, I add here a version of the same using old-ES3 syntax:

 function gcd (a, b) { return b ? gcd(b, a % b) : a; } function Rational(num, denom) { if (!denom) denom = 1; if (num % 1 || denom % 1) throw "Rational constructor should get integer value(s)"; this.num = num * Math.sign(denom); this.denom = Math.abs(denom); } Rational.prototype.mul = function (b) { if (typeof b === "number") b = new Rational(b); var denom1 = gcd(this.denom, b.num); var denom2 = gcd(this.num, b.denom); return new Rational((this.num / denom2) * (b.num / denom1), (this.denom / denom1) * (b.denom / denom2)); } Rational.prototype.toString = function () { var sgn = this.num < 0 ? "-" : ""; var n = Math.abs(this.num); var i = Math.floor(n / this.denom); var rem = (n % this.denom); var frac = rem ? rem + "/" + this.denom : ""; var remainder = { "1/2": "½", "1/3": "⅓", "2/3": "⅔", "1/4": "¼", "3/4": "¾", "1/5": "⅕", "2/5": "⅖", "3/5": "⅗", "4/5": "⅘", "1/6": "⅙", "5/6": "⅚", "1/7": "⅐", "1/8": "⅛", "3/8": "⅜", "5/8": "⅝", "7/8": "⅞", "1/9": "⅑", "1/10": "⅒" }[frac] || frac && ((i ? "+" : "") + frac); return sgn + (i || !remainder ? i : "") + remainder; } document.getElementById("quantity").addEventListener("change", onSelectionChange); var n1 = new Rational(2); var n2 = new Rational(1, 2); function onSelectionChange() { var selection = +document.getElementById("quantity").value; document.getElementById('amount').textContent = n1.mul(selection); document.getElementById('amount2').textContent = n2.mul(selection); } 
 <label>How many Banana Bread's are you making?</label> <!-- Selection --> <select id="quantity"> <option>1</option> <option>2</option> <option>3</option> </select><br><br> <!-- HTML Recipe --> <p> Step 1: Add <span id="amount">2</span> cups flour and <span id="amount2">&frac12;</span> tsp salt into a large, dry bowl. </p> 

...

    amount = Math.floor(numerator) + '/' + Math.floor(denominator);
    //EDIT
    switch (amount) {
        case '1/4':
            amount = '&frac14;';
        break;
        case '1/3':
            amount = '&‌#8531;';
        break;
        case '1/2':
            amount = '&frac12;';
        break;
        case '2/3':
            amount = '&‌#8532;';
        break;
        case '3/4':
            amount = '&frac34;';
        break;
        default:
            amount = amount;
        break;
    }
    //END OF EDIT
    if ( base ) {
        amount = base + ' ' + amount;
    }
    return amount;

To representate 0.5 as a fraction you're using the html entity &frac12; . The numberToFraction() function is actually returning a string like "1/2" though.

So you need to include a check if amount is 1/2 and in case it is replace it by &frac12; and return this instead.

Furthermore to update the span's you need to use their .innerHTML property instead of .innerText - otherwise you won't see a fraction.

Here's an example:

 document.getElementById("button").addEventListener("click", onButtonClick); function onButtonClick() { document.getElementById("amount").innerText = 2; document.getElementById("amount2").innerText = 1 / 2; var n1 = document.getElementById("amount").innerText; var n2 = document.getElementById("amount2").innerText; var selection = document.getElementById("quantity").value; if (selection === 'a') { document.getElementById('amount').innerText = n1; document.getElementById('amount2').innerHTML = numberToFraction(n2); } if (selection === 'b') { document.getElementById('amount').innerText = n1 * 2; document.getElementById('amount2').innerHTML = n2 * 2; } if (selection === 'c') { document.getElementById('amount').innerText = n1 * 3; document.getElementById('amount2').innerHTML = numberToFraction(n2 * 3) } } var numberToFraction = function(amount) { // This is a whole number and doesn't need modification. if (parseFloat(amount) === parseInt(amount)) { return amount; } // Next 12 lines are cribbed from https://stackoverflow.com/a/23575406. var gcd = function(a, b) { if (b < 0.0000001) { return a; } return gcd(b, Math.floor(a % b)); }; var len = amount.toString().length - 2; var denominator = Math.pow(10, len); var numerator = amount * denominator; var divisor = gcd(numerator, denominator); numerator /= divisor; denominator /= divisor; var base = 0; // In a scenario like 3/2, convert to 1 1/2 // by pulling out the base number and reducing the numerator. if (numerator > denominator) { base = Math.floor(numerator / denominator); numerator -= base * denominator; } amount = Math.floor(numerator) + '/' + Math.floor(denominator); if (amount == "1/2") { amount = "&frac12;" } if (base) { amount = base + ' ' + amount; } return amount; }; 
 <label> How many Banana Bread's are you making? </label> <!-- Selection --> <select id="quantity"> <option value="a">1</option> <option value="b">2</option> <option value="c">3</option> </select><br><br> <!-- Button --> <button id="button" type="button">Let's get started!</button><br><br> <!-- HTML Recipe --> <p> Step 1: Add <span id="amount">2</span> cups flour and <span id="amount2"> &frac12;</span> tsp salt into a large, dry bowl. </p> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM