"仁扬" 发布的文章

我日常的开发环境是 Manjaro Linux,PHP 是自己编译的 7.2。当我升级系统后(起初不知道升级了,电脑开机很久也没关机),发现我的 PHP 炸了!!!

php: error while loading shared libraries: libicui18n.so.73: cannot open shared object file: No such file or directory

报了这个错误~

libicui18n 是国际化组件 Unicode(ICU) 库的一部分,且 PHP 正是依赖它来处理国际化功能。

libicui18n.so.73 找不到了?估计是升级后变成 74 或者 75 了?还是被删了……

搜了资料发现可以自己安装:

yay -S icu

对了,如果你是其他发行版,可以参考以下命令:

# Ubuntu/Debian 系统
sudo apt-get update
sudo apt-get install libicu-dev

# CentOS/RHEL 系统
sudo yum update
sudo yum install libicu

# Fedora 系统
sudo dnf update
sudo dnf install libicu

然而,我发现我的系统中已经安装了 icu 库,确实是 74 版本:

ls /usr/lib/libicui18n.so.74

OK,那我创建一个符号链接,将已安装版本的库链接到 PHP 尝试加载的版本,这下总可以了吧!

sudo ln -s /usr/lib/libicui18n.so.74 /usr/lib/libicui18n.so.73

很意外,报了另一个错误?

php: error while loading shared libraries: libicuuc.so.73: cannot open shared object file: No such file or directory

继续:

sudo ln -s /usr/lib/libicuuc.so.74 /usr/lib/libicuuc.so.73

好家伙,又报另一个错:

php: error while loading shared libraries: libicudata.so.73: cannot open shared object file: No such file or directory

sudo ln -s /usr/lib/libicudata.so.74 /usr/lib/libicudata.so.73

补齐了以上 3 个符号链接后,我再次尝试运行 PHP,终于能用了!!!

如果你也遇到类似的报错,可以参考。不过,对于大版本的更新,有可能链接后是没办法工作的,这个时候还是重新编译 PHP 吧!

以后可能还会遇到这个问题,索性就记录下来,明天除夕,提前祝大家新你快乐~

最近文章快凉凉了,大家记得给文章点赞支持~谢谢大家的喜欢!关注我,分享有趣的知识共同学习!

AI 大模型太火爆了!工作中经常使用,感觉已经离不开了大模型了!最近测试了智谱AI大模型,实测下来真的很好用!

今天的股市太绿了,我让它给我写个文案,没想到还给股民按摩了哈哈!

你是一名文学家,请你为我创作文案,主题是 2024 年,A 股走势绿得可怕。要求:语言流畅通顺,观点独特,行文风骚。

2024年,A股走势绿意盎然,恍若春日里的翠竹,节节败退。那抹绿,如同一幅泼墨山水,挥毫之间,惊心动魄。
这一年,股市的绿意犹如江南的梅雨,绵延不绝,投资者们的心情也随着这绿意,起伏不定。有人调侃:“绿”成了心头大患,也有人戏言:“要想生活过得去,头上总得带点绿。”
然而,在这片绿海之中,智者看到了机遇,勇者捕捉到了逆袭的曙光。绿得可怕,恰似风雨前的乌云,预示着一场翻天覆地的变革即将来临。
风骚独步的投资者,把这片绿色视为一种挑战,一种磨砺。他们深知,在这片绿意盎然的A股市场,唯有独具慧眼,方能洞察先机,把握那稍纵即逝的盈利机会。
绿得可怕,是因为市场在洗牌,在重塑。那些站在风口浪尖的企业,或将涅槃重生,或将黯然离场。而作为投资者的我们,唯有紧跟时代步伐,拥抱变革,方能在这片绿色的股市中,破茧成蝶,翩翩起舞。
2024年,A股走势绿得可怕,但这绿色背后,却蕴藏着无尽的机遇与挑战。让我们以独特的视角,审视这片绿色的股市,携手共进,书写属于我们的辉煌篇章。

我平时使用 Go 语言,但我发现官方只给了 Python 和 Java 的接口!

Python: https://github.com/zhipuai/zhipuai-sdk-python-v4

Java: https://github.com/zhipuai/zhipuai-sdk-java-v4

这对于我们 Go 语言开发者来说,确实有点不方便。

翻遍了全球最大的男性交友平台——GitHub!Go 语言一个 SDK 都没有!难道要自己手写?

