Rounding to k digits

244 views
Skip to first unread message

Jochen Voss

unread,
Aug 13, 2025, 8:42:35 AMAug 13
to golang-nuts
Dear all,

I would like to define a function "func Round(x float64, digits int) float64" which rounds to the given number of digits, in the sense that I want "strconv.FormatFloat(x, 'f', -1, 64)" to show at most the given number of digits after the decimal point.

The following naive approach does not work:

func Round(x float64, digits int) float64 {
eps := math.Pow10(-digits)
return math.Round(x/eps) * eps
}

For example for rounding math.Pi to five digits I get "3.1415900000000003" instead of "3.14159".  https://go.dev/play/p/gRtHG6ZgTjj .

Anthropic's Claude suggested the following:

func Round(x float64, digits int) float64 {
if digits <= 0 {
pow := math.Pow10(-digits)
return math.Round(x/pow) * pow
}

format := "%." + strconv.Itoa(digits) + "f"
s := fmt.Sprintf(format, x)
result, _ := strconv.ParseFloat(s, 64)

return result
}

This seems to work, but also seems quite inefficient.

Is there a better way?

All the best,
Jochen

PS.: Here is some testing code for experimenting https://go.dev/play/p/Xcd6fTvYend


robert engels

unread,
Aug 13, 2025, 8:52:08 AMAug 13
to Jochen Voss, golang-nuts
Read up on numerical analysis - what you are asking for is impossible :)

You need to convert to a string, or use BCD/fixed place values - like github.com/robaho/fixed

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/7d3ccdd4-d88b-4eba-8a36-02b51b7751e1n%40googlegroups.com.

Jochen Voss

unread,
Aug 13, 2025, 8:55:46 AMAug 13
to golang-nuts
Dear Robert,

Thank you for your response.  To make sure I understand: Are you saying Claude's Round() function does not work?  (For which example?)  Or are you saying it is impossible to do better than Claude's function?

Many thanks,
Jochen

Alexander Ertli

unread,
Aug 13, 2025, 9:07:46 AMAug 13
to robert engels, Jochen Voss, golang-nuts

Hi Jochen,

I think it's possible with a trick.

My first naive thought on how to solve this is to simply shift the decimal point over, do the rounding on the whole number, and then shift it back.

import "math" 
// Round performs rounding by shifting the decimal, rounding, and shifting back. 
func Round(x float64, digits int) float64 {
   scale := math.Pow(10, float64(digits)) 
  return math.Round(x*scale) / scale 
}


Peter Weinberger (温博格)

unread,
Aug 13, 2025, 9:10:50 AMAug 13
to Alexander Ertli, robert engels, Jochen Voss, golang-nuts
The canonical reference on this is by Guy Steele, "How to Print
Floating Point Numbers Accurately"
> To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/CAC4E5Zk1bq7z%2B%2BePzBRnsCoEYWsAW0RJzAd6KvjqNeJyRayDOQ%40mail.gmail.com.

Bushnell, Thomas

unread,
Aug 13, 2025, 9:15:58 AMAug 13
to Peter Weinberger (温博格), Alexander Ertli, robert engels, Jochen Voss, golang-nuts
I think the OP is getting caught up by confusing "decimal precision" with some property of a binary floating point number.

To OP: You need to think of x here as a binary floating point number, and not a real number. It does not have infinite precision, and its properties are governed by the restrictions of binary floating point, not decimal floating point. Importantly, there is no exact representation of 10^-5 in binary.

If you are concerned with how things print in decimal, you should do this by converting them to decimal, and the generally used advice is to do so as late as possible - namely, at the point when you are printing them for a human. (And for that, Steele's article is the absolute bog standard way to do it, don't try it yourself because there are many subtleties!)

There is no such thing, really, as a binary floating point number with N digits of decimal precision, in the sense that it has some stable property you might have expected from the properties of real numbers.

Thomas

-----Original Message-----
From: 'Peter Weinberger (温博格)' via golang-nuts <golan...@googlegroups.com>
Sent: Wednesday, August 13, 2025 2:09 PM
To: Alexander Ertli <ertli.a...@googlemail.com>
Cc: robert engels <ren...@ix.netcom.com>; Jochen Voss <joche...@gmail.com>; golang-nuts <golan...@googlegroups.com>
Subject: Re: [go-nuts] Rounding to k digits

This message was sent by an external party.
To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/CAOUkXSq6KRgyyRWd1MJiaERxc%2BQYO%2BoGC3SSwhQ5_CGZ2WY05A%40mail.gmail.com.

Jochen Voss

unread,
Aug 13, 2025, 9:16:51 AMAug 13
to golang-nuts
Hi Alexander,

Thanks, this is interesting.  My original


func Round(x float64, digits int) float64 {
eps := math.Pow10(-digits)
return math.Round(x/eps) * eps
}

leads to near-immediate fuzzing failures, whereas your

func Round(x float64, digits int) float64 {
   scale := math.Pow(10, float64(digits)) 
  return math.Round(x*scale) / scale 
}

has now survived 4 minutes of fuzzing.  Maybe the difference is that scale can be represented exactly, while eps has truncation error?

Many thanks,
Jochen

Jochen Voss

unread,
Aug 13, 2025, 9:18:22 AMAug 13
to golang-nuts
Dear Peter,

Thanks for the reference!

All the best,
Jochen

Jochen Voss

unread,
Aug 13, 2025, 9:29:00 AMAug 13
to golang-nuts
Hi Thomas,

The background is as follows:

