1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

numeric.c: round to nearest even

* numeric.c (flo_round, int_round): support round-to-nearest-even
  semantics of IEEE 754 to match sprintf behavior, and add `half:`
  optional keyword argument for the old behavior.
  [ruby-core:76273] [Bug #12548]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@56590 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
nobu 2016-11-05 09:49:39 +00:00
parent 76977611dd
commit dfe91fcd08
10 changed files with 322 additions and 39 deletions

View file

@ -1,3 +1,10 @@
Sat Nov 5 18:49:37 2016 Nobuyoshi Nakada <nobu@ruby-lang.org>
* numeric.c (flo_round, int_round): support round-to-nearest-even
semantics of IEEE 754 to match sprintf behavior, and add `half:`
optional keyword argument for the old behavior.
[ruby-core:76273] [Bug #12548]
Sat Nov 5 18:17:54 2016 Akinori MUSHA <knu@iDaemons.org>
* lib/set.rb (Set#compare_by_identity, Set#compare_by_identity?):

11
NEWS
View file

@ -69,6 +69,9 @@ with all sufficient information, see the ChangeLog file or Redmine
* Float#ceil, Float#floor, and Float#truncate now take an optional
digits, as well as Float#round. [Feature #12245]
* Float#round now takes an optional keyword argument, half option, and
the default behavior is round-to-nearest-even now. [Bug #12548]
* Hash
* Hash#transform_values and Hash#transform_values! [Feature #12512]
@ -83,6 +86,9 @@ with all sufficient information, see the ChangeLog file or Redmine
* Integer#digits for extracting columns of place-value notation [Feature #12447]
* Int#round now takes an optional keyword argument, half option, and the
default behavior is round-to-nearest-even now. [Bug #12548]
* IO
* IO#gets, IO#readline, IO#each_line, IO#readlines, IO#foreach now takes
@ -112,6 +118,11 @@ with all sufficient information, see the ChangeLog file or Redmine
* Support CLOCK_MONOTONIC_RAW_APPROX, CLOCK_UPTIME_RAW, and
CLOCK_UPTIME_RAW_APPROX which are introduced by macOS 10.12.
* Rational
* Rational#round now takes an optional keyword argument, half option, and
the default behavior is round-to-nearest-even now. [Bug #12548]
* Regexp
* Regexp#match? [Feature #8110]

View file

@ -1135,6 +1135,17 @@ VALUE rb_math_sqrt(VALUE);
void Init_newline(void);
/* numeric.c */
#ifndef ROUND_DEFAULT
# define ROUND_DEFAULT RUBY_NUM_ROUND_HALF_EVEN
#endif
enum ruby_num_rounding_mode {
RUBY_NUM_ROUND_HALF_UP,
RUBY_NUM_ROUND_HALF_EVEN,
RUBY_NUM_ROUND_DEFAULT = ROUND_DEFAULT
};
#define ROUND_TO(mode, up, even) \
((mode) == RUBY_NUM_ROUND_HALF_EVEN ? even : up)
int rb_num_to_uint(VALUE val, unsigned int *ret);
VALUE ruby_num_interval_step_size(VALUE from, VALUE to, VALUE step, int excl);
int ruby_float_step(VALUE from, VALUE to, VALUE step, int excl);
@ -1148,11 +1159,12 @@ VALUE rb_int_minus(VALUE x, VALUE y);
VALUE rb_int_mul(VALUE x, VALUE y);
VALUE rb_int_idiv(VALUE x, VALUE y);
VALUE rb_int_modulo(VALUE x, VALUE y);
VALUE rb_int_round(VALUE num, int ndigits);
VALUE rb_int_round(VALUE num, int ndigits, enum ruby_num_rounding_mode mode);
VALUE rb_int2str(VALUE num, int base);
VALUE rb_dbl_hash(double d);
VALUE rb_fix_plus(VALUE x, VALUE y);
VALUE rb_int_ge(VALUE x, VALUE y);
enum ruby_num_rounding_mode rb_num_get_rounding_option(VALUE opts);
#if USE_FLONUM
#define RUBY_BIT_ROTL(v, n) (((v) << (n)) | ((v) >> ((sizeof(v) * 8) - n)))

View file

@ -205,8 +205,8 @@ module REXML
# Now, get the bounds. The XPath bounds are 1..length; the ruby bounds
# are 0..length. Therefore, we have to offset the bounds by one.
ruby_start = ruby_start.round - 1
ruby_length = ruby_length.round
ruby_start = round(ruby_start) - 1
ruby_length = round(ruby_length)
if ruby_start < 0
ruby_length += ruby_start unless infinite_length
@ -376,10 +376,13 @@ module REXML
end
def Functions::round( number )
number = number(number)
begin
number(number).round
neg = number.negative?
number = number.abs.round(half: :up)
neg ? -number : number
rescue FloatDomainError
number(number)
number
end
end

121
numeric.c
View file

@ -93,7 +93,7 @@ round(double x)
#endif
static double
round_to_nearest(double x, double s)
round_half_up(double x, double s)
{
double f, xs = x * s;
@ -117,12 +117,44 @@ round_to_nearest(double x, double s)
return x;
}
static double
round_half_even(double x, double s)
{
double f, d, xs = x * s;
if (x > 0.0) {
f = floor(xs);
d = xs - f;
if (d > 0.5)
d = 1.0;
else if (d == 0.5 || ((double)((f + 0.5) / s) <= x))
d = fmod(f, 2.0);
else
d = 0.0;
x = f + d;
}
else if (x < 0.0) {
f = ceil(xs);
d = f - xs;
if (d > 0.5)
d = 1.0;
else if (d == 0.5 || ((double)((f - 0.5) / s) >= x))
d = fmod(-f, 2.0);
else
d = 0.0;
x = f - d;
}
return x;
}
static VALUE fix_uminus(VALUE num);
static VALUE fix_mul(VALUE x, VALUE y);
static VALUE fix_lshift(long, unsigned long);
static VALUE fix_rshift(long, unsigned long);
static VALUE int_pow(long x, unsigned long y);
static VALUE int_cmp(VALUE x, VALUE y);
static VALUE int_odd_p(VALUE x);
static VALUE int_even_p(VALUE x);
static int int_round_zero_p(VALUE num, int ndigits);
VALUE rb_int_floor(VALUE num, int ndigits);
VALUE rb_int_ceil(VALUE num, int ndigits);
@ -152,6 +184,38 @@ rb_num_zerodiv(void)
rb_raise(rb_eZeroDivError, "divided by 0");
}
enum ruby_num_rounding_mode
rb_num_get_rounding_option(VALUE opts)
{
static ID round_kwds[1];
VALUE rounding;
const char *s;
long l;
if (!NIL_P(opts)) {
if (!round_kwds[0]) {
round_kwds[0] = rb_intern_const("half");
}
if (!rb_get_kwargs(opts, round_kwds, 0, 1, &rounding)) goto noopt;
if (SYMBOL_P(rounding)) rounding = rb_sym2str(rounding);
s = StringValueCStr(rounding);
l = RSTRING_LEN(rounding);
switch (l) {
case 2:
if (strncasecmp(s, "up", 2) == 0)
return RUBY_NUM_ROUND_HALF_UP;
break;
case 4:
if (strncasecmp(s, "even", 4) == 0)
return RUBY_NUM_ROUND_HALF_EVEN;
break;
}
rb_raise(rb_eArgError, "unknown rounding mode: %"PRIsVALUE, rounding);
}
noopt:
return RUBY_NUM_ROUND_DEFAULT;
}
/* experimental API */
int
rb_num_to_uint(VALUE val, unsigned int *ret)
@ -201,7 +265,6 @@ compare_with_zero(VALUE num, ID mid)
#define FIXNUM_NEGATIVE_P(num) ((SIGNED_VALUE)(num) < 0)
#define FIXNUM_ZERO_P(num) ((num) == INT2FIX(0))
#if 0
static inline int
int_pos_p(VALUE num)
{
@ -213,7 +276,6 @@ int_pos_p(VALUE num)
}
return Qnil;
}
#endif
static inline int
int_neg_p(VALUE num)
@ -1962,11 +2024,27 @@ int_round_zero_p(VALUE num, int ndigits)
return (-0.415241 * ndigits - 0.125 > bytes);
}
static SIGNED_VALUE
int_round_half_even(SIGNED_VALUE x, SIGNED_VALUE y)
{
SIGNED_VALUE z = +(x + y / 2) / y;
if ((z * y - x) * 2 == y) {
z &= ~1;
}
return z * y;
}
static SIGNED_VALUE
int_round_half_up(SIGNED_VALUE x, SIGNED_VALUE y)
{
return (x + y / 2) / y * y;
}
/*
* Assumes num is an Integer, ndigits <= 0
*/
VALUE
rb_int_round(VALUE num, int ndigits)
rb_int_round(VALUE num, int ndigits, enum ruby_num_rounding_mode mode)
{
VALUE n, f, h, r;
@ -1979,7 +2057,9 @@ rb_int_round(VALUE num, int ndigits)
SIGNED_VALUE x = FIX2LONG(num), y = FIX2LONG(f);
int neg = x < 0;
if (neg) x = -x;
x = (x + y / 2) / y * y;
x = ROUND_TO(mode,
int_round_half_up(x, y),
int_round_half_even(x, y));
if (neg) x = -x;
return LONG2NUM(x);
}
@ -1991,7 +2071,11 @@ rb_int_round(VALUE num, int ndigits)
r = rb_int_modulo(num, f);
n = rb_int_minus(num, r);
r = int_cmp(r, h);
if (FIXNUM_POSITIVE_P(r) || (FIXNUM_ZERO_P(r) && !int_neg_p(num))) {
if (FIXNUM_POSITIVE_P(r) ||
(FIXNUM_ZERO_P(r) &&
ROUND_TO(mode,
int_pos_p(num),
int_odd_p(rb_int_idiv(n, f))))) {
n = rb_int_plus(n, f);
}
return n;
@ -2109,21 +2193,27 @@ static VALUE
flo_round(int argc, VALUE *argv, VALUE num)
{
double number, f, x;
VALUE nd, opt;
int ndigits = 0;
enum ruby_num_rounding_mode mode;
if (rb_check_arity(argc, 0, 1)) {
ndigits = NUM2INT(argv[0]);
if (rb_scan_args(argc, argv, "01:", &nd, &opt)) {
ndigits = NUM2INT(nd);
}
mode = rb_num_get_rounding_option(opt);
if (ndigits < 0) {
return rb_int_round(flo_to_i(num), ndigits);
return rb_int_round(flo_to_i(num), ndigits, mode);
}
number = RFLOAT_VALUE(num);
if (ndigits == 0) {
return dbl2ival(round(number));
x = ROUND_TO(mode,
round(number), round_half_even(number, 1.0));
return dbl2ival(x);
}
if (float_invariant_round(number, ndigits, &num)) return num;
f = pow(10, ndigits);
x = round_to_nearest(number, f);
x = ROUND_TO(mode,
round_half_up(number, f), round_half_even(number, f));
return DBL2NUM(x / f);
}
@ -4862,16 +4952,19 @@ static VALUE
int_round(int argc, VALUE* argv, VALUE num)
{
int ndigits;
int mode;
VALUE nd, opt;
if (!rb_check_arity(argc, 0, 1)) return num;
ndigits = NUM2INT(argv[0]);
if (!rb_scan_args(argc, argv, "01:", &nd, &opt)) return num;
ndigits = NUM2INT(nd);
mode = rb_num_get_rounding_option(opt);
if (ndigits > 0) {
return rb_Float(num);
}
if (ndigits == 0) {
return num;
}
return rb_int_round(num, ndigits);
return rb_int_round(num, ndigits, mode);
}
/*

View file

@ -1250,7 +1250,7 @@ nurat_truncate(VALUE self)
}
static VALUE
nurat_round(VALUE self)
nurat_round_half_up(VALUE self)
{
VALUE num, den, neg;
@ -1273,6 +1273,33 @@ nurat_round(VALUE self)
return num;
}
static VALUE
nurat_round_half_even(VALUE self)
{
VALUE num, den, neg, qr;
get_dat1(self);
num = dat->num;
den = dat->den;
neg = f_negative_p(num);
if (neg)
num = f_negate(num);
num = f_add(f_mul(num, TWO), den);
den = f_mul(den, TWO);
qr = rb_funcall(num, rb_intern("divmod"), 1, den);
num = RARRAY_AREF(qr, 0);
if (f_zero_p(RARRAY_AREF(qr, 1)))
num = rb_funcall(num, '&', 1, LONG2FIX(((int)~1)));
if (neg)
num = f_negate(num);
return num;
}
static VALUE
f_round_common(int argc, VALUE *argv, VALUE self, VALUE (*func)(VALUE))
{
@ -1403,7 +1430,14 @@ nurat_truncate_n(int argc, VALUE *argv, VALUE self)
static VALUE
nurat_round_n(int argc, VALUE *argv, VALUE self)
{
return f_round_common(argc, argv, self, nurat_round);
VALUE opt;
enum ruby_num_rounding_mode mode = (
argc = rb_scan_args(argc, argv, "*:", NULL, &opt),
rb_num_get_rounding_option(opt));
VALUE (*round_func)(VALUE) =
ROUND_TO(mode,
nurat_round_half_up, nurat_round_half_even);
return f_round_common(argc, argv, self, round_func);
}
/*

View file

@ -659,6 +659,48 @@ class TestFloat < Test::Unit::TestCase
}
end
def test_round_half_even
assert_equal(12.0, 12.5.round(half: :even))
assert_equal(14.0, 13.5.round(half: :even))
assert_equal(2.2, 2.15.round(1, half: :even))
assert_equal(2.2, 2.25.round(1, half: :even))
assert_equal(2.4, 2.35.round(1, half: :even))
assert_equal(-2.2, -2.15.round(1, half: :even))
assert_equal(-2.2, -2.25.round(1, half: :even))
assert_equal(-2.4, -2.35.round(1, half: :even))
assert_equal(7.1364, 7.13645.round(4, half: :even))
assert_equal(7.1365, 7.1364501.round(4, half: :even))
assert_equal(7.1364, 7.1364499.round(4, half: :even))
assert_equal(-7.1364, -7.13645.round(4, half: :even))
assert_equal(-7.1365, -7.1364501.round(4, half: :even))
assert_equal(-7.1364, -7.1364499.round(4, half: :even))
end
def test_round_half_up
assert_equal(13.0, 12.5.round(half: :up))
assert_equal(14.0, 13.5.round(half: :up))
assert_equal(2.2, 2.15.round(1, half: :up))
assert_equal(2.3, 2.25.round(1, half: :up))
assert_equal(2.4, 2.35.round(1, half: :up))
assert_equal(-2.2, -2.15.round(1, half: :up))
assert_equal(-2.3, -2.25.round(1, half: :up))
assert_equal(-2.4, -2.35.round(1, half: :up))
assert_equal(7.1365, 7.13645.round(4, half: :up))
assert_equal(7.1365, 7.1364501.round(4, half: :up))
assert_equal(7.1364, 7.1364499.round(4, half: :up))
assert_equal(-7.1365, -7.13645.round(4, half: :up))
assert_equal(-7.1365, -7.1364501.round(4, half: :up))
assert_equal(-7.1364, -7.1364499.round(4, half: :up))
end
def test_Float
assert_in_delta(0.125, Float("0.1_2_5"), 0.00001)
assert_in_delta(0.125, "0.1_2_5__".to_f, 0.00001)

View file

@ -187,13 +187,49 @@ class TestInteger < Test::Unit::TestCase
assert_int_equal(11110, 11111.round(-1))
assert_int_equal(11100, 11111.round(-2))
assert_int_equal(+200, +249.round(-2))
assert_int_equal(+300, +250.round(-2))
assert_int_equal(+200, +250.round(-2))
assert_int_equal(-200, -249.round(-2))
assert_int_equal(-300, -250.round(-2))
assert_int_equal(+30 * 10**70, (+25 * 10**70).round(-71))
assert_int_equal(-30 * 10**70, (-25 * 10**70).round(-71))
assert_int_equal(+200, +249.round(-2, half: :even))
assert_int_equal(+200, +250.round(-2, half: :even))
assert_int_equal(+300, +349.round(-2, half: :even))
assert_int_equal(+400, +350.round(-2, half: :even))
assert_int_equal(+200, +249.round(-2, half: :up))
assert_int_equal(+300, +250.round(-2, half: :up))
assert_int_equal(+300, +349.round(-2, half: :up))
assert_int_equal(+400, +350.round(-2, half: :up))
assert_int_equal(-200, -250.round(-2))
assert_int_equal(-200, -249.round(-2, half: :even))
assert_int_equal(-200, -250.round(-2, half: :even))
assert_int_equal(-300, -349.round(-2, half: :even))
assert_int_equal(-400, -350.round(-2, half: :even))
assert_int_equal(-200, -249.round(-2, half: :up))
assert_int_equal(-300, -250.round(-2, half: :up))
assert_int_equal(-300, -349.round(-2, half: :up))
assert_int_equal(-400, -350.round(-2, half: :up))
assert_int_equal(+20 * 10**70, (+25 * 10**70).round(-71))
assert_int_equal(-20 * 10**70, (-25 * 10**70).round(-71))
assert_int_equal(+20 * 10**70, (+25 * 10**70 - 1).round(-71))
assert_int_equal(-20 * 10**70, (-25 * 10**70 + 1).round(-71))
assert_int_equal(+40 * 10**70, (+35 * 10**70).round(-71))
assert_int_equal(-40 * 10**70, (-35 * 10**70).round(-71))
assert_int_equal(+30 * 10**70, (+35 * 10**70 - 1).round(-71))
assert_int_equal(-30 * 10**70, (-35 * 10**70 + 1).round(-71))
assert_int_equal(+20 * 10**70, (+25 * 10**70).round(-71, half: :even))
assert_int_equal(-20 * 10**70, (-25 * 10**70).round(-71, half: :even))
assert_int_equal(+20 * 10**70, (+25 * 10**70 - 1).round(-71, half: :even))
assert_int_equal(-20 * 10**70, (-25 * 10**70 + 1).round(-71, half: :even))
assert_int_equal(+40 * 10**70, (+35 * 10**70).round(-71, half: :even))
assert_int_equal(-40 * 10**70, (-35 * 10**70).round(-71, half: :even))
assert_int_equal(+30 * 10**70, (+35 * 10**70 - 1).round(-71, half: :even))
assert_int_equal(-30 * 10**70, (-35 * 10**70 + 1).round(-71, half: :even))
assert_int_equal(+30 * 10**70, (+25 * 10**70).round(-71, half: :up))
assert_int_equal(-30 * 10**70, (-25 * 10**70).round(-71, half: :up))
assert_int_equal(+20 * 10**70, (+25 * 10**70 - 1).round(-71, half: :up))
assert_int_equal(-20 * 10**70, (-25 * 10**70 + 1).round(-71, half: :up))
assert_int_equal(+40 * 10**70, (+35 * 10**70).round(-71, half: :up))
assert_int_equal(-40 * 10**70, (-35 * 10**70).round(-71, half: :up))
assert_int_equal(+30 * 10**70, (+35 * 10**70 - 1).round(-71, half: :up))
assert_int_equal(-30 * 10**70, (-35 * 10**70 + 1).round(-71, half: :up))
assert_int_equal(1111_1111_1111_1111_1111_1111_1111_1110, 1111_1111_1111_1111_1111_1111_1111_1111.round(-1))
assert_int_equal(-1111_1111_1111_1111_1111_1111_1111_1110, (-1111_1111_1111_1111_1111_1111_1111_1111).round(-1))

View file

@ -597,17 +597,20 @@ class Rational_Test < Test::Unit::TestCase
end
def test_trunc
[[Rational(13, 5), [ 2, 3, 2, 3]], # 2.6
[Rational(5, 2), [ 2, 3, 2, 3]], # 2.5
[Rational(12, 5), [ 2, 3, 2, 2]], # 2.4
[Rational(-12,5), [-3, -2, -2, -2]], # -2.4
[Rational(-5, 2), [-3, -2, -2, -3]], # -2.5
[Rational(-13, 5), [-3, -2, -2, -3]], # -2.6
[[Rational(13, 5), [ 2, 3, 2, 3, 3, 3]], # 2.6
[Rational(5, 2), [ 2, 3, 2, 2, 2, 3]], # 2.5
[Rational(12, 5), [ 2, 3, 2, 2, 2, 2]], # 2.4
[Rational(-12,5), [-3, -2, -2, -2, -2, -2]], # -2.4
[Rational(-5, 2), [-3, -2, -2, -2, -2, -3]], # -2.5
[Rational(-13, 5), [-3, -2, -2, -3, -3, -3]], # -2.6
].each do |i, a|
assert_equal(a[0], i.floor)
assert_equal(a[1], i.ceil)
assert_equal(a[2], i.truncate)
assert_equal(a[3], i.round)
s = proc {i.inspect}
assert_equal(a[0], i.floor, s)
assert_equal(a[1], i.ceil, s)
assert_equal(a[2], i.truncate, s)
assert_equal(a[3], i.round, s)
assert_equal(a[4], i.round(half: :even), s)
assert_equal(a[5], i.round(half: :up), s)
end
end

View file

@ -96,17 +96,17 @@ class TestMathn < Test::Unit::TestCase
def test_round
assert_separately(%w[-rmathn], <<-EOS, ignore_stderr: true)
assert_equal( 3, ( 13/5).round)
assert_equal( 3, ( 5/2).round)
assert_equal( 2, ( 5/2).round)
assert_equal( 2, ( 12/5).round)
assert_equal(-2, (-12/5).round)
assert_equal(-3, ( -5/2).round)
assert_equal(-2, ( -5/2).round)
assert_equal(-3, (-13/5).round)
assert_equal( 3, ( 13/5).round(0))
assert_equal( 3, ( 5/2).round(0))
assert_equal( 2, ( 5/2).round(0))
assert_equal( 2, ( 12/5).round(0))
assert_equal(-2, (-12/5).round(0))
assert_equal(-3, ( -5/2).round(0))
assert_equal(-2, ( -5/2).round(0))
assert_equal(-3, (-13/5).round(0))
assert_equal(( 13/5), ( 13/5).round(2))
@ -115,6 +115,48 @@ class TestMathn < Test::Unit::TestCase
assert_equal((-12/5), (-12/5).round(2))
assert_equal(( -5/2), ( -5/2).round(2))
assert_equal((-13/5), (-13/5).round(2))
assert_equal( 3, ( 13/5).round(half: :even))
assert_equal( 2, ( 5/2).round(half: :even))
assert_equal( 2, ( 12/5).round(half: :even))
assert_equal(-2, (-12/5).round(half: :even))
assert_equal(-2, ( -5/2).round(half: :even))
assert_equal(-3, (-13/5).round(half: :even))
assert_equal( 3, ( 13/5).round(0, half: :even))
assert_equal( 2, ( 5/2).round(0, half: :even))
assert_equal( 2, ( 12/5).round(0, half: :even))
assert_equal(-2, (-12/5).round(0, half: :even))
assert_equal(-2, ( -5/2).round(0, half: :even))
assert_equal(-3, (-13/5).round(0, half: :even))
assert_equal(( 13/5), ( 13/5).round(2, half: :even))
assert_equal(( 5/2), ( 5/2).round(2, half: :even))
assert_equal(( 12/5), ( 12/5).round(2, half: :even))
assert_equal((-12/5), (-12/5).round(2, half: :even))
assert_equal(( -5/2), ( -5/2).round(2, half: :even))
assert_equal((-13/5), (-13/5).round(2, half: :even))
assert_equal( 3, ( 13/5).round(half: :up))
assert_equal( 3, ( 5/2).round(half: :up))
assert_equal( 2, ( 12/5).round(half: :up))
assert_equal(-2, (-12/5).round(half: :up))
assert_equal(-3, ( -5/2).round(half: :up))
assert_equal(-3, (-13/5).round(half: :up))
assert_equal( 3, ( 13/5).round(0, half: :up))
assert_equal( 3, ( 5/2).round(0, half: :up))
assert_equal( 2, ( 12/5).round(0, half: :up))
assert_equal(-2, (-12/5).round(0, half: :up))
assert_equal(-3, ( -5/2).round(0, half: :up))
assert_equal(-3, (-13/5).round(0, half: :up))
assert_equal(( 13/5), ( 13/5).round(2, half: :up))
assert_equal(( 5/2), ( 5/2).round(2, half: :up))
assert_equal(( 12/5), ( 12/5).round(2, half: :up))
assert_equal((-12/5), (-12/5).round(2, half: :up))
assert_equal(( -5/2), ( -5/2).round(2, half: :up))
assert_equal((-13/5), (-13/5).round(2, half: :up))
EOS
end
end