Golang实现简易的1位小数计数器

Golang实现简易的1位小数计数器 我正在尝试实现一个计数器,用于统计我的游戏中已经过去了多少秒。它仅基于秒数,我需要它用于一个游戏服务器项目。我想做的,真的只是 0 + 0.2,然后下一个 tick 0.2+0.20.4+0.2…… 使用浮点数无法实现这一点,因为最终会得到 1.99999999998 而不是 2,这会破坏计算。我研究过 Big Int、定点精度库,但我不确定应该使用什么。我不希望有太多开销,因为这每秒会被调用 5-10 次。简单的 0 + 0.2 会完美地工作,这样我就可以判断是否过去了一秒或半秒,并且我可以将之前的时间存储在一个简单的数字中。

想法是玩家会有一个上次执行操作的时间戳,我会根据时间检查是否已经过去了 X 时间(time- lastTime > X),如果为真,我将执行某些操作。我可以访问服务器的 tick 率,我将其设置为 0.2。如果没有其他办法,我总是可以使用操作系统时间。对于该怎么做有什么建议吗?


更多关于Golang实现简易的1位小数计数器的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

你可以使用整数数据类型(int64)来表示200毫秒。

更多关于Golang实现简易的1位小数计数器的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


当然我可以,但 @j-forster 提供了一个很好的例子。

能详细说明一下吗?我更喜欢使用较小的十进制数,但我想使用较大的数字也能达到同样的效果。

如果你使用200毫秒作为固定的步长,可以创建一个自定义整数类型,将200毫秒精确地定义为一个步长。

package main

import (
	"fmt"
)

type Counter int64 // 200ms step
const (
	Step200MS  Counter = 1
	StepSecond         = 5 * Step200MS
)

func (c Counter) String() string {
	return fmt.Sprintf("%d.%ds", c/5, c%5*200)
}

func main() {
	var c Counter

	c += Step200MS
	fmt.Printf("time: %s\n", c)

	c++ // equals 200ms
	fmt.Printf("time: %s\n", c)

	c += StepSecond
	fmt.Printf("time: %s\n", c)
}

参见 https://play.golang.org/p/7f7RyfvLK66

对于游戏服务器中的时间计数,使用浮点数确实会累积精度误差。以下是几种实用的解决方案:

方案一:使用整数计数(推荐)

type Counter struct {
    ticks   int     // 总tick数
    tickRate int    // 每tick的毫秒数
}

func NewCounter(tickRateMs int) *Counter {
    return &Counter{tickRate: tickRateMs}
}

func (c *Counter) Tick() {
    c.ticks++
}

func (c *Counter) ElapsedSeconds() float64 {
    return float64(c.ticks * c.tickRate) / 1000.0
}

func (c *Counter) HasElapsed(lastTime int, intervalMs int) bool {
    elapsed := c.ticks - lastTime
    return elapsed*c.tickRate >= intervalMs
}

// 使用示例
counter := NewCounter(200) // 0.2秒 = 200毫秒
counter.Tick()
seconds := counter.ElapsedSeconds() // 0.2

方案二:使用 time.Duration

type TimeCounter struct {
    startTime time.Time
    tickRate  time.Duration
    tickCount int
}

func NewTimeCounter(tickRateMs int) *TimeCounter {
    return &TimeCounter{
        startTime: time.Now(),
        tickRate:  time.Duration(tickRateMs) * time.Millisecond,
    }
}

func (tc *TimeCounter) Tick() {
    tc.tickCount++
}

func (tc *TimeCounter) Elapsed() time.Duration {
    return time.Duration(tc.tickCount) * tc.tickRate
}

func (tc *TimeCounter) HasElapsed(lastTick int, intervalTicks int) bool {
    return tc.tickCount-lastTick >= intervalTicks
}

// 检查是否过去特定时间
func (tc *TimeCounter) HasTimeElapsed(lastTime time.Duration, interval time.Duration) bool {
    return tc.Elapsed()-lastTime >= interval
}

方案三:使用定点数(避免浮点误差)

import "math/big"

type FixedPointCounter struct {
    value    *big.Rat
    tickRate *big.Rat
}

func NewFixedPointCounter(tickRate float64) *FixedPointCounter {
    return &FixedPointCounter{
        value:    big.NewRat(0, 1),
        tickRate: big.NewRat(int64(tickRate*10), 10), // 0.2表示为2/10
    }
}

func (fpc *FixedPointCounter) Tick() {
    fpc.value.Add(fpc.value, fpc.tickRate)
}

func (fpc *FixedPointCounter) ElapsedSeconds() float64 {
    result, _ := fpc.value.Float64()
    return result
}

func (fpc *FixedPointCounter) GreaterThan(threshold float64) bool {
    thresholdRat := big.NewRat(int64(threshold*10), 10)
    return fpc.value.Cmp(thresholdRat) > 0
}

方案四:基于系统时间的简单实现

type GameTimer struct {
    startTime time.Time
}

func NewGameTimer() *GameTimer {
    return &GameTimer{startTime: time.Now()}
}

func (gt *GameTimer) ElapsedSeconds() float64 {
    elapsed := time.Since(gt.startTime)
    return elapsed.Seconds()
}

func (gt *GameTimer) CheckAction(lastTime time.Time, intervalSeconds float64) bool {
    elapsed := time.Since(lastTime).Seconds()
    return elapsed >= intervalSeconds
}

// 使用示例
timer := NewGameTimer()
lastAction := time.Now()

// 每tick检查
if timer.CheckAction(lastAction, 1.0) { // 检查是否过去1秒
    // 执行操作
    lastAction = time.Now()
}

对于游戏服务器,推荐使用方案一(整数计数),因为:

  1. 完全避免浮点数精度问题
  2. 性能开销最小
  3. 逻辑简单清晰
  4. 每秒5-10次的调用频率完全无压力

整数方案可以直接用tick数进行计算和比较,不需要处理浮点数误差,是最适合游戏循环时间统计的方法。

回到顶部