I have a library function which tries not to impose it's own precision/rounding on library users.  The output ends up in a PDF file, by which point it needs to be converted to text for writing to the file.  Something like this: https://pkg.go.dev/seehuhn.de/go/pdf/graphics#Writer.LineTo .  Because the library does not want to prescribe the precision, strconv.FormatFloat(x, 'f', -1, 64) is used when coordinates and dimensions are coverted to text for include in the PDF.  To me, float64 seems to be the only reasonable type for the coordinate arguments of LineTo().  Using either string or some external "decimal number with precision" class for the coordinates would feel extremely awkward to me.

The Round() function is meant as a convenience for library users, when they know whether they want 3.1 or 3.14 or 3.142 inside the PDF file.

All the best,
Jochen

robert engels

unread,
Aug 13, 2025, 9:42:18 AMAug 13
to Jochen Voss, golang-nuts
A floating point number cannot represent all possible real numbers - so when you “shift it back” the number you expect may not be possible.

See this on why 0.1 cannot be represented. https://how.dev/answers/why-does-01-not-exist-in-floating-point

Jason E. Aten

unread,
Aug 13, 2025, 6:33:40 PMAug 13
to golang-nuts
Hi Jochen,

I think you can get what you want simply with the fmt package.

Consider fmt.Sprintf("%0.5f", x), for example, if you wanted 5 decimal places.

If you need to actually round a floating point number, rather than just displaying 
a certain number of places, the CockroachDB guys wrote a good summary of
the approaches here, back in 2017:


As they mention, there is now https://pkg.go.dev/math#Round in the standard
library, which would typically be used by:

multiplying by a power of 10, math.Round()-ing, and then dividing by
the same power of 10.

Jason E. Aten

unread,
Aug 13, 2025, 6:38:06 PMAug 13
to golang-nuts
I'll add that I typically just use fmt.Sprintf("%v", x), which gives the shortest possible unambiguous
string representation of the floating point value x. 

Brian Candler

unread,
Aug 14, 2025, 5:04:03 PMAug 14
to golang-nuts
On Wednesday, 13 August 2025 at 20:10:50 UTC+7 Peter Weinberger (温博格) wrote:
The canonical reference on this is by Guy Steele, "How to Print
Floating Point Numbers Accurately"

Bushnell, Thomas

unread,
Aug 15, 2025, 4:28:42 AMAug 15
to Brian Candler, golang-nuts

That’s a great page, but this part is a whopper: “Computers can only natively store integers.” If we mean “what the hardware directly does” it doesn’t “store integers”, it represents them by patterns of magnetic fields or whatever. But there is nothing “native” about the method used to store integers, certainly not compared to the way it stores fractions.

 

There are just representations, none of them more native than another. There are whole languages in which rational numbers are stored exactly. (And since floating point stores rational approximations only, this isn’t a difference.) You can even design languages which store real numbers exactly. (Wait, what? Doesn’t it require infinite memory to store an arbitrary real? Of course it does! But you cannot input an arbitrary real, and so you only need to store what you can input [always finitely representable] and compute. And you can compute with, well, magic; stay tuned.)

 

So it is a decent explanation of the basics and an overview of languages. But that’s a whopper.

 

Ok, now as it notes, Scheme has a notion of “exactness”. How might we go about representing real numbers exactly in Scheme, once we note that we are only on the hook for representing what we can input and the results of computations? Well…. You store them symbolically!

 

So your pi constant is just a symbol “pi” and you don’t try to represent its decimal expansion at all until you output it. (And when you output it, you only need to print so many digits, which you can compute as you output them.) So what about double that? Or its square root? Same thing…represent it symbolically, and then do the necessary computation of a series expansion at the time you output the number. This is terribly inefficient, of course. I’m not suggesting it’s a replacement for floating point numbers (nor are exact rationals, though they should get more use than they unfortunately do). But it is not impossible, and, more to the point, it is not “less native” than integers.

 

(Unless by “native” you mean “implemented by assembly instructions”, but then, floating point are implemented by assembly instructions…)

 

Thomas

 

 

From: 'Brian Candler' via golang-nuts <golan...@googlegroups.com>
Sent: Thursday, August 14, 2025 10:04 PM
To: golang-nuts <golan...@googlegroups.com>
Subject: Re: [go-nuts] Rounding to k digits

 

This message was sent by an external party.

 

On Wednesday, 13 August 2025 at 20:10:50 UTC+7 Peter Weinberger (温博格) wrote:

--

You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Jan Mercl

unread,
Aug 15, 2025, 9:22:19 AMAug 15
to Bushnell, Thomas, Brian Candler, golang-nuts
The only thing a binary computer can store is a bit. A bit is either 0 or 1, both are integers. Or we can say that it can only store booleans true and false, but that doesn't change anything important as the cardinality of those sets are the same 

Everything else is an agreed upon interpretation of one or more bits.


Bushnell, Thomas

unread,
Aug 15, 2025, 11:03:03 AMAug 15
to Jan Mercl, Brian Candler, golang-nuts

Your point is well taken! But I think it’s your focus on the cardinality that matters here. There is nothing inherent in the bit that it is “0 or 1”, which happen to be integers.

 

It is just as sensible to say it is “pi” or “pi + 2”, in terms of what the actual storage is. It’s anything from a doubleton set and everything else is interpretation. The conventional interpretation, as 0 or 1, is just conventional, and is not something about what it “natively stores”. 😊

Reply all
Reply to author
Forward
0 new messages