Repository: goby-lang/goby (go)

Issue:
VM Float API Implementation
Referenced from the Ruby Ver. 2.4.1. Float data type API:

- [ ] Constant `Float::INFINITE` stands for the number is infinite
- [ ] Constant `Float::NAN` stands for "Not a Number"

- [ ] `Float#modulo` Synonym to the `%` operator (Get the quotient from the divide operation)
- [ ] `Float#quo` Synonym to the `/` operator (Get the quotient from the divide operation)
- [ ] `Float#abs` Return positive number of the float. "Formally as the distance between 0 to the number of the float in 1-dimensional axis ..."
- [ ] `Float#angle` (and `Float#arg`) If positive -> return 0; else -> return PI
- [ ] `Float#ceil` Returns smallest number greater than or equal to the receiver
- [ ] `Float#floor` Returns largest number smaller than or equal to the receiver
- [ ] `Float#round` Rounds float to a given precision in decimal digits (default 0 digits). Precision may be negative. Returns a floating point number when `n` digits is more than zero.
- [ ] `Float#finite?` Returns `true` if receiver isn't  equal to `Float::INFINITE`
- [ ] `Float#infinite?` Returns `true` if receiver is equal to `Float::INFINITE`
- [ ] `Float#divmod` Returns array which is in format of [ (Integer) Quotient, (Float) Remainder ]
- [ ] `Float#nan?` Returns `true` if receiver is equal to `Float::NAN`
- [ ] `Float#positive?` Returns `true` if float number is larger than `0.0`
- [ ] `Float#negative?` Returns `true` if float number is less than `0.0`
- [ ] `Float#zero?` Returns `true` if float number is equal to `0.0`
- [ ] `Float#rationalize` Convert to something called `Rational` data type (Currently not supported but from my point of view, it is worth being addressed in this issue)

Pull request:
Float APIs - Part1
This PR is for continuing the work at #615 (thanks @SD10 for previous works)

It implements:
- [x] `Float#abs` Return positive number of the float. "Formally as the distance between 0 to the number of the float in 1-dimensional axis ..."
- [x] `Float#ceil` Returns the smallest number greater than or equal to the receiver
- [x] `Float#floor` Returns the largest number smaller than or equal to the receiver
- [x] `Float#round` Rounds float to a given precision in decimal digits (default 0 digits). Precision may be negative. Returns a floating-point number when `n` digits are more than zero.

- [x] `Float#positive?` Returns `true` if float number is larger than `0.0`
- [x] `Float#negative?` Returns `true` if float number is less than `0.0`
- [x] `Float#zero?` Returns `true` if float number is equal to `0.0`


