Number operations

Hi ,

I have a scenario where we have two numbers 8234 and 5.
I want to compare the number digit by digit with 5 and insert at the place where it can fit (85234- > 2 is smaller than 5 and so inserted before that place)
For this, I tried to convert like below , but facing prob

var num int= -6285
var digit int32 = 5
str := strconv.Itoa(num)
var finalData strings.Builder
for _, j := range str{
	
}

println(finalData.String())

can you help me to extract the number from beginning and compare with 5 and insert at the right place and return as int.
problem i was facing is in conversion of string to int

2 Likes

https://play.golang.org/

var num int = -6285
str := strconv.Itoa(num)
var finalData strings.Builder
for _, j := range str {
	// Logic of 5 here
	finalData.WriteString(string(j))
}
fmt.Println(finalData.String())
2 Likes

Hi Gonzalo,

problem is that, I have to compare every digit with 5
but j value will not be actual value , example: println(j) j print 54 for digit 6 and 50 for 2,etc.,
How to print as 6 instead 45

Thank you.

1 Like

You can print with %c, playground

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	var num int = -6285
	str := strconv.Itoa(num)
	var finalData strings.Builder
	for _, j := range str {
		// Logic of 5 here
		fmt.Printf("%c \n", j)
		finalData.WriteString(string(j))
	}
	fmt.Println(finalData.String())
}
1 Like

Hi Gonzalo,

yes , we can print.
My requirement is that I have to compare each digit with 5

for _, j := range str {
if j > 5{
finalData.WriteString(“5”)
}
finalData.WriteString(string(j))
}

since j value will converted (i.e., 6 will be 54). I cannot actually compare.

1 Like

I am not sure if i understood you right but if i did, here it is :slight_smile:


import (
	"fmt"
)

func main() {
	var num int = 8234
	helper := 10
	finalNumber:=5
	
	for num > 0 {
		currentNumber := num % 10
		if currentNumber < 5 {
			finalNumber = finalNumber + currentNumber * helper
		} else {
			finalNumber = finalNumber * 10 + currentNumber
		}
		helper *= 10
		num /= 10
	}
	
	fmt.Println(finalNumber)
}
1 Like

An easy way to do this is to convert the digit into a rune as you did for the number. It has to be a rune because we want to compare it with j which is a rune.

d := rune(strconv.Itoa(digit)[0])

You then may compare runes. We use the property that order of rune values is the same as for digit values. Rune comparison yields the same result as digit comparison. This avoids the need to convert runes back to digits.

Here is the solution : https://play.golang.org/p/S_IvZAUR7CQ

You were not that far from it.

2 Likes

Instead of converting to a string, don’t you think it’s better do use / and % to create a slice of digits?

Can’t give you an example though, as I’m on mobile currently.

3 Likes

The difficulty is that we need to isolate each digit starting from the most significant digit. This is not trivial with modulo operations. The conversion to string yields a simpler solution because we iterate over digits.
We could use bytes instead of runes, because a digit is encoded in a single byte.

1 Like

So what’s the problem? Iterating a slice of int ist Not that different from iterating a slice of rune. You might need to reverse first or just iterate from “the back”.

String conversion does probably do exactly what I am proposing plus a lot of extra work, so you really should give it a try…

I’d really like to show some code, but I’m not sure if I will be back on a computer before the end of the week.

1 Like

The trick to this question is try to explore around a bit to get back your numerical properties. One easy way is to understand how byte works in Go: Go Playground - The Go Programming Language

Notice that the string-type digits are in a numerical representations:

//     "0"   "1"   "2"    "3"   "4"  "5"   "6"   "7"   "8"   "9"   
[]byte{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}

Hence, we still maintain the numerical properties for mathematical calculations later.

