Comments 29
больше никогда не буду писать от лени int(x+0.5)
Округление (round) в Go нелегко сделать правильно
А деление вычитанием в этом фантастическом языке делать не надо?
Первый и третий кейс понятны. Но почему округление -0.5 должно приводить к -1? Математически верно приводить к 0 (ну или к -0, если есть это спецзначение).
{-0.49999999999999994, negZero}, // -0.5+epsilon
{-0.5, -1},
{-0.5000000000000001, -1}, // -0.5-epsilon
Или в фразе
Я буду рассматривать округление к ближайшему целому, причём 0,5 будет округляться в большую сторону.имелось в виду в «большую по модулю»?
Но почему округление -0.5 должно приводить к -1?
По крайней мере у round() в C такое же правило — цитирую по линуксовому ману:
These functions round x to the nearest integer, but round halfway cases away from zero (regardless of the current rounding direction, see fenv(3)), instead of to the nearest even integer like rint(3). For example, round(0.5) is 1.0, and round(-0.5) is -1.0.
В доступных текстах стандартов то же самое по сути.
Так что если это решение не идеальное, то по крайней мере традиционное.
(В идеале, я бы предпочёл видеть или дополнительный параметр направления округления, или раздельные функции для каждого режима округления. Но сейчас и введение только одного варианта — уже успех.)
Округление к меньшему по модулю имеет вот такое замечательное свойство: round(x) + round(-x) == 0
В большинстве случаев, встречается округление к меньшему как поведение по умолчанию.
Статья на вики
Округление к меньшему по модулю имеет вот такое замечательное свойство: round(x) + round(-x) == 0
Округление к чётному, к нечётному, к нулю (к меньшему по модулю) и от нуля — все одновременно имеют это свойство.
В большинстве случаев, встречается округление к меньшему как поведение по умолчанию.
Хм… вообще-то в большинстве случаев или к ближайшему, а середину — от нуля (как в обсуждаемой реализации), или к ближайшему, а посредине — к чётному (банкирское, оно же гауссово округление). В школе нас учили единственному варианту, что округление — к ближайшему, а середину — от нуля.
В терминах IEEE754-2008 это соответственно roundToNearestTiesAway и roundToNearestTiesToEven.
Статья на вики
Статья хорошая как вводной обзор, но сильно неполная.
Нет, например, фоннеймановского округления, про которое см. документацию IBM:
Round to prepare for shorter precision: For a BFP or HFP permissible set (двоичная арифметика), the candidate selected is the one whose voting digit has an odd value. For a DFP permissible set (десятичная арифметика), the candidate that is smaller in magnitude is selected, unless its voting digit has a value of either 0 or 5; in that case, the candidate that is greater in magnitude is selected.
PHP round(-0.5) = -1
Exasol select round(-0.5) = -1
JS Math.round(-0.5) = 0
Java Math.round(-0.5) = 0
MySQL select round(-0.5) = -1
C round(-0.5) is -1.0
R round(-0.5) = 0
Erlang round(-0.5) = -1
Тогда получается соответственно для этих значений (в том же порядке, что у Вас; Exasol не знаю, пропускаю):
PHP round(): -2, -1, 1, 2 (середина — от нуля)
Javascript Math.round(): -1, 0, 1, 2 (середина — к +INF)
Java Math.round(): -1, 0, 1, 2 (середина — к +INF)
(но рядом есть rint(), который округляет середину к чётному: -2, 0, 0, 2)
MySQL select round(): -2, -1, 1, 2 (середина — от нуля)
C round(): -2, -1, 1, 2 (середина — от нуля)
R round(): -2, 0, 0, 2 (середина — к чётному)
Erlang round(): -2, -1, 1, 2 (середина — от нуля)
Кроме того:
Python3 round(): -2, 0, 0, 2 (середина — к чётному)
Perl POSIX::round: как в C (середина — от нуля)
как видите, до сих пор ни одного случая ни «середина к нулю», ни «середина к меньшему». Особый случай — Java и склонировавший её Javascript, где, наоборот, середина — к +INF (интересно, кто и зачем такое выбрал?)
Спасибо, вы правы, поправил текст.
$x = 0.00007;
$y = $x * 100000000;
echo($y); //outputs 7000
… а вот здесь на выходе будет 6999?
$x = 0.00007;
$y = $x * 100000000;
$y = (int)$y;
echo($y); //outputs 6999, why??
Всё верно, а такое поведение связано с тем, что echo кастует все параметры к string следующим образом:
case IS_DOUBLE: {
zend_string *str;
double dval = Z_DVAL_P(op);
str = zend_strpprintf(0, "%.*G", (int) EG(precision), dval);
/* %G already handles removing trailing zeros from the fractional part, yay */
ZVAL_NEW_STR(op, str);
break;
}
да использовать их надо, а не тестировать
программист он для заказчика программист, а для языка программирования пользователь
«корректно работающее округление появилось лишь в шестой мажорной версии Java (через 15 лет, прошедших с релиза Java 1.0 до выхода Java 7)»
15 лет… пендосиннопром жжет…
мне просто интересно, если бы такие базовые штуки, как округление, я выдавал бы своим пользователям через 15 лет,… да нет, я бы не дождался результатов теста, столько не проработал бы.
и ведь на этом языке до сих пор большая часть вакансий в нашем замкадье-захолустье
if math.IsNaN(x) {
return x
}
return math.Copysign(1.0, x) * math.Floor(math.Copysign(1.0, x) * x + 0.5)
=== RUN TestRound7
--- FAIL: TestRound7 (0.00s)
main_test.go:37: round( -0.5 ) = -0 != -1
main_test.go:37: round( 0.5 ) = 0 != 1
main_test.go:37: round( 2.2517998136852485e+15 ) = 2.251799813685248e+15 != 2.251799813685249e+15
Тесты я брал из статьи. В builtins_test.go теста для round не нашел.
команда Go согласилась добавить math.Round в Go 1.10! И даже появилась работающая реализация
Подскажите, пожалуйста, как правильно на базе этой реализации округлять до двух знаков после запятой. Например, по коду получаю 63 и -63, а хочу получить 62,84 и -62,84:
func main() {
x := 62.83444444444446
y := -62.83444444444446
fmt.Println(Round(x)) // 63 --> want 62.84
fmt.Println(Round(y)) // -63 --> want -62.84
}
В других случаях, — умножал и делил на сто, пример здесь: play.golang.org/p/jE9Q8BSsHQt
Точное представление десятичных дробей в виде конечных двоичных дробей невозможно, поэтому ответ — никак. Можно умножить на 100, взять round, поделить на 100 и вывести число на экран со спецификатором "%.2f".
github.com/montanaflynn/stats
вот так: play.golang.org/p/d_OUvLXSWaJ
Вариант на базе math.Round, когда берём битовое представление числа, сдвигаем его и применяем маску…, — правильно подправить не получается.
Обзор реализаций округления в Go