对我这种喜欢偷懒的人来说,是不可能自己写的。

我坏了十个键盘,每一个都是坏了 Ctrl C V 这三个键。

研究了下接口文档(智谱AI开放平台),发现智谱 AI 的 v4 版本接口和 OpenAI 的接口长得挺像的!

那就用 go-openai 这个包试试水!GitHub - sashabaranov/go-openai: OpenAI ChatGPT, GPT-3, GPT-4, DALL·E, Whisper API wrapper for Go

折腾下来,发现两个问题:

第一个问题是,智谱 AI 的接口认证要用 jwt 生成 token,这个在网上一搜,没人做过。

根据文档,我自己写了个函数,得到 Authorization 头部后,传给 go-openai 作为请求的 Header 头发出去!

import "github.com/golang-jwt/jwt/v5"

// apikey 为智谱提供的 API Key
func splitAPIKey(apikey string) (string, string) {
    parts := strings.Split(apikey, ".")
    if len(parts) != 2 {
        return "", ""
    }
    return parts[0], parts[1]
}

func getAuthorization(authToken string) string {
    id, secret := splitAPIKey(authToken)
    payload := jwt.MapClaims{
        "api_key":   id,
        "exp":       time.Now().Add(3 * time.Minute).UnixMilli(),
        "timestamp": time.Now().UnixMilli(),
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
    token.Header["sign_type"] = "SIGN"
    res, _ := token.SignedString([]byte(secret))
    return res
}

第二个问题是,你能通过认证,但调用失败!!!这个我折腾了一下午,总算找到了罪魁祸首!

排查的方式是,将我的请求复制成 cURL 到终端,发现是能正常响应的!!!

而 go-openai 包在底层发送请求的时候,会自己加上以下头部:

    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Accept", "text/event-stream")
    req.Header.Set("Cache-Control", "no-cache")
    req.Header.Set("Connection", "keep-alive")

    resp, err := client.config.HTTPClient.Do(req) //nolint:bodyclose // body is closed in stream.Close()
    if err != nil {
        return new(streamReader[T]), err
    }

智谱 AI 服务器端,是不支持 Accept: text/event-stream 的!!!

感兴趣的同学可以自己测试下,只要带上 Accept 头就会报错,即便返回接口就是 text/event-stream,原因不明。

那该如何解决这个问题呢?我发现 go-openai 包里的 requestBuilder 是个 interface,但可惜没有暴露出来!

如果它暴露出来了,我可以套娃后替换它!不过请求头是在 requestBuilder 之后,所以这个方案只能放弃。

但它提供了自定义的 HTTPClient!

而 HTTPClient 最终会请求到 client.config.HTTPClient.Do(req) 方法!

所以用 RoundTrip(*Request) (*Response, error) 接口截获请求就完事了!

怎么搞呢?灰常简单:

type removeAcceptHeaderRoundTrip struct{}

func (s *removeAcceptHeaderRoundTrip) RoundTrip(req *http.Request) (resp *http.Response, err error) {
    req.Header.Del("Accept")
    resp, err = http.DefaultTransport.RoundTrip(req)
    return
}

然后初始化的时候,换成我们的 RoundTrip 就好了!

    cfg := openai.DefaultConfig(authorization)
    cfg.BaseURL = "https://open.bigmodel.cn/api/paas/v4"
    cfg.HTTPClient.Transport = &removeAcceptHeaderRoundTrip{} // 换成我们的 RoundTrip
    cli := openai.NewClientWithConfig(cfg)
    stream, err := cli.CreateChatCompletionStream(ctx, openai.ChatCompletionRequest{
        // Model: "glm-3-turbo",
        Model: "glm-4",
        Messages: []openai.ChatCompletionMessage{
            {Role: openai.ChatMessageRoleUser, Content: input},
        },
    })

是不是很惊艳!!!interface 大法好!抽象得当,有时候写代码真的就是几分钟的事情!!!

不得不说 Go 的标准库是真的舒服,简单翻一下代码,就知道怎么写了,1 分钟的事情干完下班!!!

(如果觉得文章有帮助,建议收藏起来,方便下次阅读)

之前写文章的时候,有人喷我用 interface 用 wire 把代码搞得太复杂了……

仁扬:我是如何组织 Go 代码的(目录结构 依赖注入 wire)

现在这个就是活生生的例子……

假如没有暴露 RoundTrip 接口,那你什么都做不了,你只能硬质头皮自己写 SDK 写反射写各种恶心的黑科技……

总之,写代码抽象思维很重要,需要时刻注意代码的领域和边界!!!

夜已深,我把完整的代码贴上来吧,大家自行食用:

package main

import (
    "context"
    "errors"
    "fmt"
    "io"
    "net/http"
    "strings"
    "time"

    "github.com/golang-jwt/jwt/v5"
    "github.com/sashabaranov/go-openai"
)

func main() {
    input := `你是一名文学家,请你为我创作文案,主题是2024年,A股走势绿得可怕。要求:语言流畅通顺,观点独特,行文风骚。
`
    ctx := context.Background()
    authToken := "......"
    authorization := getAuthorization(authToken)
    cfg := openai.DefaultConfig(authorization)
    cfg.BaseURL = "https://open.bigmodel.cn/api/paas/v4"
    cfg.HTTPClient.Transport = &removeAcceptHeaderRoundTrip{} // 替换成我们自己的
    cli := openai.NewClientWithConfig(cfg)
    stream, err := cli.CreateChatCompletionStream(ctx, openai.ChatCompletionRequest{
        // Model: "glm-3-turbo",
        Model: "glm-4",
        Messages: []openai.ChatCompletionMessage{
            {Role: openai.ChatMessageRoleUser, Content: input},
        },
    })
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    defer stream.Close()

    fmt.Print(input)
    fmt.Println("")

    for {
        response, e := stream.Recv()
        if errors.Is(e, io.EOF) {
            break
        }
        if e != nil {
            break
        }
        content := response.Choices[0].Delta.Content
        fmt.Print(content)
    }

    fmt.Println("")

    return
}

func getAuthorization(authToken string) string {
    id, secret := splitAPIKey(authToken)
    payload := jwt.MapClaims{
        "api_key":   id,
        "exp":       time.Now().Add(3 * time.Minute).UnixMilli(),
        "timestamp": time.Now().UnixMilli(),
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
    token.Header["sign_type"] = "SIGN"
    res, _ := token.SignedString([]byte(secret))
    return res
}

func splitAPIKey(apikey string) (string, string) {
    parts := strings.Split(apikey, ".")
    if len(parts) != 2 {
        return "", ""
    }
    return parts[0], parts[1]
}

type removeAcceptHeaderRoundTrip struct{}

func (s *removeAcceptHeaderRoundTrip) RoundTrip(req *http.Request) (resp *http.Response, err error) {
    req.Header.Del("Accept")
    resp, err = http.DefaultTransport.RoundTrip(req)
    return
}

如果觉得文章有帮助,建议收藏起来,方便下次阅读。

喜欢我的文章可以关注我,分享有趣的知识共同学习!

注:本文包含AI创作。

第一部分:浮点数的基础

一、定义与原理

浮点数是计算机科学中用于近似表示实数的一种方法。它们对于处理广泛的值特别有用,特别是在数值的大小超出整数所能表示的范围时。

  1. 表示实数:在现实世界中,许多量(如温度、距离或速度)可以是任意精度的实数。浮点数允许计算机以有限的数字位来近似这些数值。
  2. 构成:一个浮点数由三个部分组成:符号位(表明正负)、指数位(决定数值的规模或大小)和尾数位(提供实际数值的精确度)。
  3. 动态范围:通过分离数值的大小(指数)和精确度(尾数),浮点数能够覆盖非常大的范围,从非常小的数到非常大的数。

二、浮点数的内部表示(IEEE 754标准)

IEEE 754标准是一种广泛采用的标准,用于在计算机中表示浮点数。

  1. 标准格式

    • 单精度(32位):这种格式使用1位符号位、8位指数位和23位尾数位。
    • 双精度(64位):在这种格式中,1位用于符号,11位用于指数,52位用于尾数。
    • 格式:| sign | exponent | fraction |
  2. 符号位:第一位是符号位,0表示正数,1表示负数。
  3. 指数位:指数位用于表示数值的大小。它是一个偏移的二进制表示,这意味着指数的实际值是指数位的值减去一个偏移量。

为什么指数位的值需要偏移量?
比如单精度浮点数,8位指数位可以表达256个数,但只能是非负数。
如果使用+127偏移量(也就是指数3用130表示),负数就可以直接表达,并且可以直接比较大小。

  1. 尾数位:尾数位(或称为有效位)提供了数值的精度。有两种情况:

    • 规约形式:在规约形式中,浮点数被表示为一个非零的数字,这个数字的小数点前有一个非零的数字(通常是1)。这样的表示确保了浮点数的精度和有效位数最大化。
    • 非规约形式:用来表达非常接近0的数字。在这种形式下,浮点数的表示允许尾数的最高位是0,这意味着可以表示比标准规约形式更小的数。

这个地方可能看着很奇怪,它的作用在于对接近 0 的数字提供更高的精度。

比如单精度浮点数 “0.000 ... 00102”,可以表示为 1.02 10^a,也可以表示为 0.000...102 10^b。

如果 a 溢出了,存不了更小的小表达负数(只能存 0,指数位有偏移,没有更小的数了),这个数就表达不了!

但是用 b 表达就可以装进 8 位指数位,也就可以保证精度了。

  1. 特殊值的表示:IEEE 754还定义了一些特殊值的表示方式,如正无穷大(+Infinity)、负无穷大(-Infinity)和非数值(NaN,Not a Number)。

第二部分:使用浮点数时的挑战和注意事项

  1. 精度问题

    • 浮点数由于精度的限制,有时候对计算会有一定的影响。

比如,二进制浮点数不能表达0.1和0.01,0.1的平方既不是准确的0.01,也不是最接近0.01的可表达的数。

浮点数也不能表达圆周率 pi,所以 tan(pi/2) 不等于正无穷,也不会溢出。

  1. 比较与运算

我一般是缩放一定的比例后转换成 int 再比较,GPT4 给了一个新的思路:

package main

import (
    "fmt"
    "math"
)

// FloatEquals 比较两个浮点数是否足够接近,以epsilon作为容差
func FloatEquals(a, b, epsilon float64) bool {
    return math.Abs(a - b) < epsilon
}

func main() {
    a := 0.1
    b := 0.2
    c := 0.3

    // 直接比较 - 往往不可靠
    fmt.Println("Direct comparison:", b+c == a) // false

    // 使用FloatEquals函数比较
    epsilon := 1e-9 // 定义一个足够小的epsilon值
    fmt.Println("Epsilon comparison:", FloatEquals(b+c, a, epsilon)) // true
}
  1. 特殊值的处理(+Inf-InfNaN

    • Go 语言如果你使用浮点数作为 map 的 key,你会发现 map[NaN] != map[NaN]!
    • 因为 NaN != NaN,所以哈希后也是不一致的,底层会加上随机数保证这一特性。
    • 最好不要直接用浮点数当作 key,除非你明确知道数据的形式。
  2. 数值稳定性问题

    • 在连续的运算中,尤其是涉及大量或小量级数值的运算时,精度损失可能会累积。

参考资料:

想不想知道 e5 洋垃圾的性能?好家伙,我手痒了!!!

前阵子我用 Go 写了一个小工具 —— GoHyperPi!通过计算圆周率 π 的值,测试 CPU 的单核和多核性能。

代码是开源的,可以移步到 GitHub 查看 → https://github.com/Lofanmi/GoHyperPi

与传统的性能测试软件 SuperPi(只能单核)、HyperPi(支持多核但不跨平台)相比,

GoHyperPi 能更充分利用多核处理器的并行计算能力,跨平台地进行性能测试,以更全面和准确地评估计算机的处理器性能。

为什么我要写这个软件呢?

1、Super PI 这个软件是单核的,而 HyperPi 支持多核但是又不跨平台;

2、Hyper PI 使用的人偏少,跑分数据不全面;

3、我组装了双路 E5-2690 v4,我想知道它的性能是怎么样的!!!

而 Go 语言有调度器有协程,写一个并行计算的程序,对我来说这不和呼吸一样简单?

嘿嘿,仅用 200 行代码不到~

它的原理是什么?

1、-n 指定圆周率小数点后的位数。我的测试都是计算圆周率 10 万位;

2、-proc 并行数量,默认程序自动获取 CPU 的核心数量(超线程会当做真正的核心)。程序会启动一定数量的协程,计算圆周率,并测量时间;

3、-times 并行倍数。最终的跑分结果为小数点第几位每秒,倍数越高得分越高,默认 2 倍。

废话不多说,先看跑分!双路 E5-2690 v4 洋垃圾,28 核心 56 线程,单核跑分 1838.56,多核跑分 102608.14!

阅读全文