Patch:
diff --git a/vm/float.go b/vm/float.go
index 6c7cb5a..8bd4392 100644
--- a/vm/float.go
+++ b/vm/float.go
@@ -317,7 +317,157 @@ var builtinFloatInstanceMethods = []*BuiltinMethodObject{
 		Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
 			r := receiver.(*FloatObject)
 			return t.vm.initGoObject(&r.value)
+		},
+	},
+	{
+		// Returns the Float as a positive value.
+		//
+		// ```Ruby
+		// -34.56.abs # => 34.56
+		// 34.56.abs # => 34.56
+		// ```
+		// @return [Float]
+		Name: "abs",
+		Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
+			if len(args) != 0 {
+				return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 0 argument. got=%v", strconv.Itoa(len(args)))
+			}
+			r := receiver.(*FloatObject)
+			result := math.Abs(r.value)
+			return t.vm.initFloatObject(result)
+		},
+	},
+	{
+		// Returns the smallest Integer greater than or equal to self.
+		//
+		// ```Ruby
+		// 1.2.ceil  # => 2
+		// 2.ceil    # => 2
+		// -1.2.ceil # => -1
+		// -2.ceil   # => -2
+		// ```
+		// @return [Integer]
+		Name: "ceil",
+		Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
+			// TODO: Make ceil accept arguments
+			if len(args) != 0 {
+				return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 0 argument. got=%v", strconv.Itoa(len(args)))
+			}
+			r := receiver.(*FloatObject)
+			result := math.Ceil(r.value)
+			newInt := t.vm.InitIntegerObject(int(result))
+			newInt.flag = i
+			return newInt
+		},
+	},
+	{
+		// Returns the largest Integer less than or equal to self.
+		//
+		// ```Ruby
+		// 1.2.floor  # => 1
+		// 2.0.floor  # => 2
+		// -1.2.floor # => -2
+		// -2.0.floor # => -2
+		// ```
+		// @return [Integer]
+		Name: "floor",
+		Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
+			// TODO: Make floor accept arguments
+			if len(args) != 0 {
+				return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 0 argument. got=%v", strconv.Itoa(len(args)))
+			}
+			r := receiver.(*FloatObject)
+			result := math.Floor(r.value)
+			newInt := t.vm.InitIntegerObject(int(result))
+			newInt.flag = i
+			return newInt
+		},
+	},
+	{
+		// Returns true if Float is equal to 0.0
+		//
+		// ```Ruby
+		// 0.0.zero? # => true
+		// 1.0.zero? # => false
+		// ```
+		// @return [Boolean]
+		Name: "zero?",
+		Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
+			if len(args) != 0 {
+				return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 0 argument. got=%v", strconv.Itoa(len(args)))
+			}
+			r := receiver.(*FloatObject)
+			return toBooleanObject(r.value == 0.0)
+		},
+	},
+	{
+		// Returns true if Float is larger than 0.0
+		//
+		// ```Ruby
+		// -1.0.positive? # => false
+		// 0.0.positive?  # => false
+		// 1.0.positive?  # => true
+		// ```
+		// @return [Boolean]
+		Name: "positive?",
+		Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
+			if len(args) != 0 {
+				return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 0 argument. got=%v", strconv.Itoa(len(args)))
+			}
+			r := receiver.(*FloatObject)
+			return toBooleanObject(r.value > 0.0)
+		},
+	},
+	{
+		// Returns true if Float is less than 0.0
+		//
+		// ```Ruby
+		// -1.0.negative? # => true
+		// 0.0.negative?  # => false
+		// 1.0.negative?  # => false
+		// ```
+		// @return [Boolean]
+		Name: "negative?",
+		Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
+			if len(args) != 0 {
+				return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 0 argument. got=%v", strconv.Itoa(len(args)))
+			}
+			r := receiver.(*FloatObject)
+			return toBooleanObject(r.value < 0.0)
+		},
+	},
+	{
+		//  Rounds float to a given precision in decimal digits (default 0 digits)
+		//
+		// ```Ruby
+		// 1.115.round  # => 1
+		// 1.115.round(1)  # => 1.1
+		// 1.115.round(2)  # => 1.12
+		// -1.115.round  # => -1
+		// -1.115.round(1)  # => -1.1
+		// -1.115.round(2)  # => -1.12
+		// ```
+		// @return [Integer]
+		Name: "round",
+		Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
+			var precision int
+
+			if len(args) > 1 {
+				return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Expect 0 or 1 argument. got=%v", strconv.Itoa(len(args)))
+			} else if len(args) == 1 {
+				int, ok := args[0].(*IntegerObject)
+
+				if !ok {
+					return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.IntegerClass, args[0].Class().Name)
+				}
+
+				precision = int.value
+			}
+
+			f := receiver.(*FloatObject).floatValue()
+			n := math.Pow10(precision)
 
+			return t.vm.initFloatObject(math.Round(f*n) / n)
 		},
 	},
 }


Test patch:
diff --git a/vm/float_test.go b/vm/float_test.go
index b82b2e3..1c3e0d7 100644
--- a/vm/float_test.go
+++ b/vm/float_test.go
@@ -338,6 +338,26 @@ func TestFloatNumberOfDigit(t *testing.T) {
 	}
 }
 
