Как исправить неточную математику в Javascript с помощью math.round ()? - PullRequest
2 голосов
/ 12 мая 2011

Я опубликовал этот код пару дней назад, но дискуссия, похоже, переросла в философские аспекты слабых мест Javascript (не говоря уже о моих очевидных слабостях как «программиста»), а затем вымерла, так и не объяснив решения. Я надеюсь, что кто-нибудь поможет исправить это.

Из-за явления, известного как «математика с плавающей запятой», Javascript возвращает арифметически неточный ответ (.00000004 или аналогичный ниже правильный ответ) в коде калькулятора ниже. Мне посоветовали округлить ответ путем «вызова math.round () для переменной», который, я думаю, будет работать нормально для моих целей, только мой кунг-фу JS был слишком слабым, и синтаксис выполнения этого в контексте, таким образом, далеко ускользнуло от меня.

Где / как мне сделать этот звонок? Пока что все мои попытки провалились, даже когда я думал, что каждая из них потерпит неудачу. Я был бы признателен за ответ, который учитывает мои низкие знания предмета. Для кого-то это должно быть хламом.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Untitled Document</title>

<script language="javascript">

<!-- Begin Trip Tickets Savings Calc script
function  doMath4() {
    var one = parseInt(document.theForm4.elements[0].value);
    var two = parseInt(document.theForm4.elements[1].value);
    var selection = document.getElementsByName("zonett")[0].value;

    if(selection == "z4"){
        var prodZ4tt = (((one  *   two) * 4.25) *12) - (((one  *   two) * 3.75) *12);
        alert("Your yearly savings if you buy Trip Tickets is $"  +  prodZ4tt  +  ".");
    }
    else if(selection == "z3"){
        var prodZ3tt = (((one  *   two) * 3.75) *12) - (((one  *   two) * 3.35) *12);
        alert("Your yearly savings if you buy Trip Tickets is $"  +  prodZ3tt  +  ".");
    }
    else if(selection == "z2"){
        var prodZ2tt = (((one  *   two) * 3) *12) - (((one  *   two) * 2.8) *12);
        alert("Your yearly savings if you buy Trip Tickets is $"  +  prodZ2tt  +  ".");
    }
    else if(selection == "z1"){
        var prodZ1tt = (((one  *   two) * 2.5) *12) - (((one  *   two) * 2.3) *12);
        alert("Your yearly savings if you buy Trip Tickets is $"  +  prodZ1tt  +  ".");
    }
    else if(selection == "Base"){
        var prodBasett = (((one  *   two) * 1.5) *12) - (((one  *   two) * 1.5) *12);
        alert("Your yearly savings if you buy Trip Tickets is $"  +  prodBasett  +  ".");
    }
}

// End Trip Tickets Savings Calc script -->

</script>
</head>

<body>


<form name="theForm4" class="calcform">
<h2>You Do the Math: Commuter Express Trip Tickets Vs. Cash</h2>
<div class="calcform-content">
    <div class="formrow-calc">
      <div class="calcform-col1">
        <p>Days you commute on Commuter Express monthly:</p>
      </div>
      <div class="calcform-col2">
        <input type="text">
      </div>
      <div class="calcform-col3">&nbsp;</div>
    </div>
    <div class="clear"></div>


    <div class="formrow-calc">
      <div class="calcform-col1">
        <p>Daily boardings on Commuter Express Bus:</p>

        <table border="0" cellspacing="0" cellpadding="0" class="fareexampletable">
            <tr>
              <td colspan="2" class="savingsleft"><p class="ifyouride">EXAMPLE:</p></td>
              </tr>
            <tr>
              <td class="savingsleft"><p><strong>Go to work:</strong></p></td>
              <td class="savingsright"><p>1 time</p></td>
            </tr>
            <tr>
              <td class="savingsleft"><p><strong>Come home from work:</strong></p></td>
              <td class="additionline savingsright"><p>+1 time</p></td>
            </tr>
            <tr>
              <td class="savingsleft"><p><strong>Total:</strong></p></td>
              <td class="savingsright"><p>2 times</p></td>
            </tr>
          </table>
      </div>
      <div class="calcform-col2">
        <input type="text">
      </div>
      <div class="calcform-col3">&nbsp;</div>
    </div>
    <div class="clear"></div>


    <div class="formrow-calc savings-zone">
      <div class="calcform-col1">
        <p>Choose Zone:</p>
      </div>
      <div class="calcform-col2">
        <select name="zonett">
          <option value="Base">Base</option>
          <option value="z1">Zone 1</option>
          <option value="z2">Zone 2</option>
          <option value="z3">Zone 3</option>
          <option value="z4">Zone 4</option>
        </select>
      </div>
    </div>



    <div class="formrow-calc">


          <div class="calcform-col4-ce">


 <button type="submit" onclick="doMath4()" class="btn-submit"><div class="btn-submit"><img src="img/btn_savings.png" alt="Show My Yearly Savings" /></div></button> 


    </div>
    </div>
    <div class="clear">


</div>



</div>
</form>

</body>
</html>

Ответы [ 4 ]

