Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rounding value half-up to 2 decimal places in Go

Tags:

Rounding positive value (example here: 1.015) half-up to 2 decimal places using math.Round() in Go:

fmt.Println(math.Round(1.015*100) / 100)

Go Playground

I got: 1.02. That's correct.

But when I employed a function to do the same job:

func RoundHalfUp(x float64) float64 {
    return math.Round(x*100) / 100
}

Go Playground

I got 1.01.

What's wrong with the RoundHalfUp function?

like image 262
Pauli Avatar asked Oct 09 '18 03:10

Pauli


1 Answers

The Go Programming Language Specification

Constants

Numeric constants represent exact values of arbitrary precision and do not overflow.

Implementation restriction: Although numeric constants have arbitrary precision in the language, a compiler may implement them using an internal representation with limited precision. That said, every implementation must:

  • Represent floating-point constants, including the parts of a complex constant, with a mantissa of at least 256 bits and a signed
    binary exponent of at least 16 bits.
  • Round to the nearest representable constant if unable to represent a floating-point or complex constant due to limits on precision.

These requirements apply both to literal constants and to the result of evaluating constant expressions.

Constant expressions

Constant expressions may contain only constant operands and are evaluated at compile time.

Constant expressions are always evaluated exactly; intermediate values and the constants themselves may require precision significantly larger than supported by any predeclared type in the language.

Implementation restriction: A compiler may use rounding while computing untyped floating-point or complex constant expressions; see the implementation restriction in the section on constants. This rounding may cause a floating-point constant expression to be invalid in an integer context, even if it would be integral when calculated using infinite precision, and vice versa.


Implement the RoundHalfUp function like the Go compiler does for math.Round(1.015*100) / 100. 1.015*100 is a untyped floating-point constant expression. Use the math/big package with at least 256 bits of precision. Go float64 (IEEE-754 64-bit floating-point) has 53 bits of precision.

For example, with 256 bits of precision (constant expression),

package main

import (
    "fmt"
    "math"
    "math/big"
)

func RoundHalfUp(x string) float64 {
    // math.Round(x*100) / 100
    xf, _, err := big.ParseFloat(x, 10, 256, big.ToNearestEven)
    if err != nil {
        panic(err)
    }
    xf100, _ := new(big.Float).Mul(xf, big.NewFloat(100)).Float64()
    return math.Round(xf100) / float64(100)
}

func main() {
    fmt.Println(RoundHalfUp("1.015"))
}

Playground: https://play.golang.org/p/uqtYwP4o22B

Output:

1.02

If we only use 53 bits of precision (float64):

xf, _, err := big.ParseFloat(x, 10, 53, big.ToNearestEven)

Playground: https://play.golang.org/p/ejz-wkuycaU

Output:

1.01
like image 53
peterSO Avatar answered Oct 04 '22 03:10

peterSO