+// API tests
+
+func TestFloatAbs(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected interface{}
+	}{
+		{"34.56.abs", 34.56},
+		{"-34.56.abs", 34.56},
+	}
+
+	for i, tt := range tests {
+		v := initTestVM()
+		evaluated := v.testEval(t, tt.input, getFilename())
+		VerifyExpected(t, i, evaluated, tt.expected)
+		v.checkCFP(t, i, 0)
+		v.checkSP(t, i, 1)
+	}
+}
+
 func TestFloatMinusZero(t *testing.T) {
 	tests := []struct {
 		input    string
@@ -369,6 +389,26 @@ func TestFloatMinusZero(t *testing.T) {
 	}
 }
 
+func TestFloatCeil(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected interface{}
+	}{
+		{"1.2.ceil", 2},
+		{"2.0.ceil", 2},
+		{"-1.2.ceil", -1},
+		{"-2.0.ceil", -2},
+	}
+
+	for i, tt := range tests {
+		v := initTestVM()
+		evaluated := v.testEval(t, tt.input, getFilename())
+		VerifyExpected(t, i, evaluated, tt.expected)
+		v.checkCFP(t, i, 0)
+		v.checkSP(t, i, 1)
+	}
+}
+
 func TestFloatDupMethod(t *testing.T) {
 	tests := []struct {
 		input    string
@@ -385,3 +425,102 @@ func TestFloatDupMethod(t *testing.T) {
 		v.checkSP(t, i, 1)
 	}
 }
+func TestFloatFloor(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected interface{}
+	}{
+		{"1.2.floor", 1},
+		{"2.0.floor", 2},
+		{"-1.2.floor", -2},
+		{"-2.0.floor", -2},
+	}
+
+	for i, tt := range tests {
+		v := initTestVM()
+		evaluated := v.testEval(t, tt.input, getFilename())
+		VerifyExpected(t, i, evaluated, tt.expected)
+		v.checkCFP(t, i, 0)
+		v.checkSP(t, i, 1)
+	}
+}
+
+func TestFloatNegative(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected interface{}
+	}{
+		{"-1.0.negative?", true},
+		{"0.0.negative?", false},
+		{"1.0.negative?", false},
+	}
+
+	for i, tt := range tests {
+		v := initTestVM()
+		evaluated := v.testEval(t, tt.input, getFilename())
+		VerifyExpected(t, i, evaluated, tt.expected)
+		v.checkCFP(t, i, 0)
+		v.checkSP(t, i, 1)
+	}
+}
+
+func TestFloatPositive(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected interface{}
+	}{
+		{"-1.0.positive?", false},
+		{"0.0.positive?", false},
+		{"1.0.positive?", true},
+	}
+
+	for i, tt := range tests {
+		v := initTestVM()
+		evaluated := v.testEval(t, tt.input, getFilename())
+		VerifyExpected(t, i, evaluated, tt.expected)
+		v.checkCFP(t, i, 0)
+		v.checkSP(t, i, 1)
+	}
+}
+
+func TestFloatRound(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected interface{}
+	}{
+		{"1.115.round", 1.0},
+		{"1.115.round(1)", 1.1},
+		{"1.115.round(2)", 1.12},
+		{"-1.115.round", -1.0},
+		{"-1.115.round(1)", -1.1},
+		{"-1.115.round(2)", -1.12},
+		{"1.115.round(-1)", 0.0},
+		{"-1.115.round(-1)", 0.0},
+	}
+
+	for i, tt := range tests {
+		v := initTestVM()
+		evaluated := v.testEval(t, tt.input, getFilename())
+		VerifyExpected(t, i, evaluated, tt.expected)
+		v.checkCFP(t, i, 0)
+		v.checkSP(t, i, 1)
+	}
+}
+
+func TestFloatZero(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected interface{}
+	}{
+		{"0.0.zero?", true},
+		{"1.0.zero?", false},
+	}
+
+	for i, tt := range tests {
+		v := initTestVM()
+		evaluated := v.testEval(t, tt.input, getFilename())
+		VerifyExpected(t, i, evaluated, tt.expected)
+		v.checkCFP(t, i, 0)
+		v.checkSP(t, i, 1)
+	}
+}