fmt.Printf("%#v\n", s[5] < s[0])  // "5" < "0" --> false
fmt.Printf("%#v\n", s[5] > s[0])  // "5" > "0" -->	true
fmt.Printf("%#v\n", s[5] == s[5])  // "5" == "5" --> true
fmt.Printf("%#v\n", s[5] == s[7])  // "5" == "7" --> false
fmt.Printf("%#v\n", s[5] < s[7])  // "5" < "7" --> true

So it is better to drop into byte data-type.


So now we know we can operate on byte values, here a proposal:

package main

import (
	"fmt"
	"strconv"
)


func inject(value int, digit uint) string {
	v := []byte(strconv.Itoa(value))
	x := []byte(strconv.Itoa(int(digit)))[0]

	p := 0
	l := len(v)
	for i, _ := range v {
		if i == l-1 {
			p = l
			break
		}

		if v[i] <= x {
			break
		}
		
		p++
	}
	
	// 4. Inject accordingly
	out := append(v[:p], append([]byte{x}, v[p:]...)...)
	
	return string(out)
}


func main() {
	x := inject(8234, 2)
	fmt.Printf("Answer: %v\n", x)
}

Keep in mind that I still do not understand your problem clearly because you try to use both positive and negative numbers and their digits positioning are different respectively. However, since you got back your mathematical properties after strings conversion, I believe you can now work it out accordingly.

Also, it’s better to guard the given “digit” input to ensure it is 0-9 and no others. strconv also convert negative sign as “-” so you need to guard against that as well (by removing the “-” sign from your slice list).

Mathematically, division and modulo is possible but I would avoid it since looping division is costly on cpu processing. After-all, we are looking at digit positioning, not heavy mathematical calculations.

2 Likes

Hi,

I have to extract digit by digit from begining.

1 Like

Thank you. It works my use case.

1 Like

So you suggest to convert the int number into a slice of digits. That could work. The sign would also have to be taken care of.
I think that converting to a string yields the most simple code since it is done in a single function call. The slicing of the number and reverse of the slice would require much more instructions. The code would be more complex. Check my implementation.

1 Like

My code did not handle the case when the digit is to be inserted after the last digit of the number. To handle this case, I need to slightly modify the code I gave. Here is the code that now works with this particular use case: https://play.golang.org/p/JTwtxTfElvV

2 Likes

thank you

1 Like

Your justification for using string operations (which, by the way, are far more costly than mathematics) is that looping division is costly on the processor. Translation: mathematics is costly, so lets use an even less efficient and more costly solution to solve the problem. (Right?)

I would recommend that the OP learn the exact kind of mathematics you are wanting to avoid. The statement:

is absurd. The first part of your post – discussing the byte type and all – is just as inefficient and as costly as string operations, excluding the extra unicode code point lookups. I (too) had trouble understanding the OPs question at first, but its not that hard for me to grasp. He wants to place a number in its proper place in an array of numbers that he generates. No byte manipulation is required. This is, in essence, what you are telling the op to do:

Avoid mathematics. Mathematics are bad (especially in loops). Alternatively, use bytes (and thereby over-complicate the algorithm).

That doesn’t make sense because mathematics is crucial for a processor to even work. The most primitive operation a processor can do are the various mathematical operations. Processors are manufactured to do mathematics; logic then tells us that, since they’re practically manufactured for mathematics, they’re bound to be excellent at it. Every other operation that a processor performs uses some kind of mathematics under the hood.

In essence, if the OP wants to accomplish the task in the way that you are suggesting, they have two options:

  1. Use bytes:
    a. Convert the string to a byte slice.
    b. loop through the slice, stringify the byte, parse the byte as an integer, and then do comparison.
    c. After the loop is done, reassemble the new integer.
  2. Use strings:
    a. Convert the number to a slice of strings (even worse).
    b. Loop through the slice of strings, parse each string as an integer, and compare that string with the number 5.
    c. When loop is done, loop through the string and reassemble the number.
    d. Parse the newly stringified number back into an actual integer.

Both operations are bad and about 30-100 times more costly than simply using division and modulous. Here’s how you could get the initial number into an array (discarding sign):