2 голосов
/ 12 мая 2011

В общем случае вы хотите округлить до последнего , так что вы сохраняете математическую точность вплоть до точки отображения данных.

Вот как я бы реализовал вашу маленькую библиотеку:

var SavingsLib = {
    round : function(val, digits) {
        var pow = Math.pow(10, digits);
        return Math.round(pow * val) / pow;
    },

    padCurrency : function (val) {
        var str = val.toString();
        var parts = str.split(".");
        return parts.length > 1 && parts[1].length == 1 ? str + "0" : str;
    },

    processForm : function() {
        var daysPerMonth = parseInt(document.theForm4.elements[0].value);
        var boardingsPerDay = parseInt(document.theForm4.elements[1].value);
        var zone = document.getElementsByName("zonett")[0].value;

        var savings = 0;
        if(zone == "z4") savings = this.calculateSavings(daysPerMonth, boardingsPerDay, 4.25, 3.75);
        else if(zone == "z3") savings = this.calculateSavings(daysPerMonth, boardingsPerDay, 3.75, 3.35);
        else if(zone == "z2") savings = this.calculateSavings(daysPerMonth, boardingsPerDay, 3, 2.8);
        else if(zone == "z1") savings = this.calculateSavings(daysPerMonth, boardingsPerDay, 2.5, 2.3);
        else if(zone == "Base") savings = this.calculateSavings(daysPerMonth, boardingsPerDay, 1.5, 1.5);

        this.showMessage(savings);

        return false; // Don't submit form
    },

    calculateSavings : function(daysPerMonth, boardingsPerDay, price1, price2) {
        var amount1 = (((daysPerMonth * boardingsPerDay) * price1) * 12);
        var amount2 = (((daysPerMonth * boardingsPerDay) * price2) * 12);

        return this.round(amount1 - amount2, 2);
    },

    showMessage : function(savings) {
        alert("Your yearly savings if you buy Trip Tickets is $" + this.padCurrency(savings));
    }
}

Мои изменения:

  • Инкапсулированные функции в объект для пространства имен
  • Извлечена математика в один метод, так что вы можете сделать один раунд вызов
  • Извлечено отображение сообщения в метод, чтобы вы могли легко его изменить
  • Добавлена ​​функция округления до цифры (поэтому 1.4500001 становится 1.45 вместо 1)
  • Добавлена ​​функция валюты пэда (поэтому 1.4 становится 1.40)
1 голос
/ 12 мая 2011

Вы можете написать kludge с помощью Math.round, но это обнулит центы (не уверен, что это применимо в вашем примере, похоже, что это возможно). То, что вы можете хотеть, это исправить:

alert((102.000000004).toFixed(2)); // alerts "102.00"

Это не округляется в IE, но из того, что я могу сказать, это не будет иметь значения в вашем случае.

(документы: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number/toFixed, http://www.codingforums.com/showthread.php?t=102421, http://msdn.microsoft.com/en-us/library/sstyff0z.aspx)

Редактировать: намеревался добавить это в комментарии, но уценка там не работает? Все еще привыкаю к ​​ТАК ...

Во всяком случае, вы также можете использовать Kludge с Math.round, если вы предпочитаете:

alert(Math.round(val * 100) / 100);

Который может по-прежнему отображать глупые проблемы с плавающей точкой, для которых вы могли бы использовать другое .toFixed (и теперь вы на 100% уверены, что нет оставшихся десятичных разрядов, которые вы не округлите должным образом):

alert((Math.round(val * 100) / 100).toFixed(2));

Редактировать: интегрировать:

Добавьте это вверху вашего блока скриптов:

function roundNicely(val) {
     return (Math.round(val * 100) / 100).toFixed(2);
}

Затем в ваших оповещениях оберните значение prodBasett в вызов функции: roundNicely(prodBasett) (и аналогично для других оповещений)

Имеет ли это смысл?

1 голос
/ 12 мая 2011

Для тех случаев, когда вам нужна точность, вам нужна математическая «Библиотека произвольной точности» ... вы можете найти хорошую статью здесь.

http://blog.thatscaptaintoyou.com/introducing-big-js-arbitrary-precision-math-for-javascript/

В Javascript изначально есть round, ceil и floor для преобразования чисел в целые числа.

var fl = 349.12783691847;
Math.round(fl) // 349
Math.ceil(f1) // 350
Math.floor(fl) // 349

когда вы выполняете математические операции, рекомендуется использовать дистрибутив и правильно использовать круглые скобки.

Math.round(((((a + b) * (c - d)) % e) * f));

Будьте осторожны с «NaN» (http://en.wikipedia.org/wiki/NaN) и не числовыми типами.

хорошего дня

1 голос
/ 12 мая 2011

Это очень просто :

var prodZ4tt = Math.round(((one  *   two) * 4.25) *12) - (((one  *   two) * 3.75) *12);

Хотя понятно, почему вы можете запутаться в синтаксисе, поскольку JavaScript не известен своим интуитивным способом выполнения действий.;)

Примечание: Округляет число до ближайшего целого числа, а не "округляет вверх".

...