#javascript #math #rounding
#javascript #математика #округление
Вопрос:
Я опубликовал этот код пару дней назад, но обсуждение, казалось, приобрело философский характер по поводу слабостей Javascript (не говоря уже о моих собственных очевидных недостатках как «программиста»), а затем прекратилось, так и не прояснив решение. Я надеюсь, что кто-нибудь поможет это исправить.
Из-за явления, по-видимому, известного как «математика с плавающей запятой», Javascript возвращает арифметически неточный ответ (.00000004 или аналогичный ниже правильного ответа) в приведенном ниже коде калькулятора. Мне посоветовали округлить ответ, «вызвав math.round() для переменной», что, я думаю, отлично подошло бы для моих целей, только мое JS kungfu было слишком слабым, и синтаксис для этого в контексте до сих пор ускользал от меня.
Где / как мне выполнить этот вызов? До сих пор все мои попытки не увенчались успехом, даже когда я был уверен, что каждая из них не будет. Я был бы очень признателен за ответ, который учитывает мои низкоуровневые знания предмета. Это, должно быть, верняк для кого-то там.
<!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">amp;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">amp;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>
Комментарии:
1. Где вы обнаруживаете неточность?
2. @ 65Fbef05 Только при вводе-выводе выбираются зоны 1-3
3. Решение простое: не используйте значения с плавающей запятой в финансовых вычислениях. Используйте центы для всего или соответственно несколько, чтобы вам не приходилось работать с числами с плавающей запятой…
4. @Felix скажи, а? Просто, вы говорите? Я явно здесь не в своей тарелке, но я вижу центовые значения в этих, по общему признанию, неудобных, но все еще функциональных формулах. Решение может быть простым, но я до сих пор его не видел.
5. Я имею в виду, что вы должны работать только с целыми числами и только для представления создавать значения с плавающей запятой. Итак, вместо
3.12 * 12
у вас есть312 * 1200
и для представления вы снова делите это на100
.
Ответ №1:
В общем случае вы хотите округлять в последнюю очередь, чтобы поддерживать математическую точность вплоть до момента отображения данных.
Вот как я бы реализовал вашу маленькую библиотеку:
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 amp;amp; 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
) - Добавлена функция pad currency (так
1.4
становится1.40
)
Комментарии:
1. Спасибо за подробности. Конечно, я реализовал это неправильно, потому что это не сработало. Это для замены содержимого функции doMath4()? Как в: функция doMath4() { … ваш код здесь … }
2. Нет, на самом деле, вы хотите вызвать
SavingsLib.processForm();
с кнопки, которую вы хотите предупредить о значении экономии.
Ответ №2:
Вы можете написать 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 )
Редактировать: предполагалось поместить это в комментарии, но markdown там не работает? Все еще привыкаю к SO…
В любом случае, вы также можете использовать 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. @Gijs, спасибо, но, к сожалению, мне нужна кроссбраузерная совместимость (вы хотите сказать, что IE на самом деле вычисляет математику правильно?), и мне нужно получить два знака после запятой там.
2. @user: Вы проверили результаты в IE?
3. @Felix — на данный момент на Mac, и мой КОМПЬЮТЕР в настоящее время не работает. Я проверю, когда эта ситуация разрешится.
4. Насколько я понимаю, это будет отлично работать в IE. Просто если бы у вас было, скажем, 240.005 долларов, в итоге получилось бы 240.00 вместо 240.01, как вы ожидали бы, в IE. Однако, глядя на цифры в вашем приложении, это не имеет значения. У вас есть только числа с 2 или меньшим количеством знаков после запятой, и вы умножаете, поэтому не будет 3-го знака после запятой (во всяком случае, никаких допустимых).
5. @Gijs Я люблю kludges, и я очарован вашим ответом, но как мне включить это в свой код синтаксически? Это правильное слово?
Ответ №3:
Это очень просто:
var prodZ4tt = Math.round(((one * two) * 4.25) *12) - (((one * two) * 3.75) *12);
Хотя понятно, почему вы можете запутаться в синтаксисе, поскольку JavaScript не известен своим интуитивным способом выполнения действий.
Примечание: При этом число округляется до ближайшего целого числа, оно не «округляется в большую сторону».
Комментарии:
1. Спасибо, как за ответ, так и за сочувствие. Во-первых, как нам округлить это значение до ближайшего 2-го десятичного знака (чтобы в нем отображались доллары и центы)?
Ответ №4:
Для тех случаев, когда вам нужна точность, вам должна понадобиться математическая «Библиотека произвольной точности»… вы могли бы найти хорошую статью здесь.
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 ) и не числовые типы.
хорошего дня