package main

import "math"

// ...

  n := math.abs(float64(8234))
  length := int64(math.floor(math.log10(n))) + 1
  r := make([]int64, 0, length)
  for int64(n) != 0 {
    r = append(r, int64(n % 10))
    n /= 10
  }

You then would have a nice list of integers (this method does work). Of course, you could completely get rid of the log10 call by using bit manipulations, but the OP can ask for that.

Note: when I tested this code in Python it reversed the number ordering so I had 4, 3, 2, 8, and then it gave me 324 0s. So you would then just need to get rid of the extra zeros, which could be done by using slices.

I’m sorry if I was a bit harsh, but please, don’t avoid mathematics just because the string method looks “simpler”. Any programmer should be writing efficient and fast code, not slow code. If that means using a lot of math in the code, then so be it. (Plus, imagine how slow the OPs code would be if they used the strings method and did that with thousands to millions of numbers? We don’t know their situation or where they’re applying this code, so we should assume that they want to do it in a fast and efficient method, and provide them with that method.) Whoever was thinking about division and modulous too, good work – keep it up. To the rest of you, please stop using strings for all your number processing. That not only significantly increases the complexity because you then need to parse integers over and over but its also more costly than just a few mathematical operations. Strings are nice for number processsing – say, on dates, times, etc. – but using them for something like this is just ridiculous.

2 Likes

Since when I discourage OP to use math? The whole point is to recover the mathematical properties back after the string conversion and I pointing out the byte-numbers representation table that OP can operate with like normal numbers?

Also, since when I said division and modulo must be completely avoided like a religious kind of stuff? The point there is to use sensibly and sparingly. As I said, at the very beginning: the trick is to explore around.

In normal application, we always simplify the mathematics towards favoring addition, subtraction, and multiplication whenever possible before jumping into writing a program based on it.

Looping division is something I tend to be very careful with because the operations underneath it not as simple as placing a / or % here, especially in multiple ops situation. Otherwise, fast inverse square root magic number would not appear won’t appear in the past.

The first word makes sense, not the last 2. The priority one is always given to writing readable and maintainable codes to solve the problem, not worrying about performance at first. Leave those to the compiler.

Otherwise, we are already writing assembly codes here (which makes zero sense from time profits).

@Gowtham_Girithar , @Ethindp’s slicing is much cleaner, and less complex than the one I presented. This should ring some bells.


At this point, there many ways to approach this number placement problems depending on your point of view:

  1. The mathematical division way (@NobbZ and @Ethindp and commonly everybody approaching mathematics this way)
  2. The string & bytes way (the one I expanded, and most people will first think this as the first way)
  3. The runes way (something new I learnt after @Christophe_Meessen)
  4. Bit masking and unsigned numbers manipulations (my preferred way and is not shown here due to the amount of complexities it contains)
  5. Fill in more when you have better idea.

If you have much better way, please by all means show it up.That’s the whole point of discussing on forum. Also, clam down and read carefully.

2 Likes

And again, I will continue to assert that the mathematical operations and bit manipulation ways are the two best for a situation like this. I’m not really sure where you heard that programs are written with addition, subtraction and multiplication as much as possible before using division, but this is incredibly absurd and makes no logical sense. This is not the 1990s. We are not programming on 386s or 486s. We are programming on core i5s, i7s, i9s, Zeons, etc., where the cost of division is inconsequential. The byte/strings method, however, is consequential. I indicated that you pretty much said to avoid mathematics because that was definitely the impression I got when reading your post.
You bring up the fact that you tend to avoid looping division. Not really sure why (again, the cost of division – and by extension the cost of looping division – is negligible) and I will say that the five mathematical operations – addition, subtraction, multiplication, division, modulous – are the most widely-used mathematical operations on the planet next to bit manipulation operations (which may be way down the list) when it comes to computers. There is no reason to avoid it under any circumstances. It may not be implemented like the other operations are but so what? The cost is so negligible that its non-existent. As an example of how inconsequential division is, searching the Linux kernel source tree yields over a thousand instances of the “/=” operator and approximately 524 instances of the “%=” operator. If even the Linux developers aren’t caring about the cost of division, I would say your being overly-cautious and need to reevaluate your viewpoint.
Finally, you bring up performance. Yes, making readable code is a good priority to have, but writing fast code is also another good one. Just because your compiler can generate ultra-fast code does not suddenly mean you can ignore that priority. Your analogy to assembly doesn’t even make sense because complicated bit manipulations make hard to read code and yet you usually don’t write a line of assembly when doing them. So, your priorities should be something like this when writing code:

  • Write fast and efficient code whenever possible. If you don’t know a fast/efficient solution, go with one that may be potentially slow until you find one. You can easily write fast and efficient code without making it look like magic or making it impossible for anyone but you to understand.
  • Write readable and easily maintainable code.
  • Write code that is secure.

So, in sum:

  • Your cautiousness with division is unfounded and really sounds like paranoia. If division of any kind – or just looping division – was as costly as your implying, then every programming book/article/whatnot on advanced programming topics out there would contain warnings to avoid it as much as possible.
  • Your analogy to assembly makes no sense because you can make practically unreadable but ultra-fast code without writing a line of assembly. Hell, writing inline assembly can sometimes make your program slower, not faster.
2 Likes

There are 2 parts I want to address out:


1st Part: Back to Topic

  1. To OP @Gowtham_Girithar, choose the one that makes sense and can explain from your side. After optimizing and benchmark some algorithms, here are the best codes between byMath and byByte I can come out with. Both approaches are making sense; the only things left are whether it fulfills the objective (question requirement) and how do you understand the algorithm (one is by mathematics, one is by text encoding).

  2. I’m hiding the code just in case you want to try things out yourself. View it by clicking on the hiding block.

  3. There is a benchmark result inside for you to visualize the performance and judge from there using my current system. Performance results are nonsense by its own without running it against a hypothesis the benchmark on your system and compare. That’s why I would avoid the topic for that.

  4. Negative numbers and positive numbers approaches are solely depends on you. From your topic, your positive number is inserted when the RHS is lower than the subject. Hence, the code’s insertion is based on that assumption. If that’s your algorithm, then negative number is the opposite (mainly because negative number is always lower than positive number on number point system). Again, since you did not specify, decision is upto yours. Currently, I’m commenting out negSign in case you need it.

  5. For exploration, do explore into math package like @Ethindp pointed . You will know why I came out from there later. For simple problem as such, you do not need to get yourself into floating-point data type.

  6. When I say complicated division, I meant by the divisor requires complex math like log, exponent, etc like

Code
package main

import (
	"fmt"
	"strconv"
)

const (
	target  = 8234
	subject = 2
)

func byMath(t int, s uint) string {
	// negSign := false

	// guard negative number
	if t < 0 {
		t *= -1
		//negSign = true
	}

	// guard against 0-9
	if s > 9 {
		panic("s should be digit from 0-9. Fix me before compile.\n")
	}

	base := 10
	div := 1
	v := []int{}
	for {
		r := (t / div) % base
		if r == 0 {
			break
		}
		v = append([]int{r}, v...)
		div *= base
	}

	out := ""
	inserted := false
	for _, d := range v {
		if d <= int(s) && !inserted {
			out += strconv.Itoa(int(s))
			inserted = true
		}
		out += strconv.Itoa(d)
	}
	return out
}

func byByte(t int, s uint) string {
	var v []byte
	var x byte

	// negSign := false

	// guard negative number
	if t < 0 {
		t *= -1
		//negSign = true
	}

	// guard against 0-9
	if s > 9 {
		panic("s should be digit from 0-9. Fix me before compile.\n")
	}

	v = []byte(strconv.Itoa(t))

	x = []byte(strconv.Itoa(int(s)))[0]

	p := 0
	l := len(v)
	for i := range v {
		if i == l-1 {
			p = 1
			break
		}

		if v[i] <= x {
			break
		}

		p++
	}

	out := append(v[:p], append([]byte{x}, v[p:]...)...)
	return string(out)
}

func main() {
	fmt.Printf("byMath: %v\n", byMath(target, subject))
	fmt.Printf("byByte: %v\n", byByte(target, subject))
}

Output:

Output:
byMath: 82234
byByte: 82234

Benchmark:
goos: linux
goarch: amd64
pkg: gosandbox
BenchmarkByMath-8        2915108               449 ns/op
BenchmarkByBytes-8      12081392               100 ns/op
PASS
ok      gosandbox       3.043s
Bechmark Codes
package main

import (
	"testing"
)

var result string

func BenchmarkByMath(b *testing.B) {
	var r string
	for n := 0; n < b.N; n++ {
		r = byMath(n, 2)
	}
	result = r
}

func BenchmarkByBytes(b *testing.B) {
	var r string
	for n := 0; n < b.N; n++ {
		r = byByte(n, 2)
	}
	result = r
}


The 2nd Part: Ethin Probst Division Arguments

1. the Keyword “Complicated”

There are 2 kinds of “complicated” perceptions here. From @Ethindp perceptive, it’s about more codes. From my perspective, it’s about readable, and maintainable codes that does not hide things. Hence, it’s a matter of point of views (and preference).

I prioritizes solving problem first, performance later. The rationale for me is simple: I’m not interested in getting into highway to hell; I would make sure the direction is correct before proceeding even as slow as country road.

2. Argument about Division

Mathematically, it is proven that division requires more efforts to reach result. This is the only reason why division has a single rule “not to divide by 0” and the result is not infinity but an error/not-a-number.

This is one simple program that benchmark all basic mathematical operations on Go compiler:

package main

import (
	"fmt"
)

const (
	subject = 10
	target  = 2
)

func divideQAndR(x, y int) (r1 int, r2 int) {
	r1 = x / y
	r2 = x % y
	return r1, r2
}

func divideRemainderOnly(x, y int) (r1 int, r2 int) {
	r1 = x % y
	return r1, r2
}

func divideQuotientOnly(x, y int) (r1 int, r2 int) {
	r1 = x / y
	return r1, r2
}

func multiply(x, y int) (r1 int, r2 int) {
	r1 = x * y
	return r1, r2
}

func subtract(x, y int) (r1 int, r2 int) {
	r1 = x - y
	return r1, r2
}

func add(x, y int) (r1 int, r2 int) {
	r1 = x + y
	return r1, r2
}

func main() {
	r1, r2 := add(subject, target)
	fmt.Printf("Add R1:%v R2:%v \n", r1, r2)

	r1, r2 = subtract(subject, target)
	fmt.Printf("Subtract R1:%v R2:%v \n", r1, r2)

	r1, r2 = multiply(subject, target)
	fmt.Printf("Multiply R1:%v R2:%v \n", r1, r2)

	r1, r2 = divideRemainderOnly(subject, target)
	fmt.Printf("DivideRemainderOnly R1:%v R2:%v \n", r1, r2)

	r1, r2 = divideQuotientOnly(subject, target)
	fmt.Printf("DivideQuotientOnly R1:%v R2:%v \n", r1, r2)

	r1, r2 = divideQAndR(subject, target)
	fmt.Printf("DivideQAndR R1:%v R2:%v \n", r1, r2)
}

OUTPUT:
// goos: linux
// goarch: amd64
// pkg: gosandbox/math
// BenchmarkAdd-8                          1000000000               0.378 ns/op
// BenchmarkSubtract-8                     1000000000               0.394 ns/op
// BenchmarkMultiply-8                     1000000000               0.447 ns/op
// BenchmarkDivideQuotientOnly-8           1000000000               0.565 ns/op
// BenchmarkDivideRemainderOnly-8          1000000000               0.738 ns/op
// BenchmarkDivideQandR-8                  1000000000               0.826 ns/op
// PASS
// ok      gosandbox/math  3.698s
Benchmark Codes
package main

import (
	"testing"
)

var result1 int
var result2 int

func BenchmarkAdd(b *testing.B) {
	var r1, r2 int

	for n := 0; n < b.N; n++ {
		r1, r2 = add(n, target)
	}

	result1 = r1
	result2 = r2
}

func BenchmarkSubtract(b *testing.B) {
	var r1, r2 int

	for n := 0; n < b.N; n++ {
		r1, r2 = subtract(n, target)
	}

	result1 = r1
	result2 = r2
}

func BenchmarkMultiply(b *testing.B) {
	var r1, r2 int

	for n := 0; n < b.N; n++ {
		r1, r2 = multiply(n, target)
	}

	result1 = r1
	result2 = r2
}

func BenchmarkDivideQuotientOnly(b *testing.B) {
	var r1, r2 int

	for n := 0; n < b.N; n++ {
		r1, r2 = divideQuotientOnly(n, target)
	}

	result1 = r1
	result2 = r2
}

func BenchmarkDivideRemainderOnly(b *testing.B) {
	var r1, r2 int

	for n := 0; n < b.N; n++ {
		r1, r2 = divideRemainderOnly(n, target)
	}

	result1 = r1
	result2 = r2
}

func BenchmarkDivideQandR(b *testing.B) {
	var r1, r2 int

	for n := 0; n < b.N; n++ {
		r1, r2 = divideQAndR(n, target)
	}

	result1 = r1
	result2 = r2
}

See the CPU rate differences between each operations using only Go compiler? This is not some backward Pentinum 4 (486) CPU, this is on i7 CPU.

However, choosing whether to divide or to multiply, if given a choice (like you still able to simplify your mathematical model or explore another formulation) and your CPU does show division has high cost, you can go for multiplication. Otherwise, use divide as it is part of the requirement and save time and efforts.

One example of math model alteration (exponent base conversion):

B^y = E^x
y*logB(B) = x*logB(E)
y*(1) = x*logB(E)
y = x*logB(E)         --- [1]

OR 

B^y = E^x
y*logE(B) = x*logE(E)
y*logE(B) = x*(1)
y = x / logE(B)       --- [2]

y = 5 / (2 + x)       --- [3]

In this case, [1] is favored over [2] when having a choice based on the operations data comparison above.

However, for case [3], it makes not sense to further simplify it to multiplication since it complicates things. Therefore, use the divide instead.

@Ethindp has a point about my paranoid over division: all the explanations here, as far as the modern processor goes (e.g. MHz to GHz), even in my point-of-view, should be considered as premature optimization but be aware of it. I can clarify that I’m an old timer that approach things bottom up, and made some hiccup here and there in the past, thus the built-up sensitivity towards division.

However as I iterate again: DO NOT get religious about “you should not using divide at all”. Solve the problem first the way you understand. Performance optimization always come in later, after you solve the problem. Changing math from division to multiplication is a kind of optimization.

3. The Need Of Maintaining Int Data Types

Back to the question, as far as I understand current question (unless OP did not mention the details out), it’s about the comparing 2 digits (finite values: 0 to 9) and the need to disassemble number into a digit slice then reassemble back.

There is no requirement for addition/subtraction/multiplication/divide of any digits except logical comparison (less than or equal) either from left-hand-side first or right-hand-side. If OP wants this, then it makes no sense to even get into string.

That’s why I’m on the byte side. It’s not complicated and algorithmic-ally, it makes a lot of visual sense. The best part about it is that the byte representations still maintains its mathematics properties for comparison purpose.

It also does not means that it can’t be done mathematically. It can. See above, and the benchmark difference, the requirements, and make your own judgement from there.

Peace.

1 Like